本文將使用一個NuGet公開的組件技術來實現一個局域網聊天程序,利用組件提供的高性能異步網絡機制實現,免去了手動編寫底層的困擾,易於二次開發,擴展本身的功能。html
聯繫做者及加羣方式(激活碼在羣裏發放):http://www.hslcommunication.cn/Cooperationgit
在Visual Studio 中的NuGet管理器中能夠下載安裝,也能夠直接在NuGet控制檯輸入下面的指令安裝:github
Install-Package HslCommunication
NuGet安裝教程 http://www.cnblogs.com/dathlin/p/7705014.html安全
以前已經有篇博客說明了同步網絡通訊的開發,同步網絡通訊適用於什麼樣的場景呢,適用於客戶端向服務器請求數據,必須有數據返回的狀況,不管成功仍是失敗。地址:http://www.cnblogs.com/dathlin/p/7697782.html服務器
而異步的網絡通訊適用於什麼狀況呢,適用於服務器進行羣發數據的時候,好比發送消息給全部的在線客戶端,爲了更好的說明異步網絡通訊的實現機制,開發一個多客戶端的局域網聊天程序來演示異步操做。網絡
特性以下:架構
本聊天程序是基於C-S架構設計的,須要建立3個項目,一個服務器項目,用來中轉全部的消息的,一個是客戶端項目,也就是實際的聊天程序,本次項目還顯示全部在線的客戶端信息,ip地址,名字。mvc
至於帳戶,本次不採用任何的用戶名密碼登陸機制,就採用簡易化處理,直接輸入一個名字便可,固然,你也能夠更改爲用戶名密碼登陸的機制,也不是特別困難。框架
簡易的聊天程序不支持圖片,表情包的發送接收,這部分實現起來不是同一個次元的,這部分之後攻克了再開新的博文。asp.net
------------> 小插曲
若是須要更復雜的功能,好比帳戶的登陸,密碼修改,版本控制,帳戶支持頭像等等,一個基於本組件擴展出來的CS架構的基礎模版項目,二次基於此進行方便的二次開發,該項目使用了好幾處的文件管理:
https://github.com/dathlin/ClientServerProject
一個C-S模版,該模版由三部分的程序組成,一個服務端運行的程序,一個客戶端運行的程序,還有一個公共的組件,實現了基礎的帳戶管理功能,版本控制,軟件升級,公告管理,消息羣發,共享文件上傳下載,批量文件傳送功能。具體的操做方法見演示就行。本項目的一個目標是:提供一個基礎的中小型系統的C-S框架,客戶端有四種模式,無縫集成訪問,winform版本,wpf版本,asp.net mvc版本,Android版本。方便企業進行中小型系統的二次開發和我的學習。
日誌組件全部的功能類都在 HslCommunication 和 HslCommunication.Enthernet 命名空間,因此再使用以前先添加:在服務器程序和客戶端程序都要添加
using HslCommunication; using HslCommunication.Enthernet;
首先先建立三個項目,server項目,client項目,common項目,而後使用Nuget將客戶端和服務器兩個項目都安裝組件,而後切換到服務器程序,接下來就是真的建立程序了。
在整個項目中,核心部分就是網絡通訊了,須要實現客戶端向服務器發送消息,這個相對比較好實現,由於服務器的ip地址和端口都是公開的。可是客戶端的ip和端口是未知的,由於咱們要實現任意的電腦都能登陸客戶端。因此咱們須要使用HslCommunication來方便的實現這些操做。
在server端和client端都須要安裝HslCommunication組件。由於咱們要實如今客戶端和服務器端進行通訊,通訊功能衆多,因此須要進行約定,消息的id,咱們最終根據消息的id來區分不一樣的消息。
綜上所述,這個項目已經初步成型,並且經過消息id能夠實現其餘本身功能擴展,能夠實現任何的交互操做。不必定是聊天系統,各類數據同步機制,推送機制,局域網機制的遊戲程序也能夠實現。
本項目的源代碼地址以下:https://github.com/dathlin/NetChatRoom
先填寫核心塊
#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()); } }
這樣就能夠實現消息的發送了。
先填寫核心塊
#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上的源代碼。