一步一步開發Game服務器(二)完成登陸,聊天,一步一步game
我知道這樣的文章在博客園已經多的大家都不想看了,但是這是我的系列文章開始,請各位大神見諒了。
多線程,線程執行器,(詳見),socket通信相關 (詳見)
本人blog相關文章測試代碼,示例,完整版svn地址。(http://code.taobao.org/svn/flynetwork_csharp/trunk/Flynetwork/BlogTest)
提供全部源碼功能塊。希望各位大神,提供寶貴意見。
莫倩,完成了多線程輔助類庫完整功能(或許後期會有bug需要修復或者優化),socket完成了tcp和http服務監聽功能,udp和websocket還在完善的狀態中。
如果有通信願意和我一起完善這個輔助類庫,請聯系我,開通svn授權。
所以源碼免費提供使用,歡迎各位愛好者,加入到項目中,無論是個人,企業,商用,都不限制。唯一要求請保留以下字樣。謝謝合作~!
1 /**
2 *
3 * @author 失足程序員
4 * @Blog http://www.cnblogs.com/ty408/
5 * @mail [email protected]
6 * @phone 13882122019
7 *
8 */
好了開始我們的話題
在服務器項目開發中,最總要的也就是登陸問題了或者說叫授權問題。
我們先創建一個console的程序
引用我的兩個庫 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源碼,自行查看情況。