Prepare
本文將使用一個NuGet公開的組件技術來實現一個局域網聊天程序,利用組件提供的高性能異步網絡機制實現,免去了手動編寫底層的困擾,易於二次開發,擴展自己的功能。
聯系作者及加群方式(激活碼在群里發放):http://www.hslcommunication.cn/Cooperation
在Visual Studio 中的NuGet管理器中可以下載安裝,也可以直接在NuGet控制台輸入下面的指令安裝:
Install-Package HslCommunication
NuGet安裝教程 http://www.cnblogs.com/dathlin/p/7705014.html
Summary
之前已經有篇博客說明了同步網絡通信的開發,同步網絡通信適用於什么樣的場景呢,適用於客戶端向服務器請求數據,必須有數據返回的情況,無論成功還是失敗。地址:http://www.cnblogs.com/dathlin/p/7697782.html
而異步的網絡通信適用於什么情況呢,適用於服務器進行群發數據的時候,比如發送消息給所有的在線客戶端,為了更好的說明異步網絡通信的實現機制,開發一個多客戶端的局域網聊天程序來演示異步操作。
特性如下:
- 局域網聊天室支持多人在線,上限取決於服務器的電腦性能。
- 支持用戶名登錄,支持重復的用戶名登錄。
- 支持顯示所有在線客戶端的信息顯示,包括在線時間,上線時間,ip地址,用戶名等等。
- 支持服務器主動發消息給客戶端。
- 支持服務器強制關閉客戶端。
- 支持其他人的上下線信息跟蹤。
本聊天程序是基於C-S架構設計的,需要創建3個項目,一個服務器項目,用來中轉所有的消息的,一個是客戶端項目,也就是實際的聊天程序,本次項目還顯示所有在線的客戶端信息,ip地址,名字。
至於賬戶,本次不采用任何的用戶名密碼登錄機制,就采用簡易化處理,直接輸入一個名字即可,當然,你也可以更改成用戶名密碼登錄的機制,也不是特別困難。
簡易的聊天程序不支持圖片,表情包的發送接收,這部分實現起來不是同一個次元的,這部分以后攻克了再開新的博文。
------------> 小插曲
如果需要更復雜的功能,比如賬戶的登錄,密碼修改,版本控制,賬戶支持頭像等等,一個基於本組件擴展出來的CS架構的基礎模版項目,二次基於此進行方便的二次開發,該項目使用了好幾處的文件管理:
https://github.com/dathlin/ClientServerProject
一個C-S模版,該模版由三部分的程序組成,一個服務端運行的程序,一個客戶端運行的程序,還有一個公共的組件,實現了基礎的賬戶管理功能,版本控制,軟件升級,公告管理,消息群發,共享文件上傳下載,批量文件傳送功能。具體的操作方法見演示就行。本項目的一個目標是:提供一個基礎的中小型系統的C-S框架,客戶端有四種模式,無縫集成訪問,winform版本,wpf版本,asp.net mvc版本,Android版本。方便企業進行中小型系統的二次開發和個人學習。
Reference
日志組件所有的功能類都在 HslCommunication 和 HslCommunication.Enthernet 命名空間,所以再使用之前先添加:在服務器程序和客戶端程序都要添加
using HslCommunication; using HslCommunication.Enthernet;
Start Program
首先先創建三個項目,server項目,client項目,common項目,然后使用Nuget將客戶端和服務器兩個項目都安裝組件,然后切換到服務器程序,接下來就是真的創建程序了。
- common項目:存放一些服務器和客戶端共同用到的類。
- server項目:消息路由中心。所有的客戶端發送的消息都先經過服務器轉發。
- client項目:和用戶交互的客戶端,接受用戶的輸入並且顯示出來。
在整個項目中,核心部分就是網絡通信了,需要實現客戶端向服務器發送消息,這個相對比較好實現,因為服務器的ip地址和端口都是公開的。但是客戶端的ip和端口是未知的,因為我們要實現任意的電腦都能登錄客戶端。所以我們需要使用HslCommunication來方便的實現這些操作。
在server端和client端都需要安裝HslCommunication組件。因為我們要實現在客戶端和服務器端進行通信,通信功能眾多,所以需要進行約定,消息的id,我們最終根據消息的id來區分不同的消息。
- 1 系統消息,用於顯示誰誰誰上線了,誰誰誰下線了
- 2 是用戶發送的消息,在聊天窗口進行顯示的
- 3 客戶端在線信息,所以在線客戶端的信息
- 4 強制客戶端下線,用於服務器向客戶端發送關閉的指令,客戶端接收到后退出程序。
綜上所述,這個項目已經初步成型,而且通過消息id可以實現其他自己功能擴展,可以實現任何的交互操作。不一定是聊天系統,各種數據同步機制,推送機制,局域網機制的游戲程序也可以實現。
本項目的源代碼地址如下:https://github.com/dathlin/NetChatRoom


Server
先填寫核心塊
#region 核心網絡服務相關
private NetComplexServer complexServer;
private void ComplexServerInitialization()
{
complexServer = new NetComplexServer(); // 實例化
complexServer.KeyToken = new Guid("91625bad-d581-44ab-b121-ffff5bcb83fb"); // 設置令牌,提升安全性
complexServer.LogNet = new HslCommunication.LogNet.LogNetSingle("log.txt"); // 設置日志記錄,如果不需要,可以刪除
complexServer.ClientOnline += ComplexServer_ClientOnline; // 客戶端上線時觸發
complexServer.ClientOffline += ComplexServer_ClientOffline; // 客戶端下線時觸發
complexServer.AllClientsStatusChange += ComplexServer_AllClientsStatusChange; // 只要有客戶端上線或下線就觸發
complexServer.AcceptString += ComplexServer_AcceptString; // 客戶端發來消息時觸發
complexServer.ServerStart(12345); // 啟動服務,需要選擇一個端口
}
private void ComplexServer_AllClientsStatusChange(string object1)
{
}
private void ComplexServer_AcceptString(AsyncStateOne object1, NetHandle object2, string object3)
{
// 我們規定
// 1 是系統消息,
// 2 是用戶發送的消息
// 3 客戶端在線信息
// 4 強制客戶端下線
// 當你的消息頭種類很多以后,可以在一個統一的類中心進行規定
if (object2 == 2)
{
// 來自客戶端的消息,就只有這么一種情況
NetMessage msg = new NetMessage()
{
FromName = object1.LoginAlias,
Time = DateTime.Now,
Type = "string",
Content = object3,
};
// 群發出去
complexServer.SendAllClients(2, JObject.FromObject(msg).ToString());
}
}
private void ComplexServer_ClientOffline(AsyncStateOne object1, string object2)
{
// 客戶端下線,發送消息給客戶端
complexServer.SendAllClients(1, object1.IpAddress + " " + object1.LoginAlias + " : " + object2);
// 發送在線信息
complexServer.SendAllClients(3, RemoveOnLine(object1.ClientUniqueID));
// 在主界面顯示信息
ShowMsg(object1.IpAddress + " " + object1.LoginAlias + " : " + object2);
ShowOnlineClient( );
}
private void ComplexServer_ClientOnline(AsyncStateOne object1)
{
// 客戶端上線,發送消息給客戶端
complexServer.SendAllClients(1, object1.IpAddress + " " + object1.LoginAlias + " : 上線");
// 發送在線信息
NetAccount account = new NetAccount()
{
Guid = object1.ClientUniqueID,
Ip = object1.IpAddress,
Name = object1.LoginAlias,
OnlineTime = DateTime.Now.ToString(),
};
complexServer.SendAllClients(3, AddOnLine(account));
// 在主界面顯示信息
ShowMsg(object1.IpAddress + " " + object1.LoginAlias + " : 上線");
ShowOnlineClient( );
}
#endregion
在此處有個功能是實現對在線客戶端的信息記錄,包含了許多的信息,並可以實現擴展
#region 在線客戶端信息實現塊
private List<NetAccount> all_accounts = new List<NetAccount>();
private object obj_lock = new object();
// 新增一個用戶賬戶到在線客戶端
private string AddOnLine(NetAccount item)
{
string result = string.Empty;
lock(obj_lock)
{
all_accounts.Add(item);
result = JArray.FromObject(all_accounts).ToString();
}
return result;
}
// 移除在線賬戶並返回相應的在線信息
private string RemoveOnLine(string guid)
{
string result = string.Empty;
lock (obj_lock)
{
for (int i = 0; i < all_accounts.Count; i++)
{
if(all_accounts[i].Guid == guid)
{
all_accounts.RemoveAt(i);
break;
}
}
result = JArray.FromObject(all_accounts).ToString();
}
return result;
}
#endregion
關於在線信息的類
/// <summary>
/// 擴展實現的賬戶信息,記錄唯一標記,ip地址,上線時間,名字
/// </summary>
public class NetAccount
{
/// <summary>
/// 唯一ID
/// </summary>
public string Guid { get; set; }
/// <summary>
/// Ip地址
/// </summary>
public string Ip { get; set; }
/// <summary>
/// 上線時間
/// </summary>
public string OnlineTime { get; set; }
/// <summary>
/// 名稱
/// </summary>
public string Name { get; set; }
/// <summary>
/// 字符串標識形式
/// </summary>
/// <returns></returns>
public override string ToString()
{
return "[" + Ip + "] : " + Name;
}
}
下面演示在服務器端如何發送一個系統消息給所有客戶端
private void userButton1_Click(object sender, EventArgs e)
{
// 服務器發送系統消息到客戶端
if(!string.IsNullOrEmpty(textBox2.Text))
{
// 來自客戶端的消息,就只有這么一種情況
NetMessage msg = new NetMessage()
{
FromName = "系統",
Time = DateTime.Now,
Type = "string",
Content = textBox2.Text,
};
// 群發出去
complexServer.SendAllClients(2, JObject.FromObject(msg).ToString());
}
}
這樣就可以實現消息的發送了。
Client
先填寫核心塊
#region 客戶端網絡塊
private NetComplexClient net_socket_client = new NetComplexClient();
private void Net_Socket_Client_Initialization()
{
try
{
net_socket_client.KeyToken = new Guid("91625bad-d581-44ab-b121-ffff5bcb83fb"); // 設置令牌,必須與連接的服務器令牌一致
net_socket_client.EndPointServer = new System.Net.IPEndPoint(
System.Net.IPAddress.Parse("127.0.0.1"),12345); // 連接的服務器的地址,必須和服務器端的信息對應
net_socket_client.ClientAlias = LoginName; // 傳入賬戶名
net_socket_client.AcceptString += Net_socket_client_AcceptString; // 接收到字符串信息時觸發
net_socket_client.ClientStart();
}
catch (Exception ex)
{
SoftBasic.ShowExceptionMessage(ex);
}
}
/// <summary>
/// 接收到服務器的字節數據的回調方法
/// </summary>
/// <param name="state">網絡連接對象</param>
/// <param name="customer">用戶自定義的指令頭,用來區分數據用途</param>
/// <param name="data">數據</param>
private void Net_socket_client_AcceptString(AsyncStateOne state, NetHandle customer, string data)
{
// 我們規定
// 1 是系統消息,
// 2 是用戶發送的消息
// 3 客戶端在線信息
// 4 退出指令
// 當你的消息頭種類很多以后,可以在一個統一的類中心進行規定
if (customer == 1)
{
ShowSystemMsg(data);
}
else if(customer == 2)
{
ShowMsg(data);
}
else if(customer == 3)
{
ShowOnlineClient(data);
}
else if(customer == 4)
{
// 退出系統
QuitSystem( );
}
}
#endregion
用戶在輸入發送信息的時候,就調用如下的方法:
// 發送消息
private void userButton1_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(textBox3.Text)) return;
net_socket_client.Send(2, textBox3.Text);
textBox3.Clear();
}
具體的代碼邏輯還需要參照github上的源代碼。
