一步一步開發Game服務器(二)完成登錄,聊天

我知道這樣的文章在博客園已經多的你們都不想看了,可是這是個人系列文章開始,請各位大神見諒了。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     }
View Code

 

 

而後在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     }
View Code

 

那麼咱們修改一下 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

 

View Code

 

修改  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         }
View Code

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         }
View Code

 

這裏因爲代碼沒有貼全,有興趣的能夠下載源碼試試

 

啓動兩個客戶端後,看到建立了兩個連接,而且登錄到服務器。

因爲聊天和登錄因此不一樣的兩個模塊,爲了提升效率 咱們再次建立一個聊天線程

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     }
View Code

 

發個消息試試

這裏一個簡單的聊天服務器,登錄到聊天就算完成了,客戶端是wpf的程序,沒有貼出源碼和過程,有須要或者要研究的親請下載svn源碼,自行查看狀況。

相關文章
相關標籤/搜索