我知道這樣的文章在博客園已經多的你們都不想看了,可是這是個人系列文章開始,請各位大神見諒了。html
多線程,線程執行器,(詳見),socket通訊相關 (詳見)程序員
本人blog相關文章測試代碼,示例,完整版svn地址。(http://code.taobao.org/svn/flynetwork_csharp/trunk/Flynetwork/BlogTest)web
提供所有源碼功能塊。但願各位大神,提供寶貴意見。服務器
莫倩,完成了多線程輔助類庫完整功能(或許後期會有bug須要修復或者優化),socket完成了tcp和http服務監聽功能,udp和websocket還在完善的狀態中。websocket
若是有通訊願意和我一塊兒完善這個輔助類庫,請聯繫我,開通svn受權。多線程
因此源碼免費提供使用,歡迎各位愛好者,加入到項目中,不管是我的,企業,商用,都不限制。惟一要求請保留如下字樣。謝謝合做~!socket
1 /** 2 * 3 * @author 失足程序員 4 * @Blog http://www.cnblogs.com/ty408/ 5 * @mail 492794628@qq.com 6 * @phone 13882122019 7 * 8 */
好了開始咱們的話題tcp
在服務器項目開發中,最總要的也就是登錄問題了或者說叫受權問題。ide
咱們先建立一個console的程序svn
引用個人兩個庫 Sz.Network.SocketPool ,Sz.Network.ThreadPool 分別是socket 幫助庫線程幫助庫。
Sz表示失足的意思。請見諒。偶喜歡上這個代號了「失足程序員」
咱們先建立一個消息處理器 MessagePool
1 public class MessagePool : ISocketPool 2 { 3 public void ActiveSocket(IOSession client) 4 { 5 } 6 7 public void CloseSocket(IOSession client) 8 { 9 10 } 11 12 public void ReadMessage(IOSession client, SocketMessage message) 13 { 14 15 } 16 17 18 public void ActiveHttp(HttpClient client, string bind, Dictionary<string, string> parms) 19 { 20 if (bind.Equals("/test/")) 21 { 22 ThreadManager.GetInstance.AddTask(ServerManager.LoginThreadID, new LoginHttpHandler(client, parms)); 23 } 24 } 25 26 public void IOSessionException(IOSession client, Exception exception) 27 { 28 Logger.Error("內部錯誤", exception); 29 } 30 31 public void HttpException(HttpClient client, Exception exception) 32 { 33 Logger.Error("內部錯誤", exception); 34 } 35 }
而後在mian函數裏面加入
1 Sz.Network.SocketPool.ListenersBox.GetInstance.SetParams(new MessagePool(), typeof(MarshalEndian)); 2 Sz.Network.SocketPool.ListenersBox.GetInstance.Start("tcp:*:9527", "http://*:8001/test/");
這樣咱們就開啓了服務器的監聽,這裏簡單介紹一下爲何我建立了tcp和http的兩個監聽呢?
這是由於經驗和工做關係,由於我是致力於遊戲開發的,服務器端須要建立兩個tcp一般是用於正常通訊的,而http監聽的登錄模塊,或者一些非總要性數據交換以及第三方登錄受權須要開啓的。一樣還由於http是短鏈接,無需保存通訊對象,減小了系統消耗,和tcp數量級消耗。
若是你不理解能夠不加入http的監聽的。直接看tcp的socket。
1 [2015-04-15 18:12:09:899:Info ] Start Listen Tcp Socket -> 0.0.0.0:9527 2 [2015-04-15 18:12:09:906:Info ] Start Listen Http Socket -> 0.0.0.0:8001/test/
運行程序,輸出。
開啓了tcp和http的監聽,http咱們今天暫時忽略其做用吧。
咱們開始準備登錄模塊的開發,同窗都知道登錄首先要面臨的就是多點同時登錄問題。
若是加入 lock 來防止多點同時登錄,那麼勢必照成服務器卡頓。吞吐量不高等因素。那麼咱們考慮把這一塊加入到單獨的線程驚喜處理。也就是幫登錄和登出,放到同一個線程處理。不用加鎖,也能作到防止多點同時登錄。
接下來咱們須要從 Sz.Network.ThreadPool 庫中取出一個線程
public static readonly long LoginThreadID = ThreadManager.GetInstance.GetThreadModel("登錄處理器");
用於控制登錄和登出
建立一個 CloseTcpHandler 處理連接端口的處理程序 須要繼承 Sz.Network.ThreadPool 下面的 TaskBase
1 public class CloseTcpHandler : TaskBase 2 { 3 4 IOSession client; 5 SocketMessage message; 6 7 public CloseTcpHandler(IOSession client) 8 : base("Tcp登錄處理") 9 { 10 this.client = client; 11 } 12 13 14 public override void TaskRun() 15 { 16 17 } 18 }
那麼咱們修改一下 MessagePool 類
public void ActiveSocket(IOSession client) { //client.SendMsg(new SocketMessage(1, System.Text.UTF8Encoding.Default.GetBytes("Holle Server!client"))); } public void CloseSocket(IOSession client) { ThreadManager.GetInstance.AddTask(ServerManager.LoginThreadID, new CloseTcpHandler(client)); }
這樣斷開連接處理就交到了 ServerManager.LoginThreadID 這個線程裏面處理了
咱們再來建立一下 LoginTcpHandler 處理登錄程序 須要繼承 Sz.Network.ThreadPool 下面的 TaskBase
修改 MessagePool 類 處理登錄消息
1 public void ReadMessage(IOSession client, SocketMessage message) 2 { 3 switch (message.MsgID) 4 { 5 case 1://登錄 6 case 2: 7 ThreadManager.GetInstance.AddTask(ServerManager.LoginThreadID, new LoginTcpHandler(client, message)); 8 break; 9 default: 10 Logger.Error("未綁定消息ID " + message.MsgID); 11 break; 12 } 13 }
這裏是我自定義消息ID是1和2的一個是登錄一個是登出。
這樣就把登錄的事件也交給了ServerManager.LoginThreadID 這個線程裏面處理了
LoginTcpHandler taskrun方法
1 public override void TaskRun() 2 { 3 using (MemoryStream msReader = new MemoryStream(message.MsgBuffer)) 4 { 5 using (System.IO.BinaryReader srReader = new BinaryReader(msReader, UTF8Encoding.Default)) 6 { 7 using (MemoryStream msWriter = new MemoryStream()) 8 { 9 using (System.IO.BinaryWriter srWriter = new BinaryWriter(msWriter, UTF8Encoding.Default)) 10 { 11 switch (message.MsgID) 12 { 13 case 1://登錄 14 string username = srReader.ReadString(); 15 if (!LoginManager.GetInstance.LoginNames.Contains(username)) 16 { 17 LoginManager.GetInstance.LoginNames.Add(username); 18 if (!LoginManager.GetInstance.LoginIPs.ContainsKey(client.ID)) 19 { 20 LoginManager.GetInstance.LoginIPs[client.ID] = username; 21 LoginManager.GetInstance.Sessions.Add(client); 22 } 23 srWriter.Write(true); 24 srWriter.Write(username + " 登錄聊天室"); 25 Logger.Info(client.RemoteEndPoint + " " + username + " 登錄成功"); 26 SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer()); 27 ServerManager.GetInstance.Tell_All(sm); 28 } 29 else 30 { 31 srWriter.Write(false); 32 srWriter.Write("登陸名稱重複,請換一個"); 33 Logger.Info(client.RemoteEndPoint + " " + username + " 登陸名稱重複!"); 34 SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer()); 35 client.SendMsg(sm); 36 } 37 break; 38 case 2:// 退出登錄 39 40 break; 41 default: 42 43 break; 44 } 45 } 46 } 47 } 48 } 49 }
CloseTcpHandler taskrun方法
1 public override void TaskRun() 2 { 3 if (LoginManager.GetInstance.LoginIPs.ContainsKey(client.ID)) 4 { 5 string username = LoginManager.GetInstance.LoginIPs[client.ID]; 6 LoginManager.GetInstance.LoginIPs.Remove(client.ID); 7 LoginManager.GetInstance.LoginIPs.Remove(username); 8 LoginManager.GetInstance.Sessions.Remove(client); 9 using (MemoryStream msWriter = new MemoryStream()) 10 { 11 using (System.IO.BinaryWriter srWriter = new BinaryWriter(msWriter, UTF8Encoding.Default)) 12 { 13 srWriter.Write(username + "退出聊天室"); 14 SocketMessage sm = new SocketMessage(3, msWriter.GetBuffer());//3表示發送消息 15 ServerManager.GetInstance.Tell_All(sm); 16 } 17 } 18 } 19 }
這裏因爲代碼沒有貼全,有興趣的能夠下載源碼試試
啓動兩個客戶端後,看到建立了兩個連接,而且登錄到服務器。
因爲聊天和登錄因此不一樣的兩個模塊,爲了提升效率 咱們再次建立一個聊天線程
public static readonly long ChatThreadID = ThreadManager.GetInstance.GetThreadModel("聊天處理器");
接下來咱們在 MessagePool 的 ReadMessage 方法的switch裏面加入
case 3://聊天 ThreadManager.GetInstance.AddTask(ServerManager.ChatThreadID, new Chat.ChatHandler(client, message)); break;
這樣咱們就把全部的聊天消息轉發的ServerManager.ChatThreadID這個線程處理。就是卡頓狀況,也不會影響客戶端聊天發送消息和新客戶端請求登錄。
這裏咱們還能夠考慮分組,聊天分組功能。好比私聊,羣聊等分組線程進行執行。
建立一個 ChatHandler 須要繼承 Sz.Network.ThreadPool 下面的 TaskBase
1 public class ChatHandler : TaskBase 2 { 3 IOSession client; 4 5 SocketMessage message; 6 7 8 public ChatHandler(IOSession client, SocketMessage message) 9 : base("聊天處理任務") 10 { 11 this.client = client; 12 this.message = message; 13 } 14 15 16 public override void TaskRun() 17 { 18 using (MemoryStream msWriter = new MemoryStream()) 19 { 20 using (System.IO.BinaryWriter srWriter = new BinaryWriter(msWriter, UTF8Encoding.Default)) 21 { 22 //構建輸入buffer 23 //驗證登錄狀況 24 if (LoginManager.GetInstance.LoginIPs.ContainsKey(client.ID)) 25 { 26 string username = LoginManager.GetInstance.LoginIPs[client.ID]; 27 using (MemoryStream msReader = new MemoryStream(message.MsgBuffer)) 28 { 29 using (System.IO.BinaryReader srReader = new BinaryReader(msReader, UTF8Encoding.Default)) 30 { 31 string msg = srReader.ReadString(); 32 msg = client.RemoteEndPoint + " " + username + " " + msg; 33 Logger.Info(msg); 34 srWriter.Write(msg); 35 SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer()); 36 ServerManager.GetInstance.Tell_All(sm); 37 } 38 } 39 } 40 else 41 { 42 srWriter.Write("還沒有登錄"); 43 SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer()); 44 client.SendMsg(sm); 45 } 46 } 47 } 48 } 49 }
發個消息試試
這裏一個簡單的聊天服務器,登錄到聊天就算完成了,客戶端是wpf的程序,沒有貼出源碼和過程,有須要或者要研究的親請下載svn源碼,自行查看狀況。