当前位置: 代码迷 >> C# >> 一步一步开发Game服务器(2)完成登陆,聊天
  详细解决方案

一步一步开发Game服务器(2)完成登陆,聊天

热度:36   发布时间:2016-05-05 04:10:09.0
一步一步开发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 138821220197  * 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:95272 [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                                     else30                                     {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                     //构建输入buffer23                     //验证登陆情况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                     else41                     {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源码,自行查看情况。

 

4楼黄焕杰
嘻嘻,先沙发~~
3楼networkcomms通信框架
非常支持
2楼wdwwtzy
看起来不错的系列,但是最好高级一点,不要只是socket连接接受这种DEMO,例如加上粘包、多包和少包, 断包的处理方法
Re: 失足程序员
@wdwwtzy,引用看起来不错的系列,但是最好高级一点,不要只是socket连接接受这种DEMO,例如加上粘包、多包和少包, 断包的处理方法,这个我在之前的文章里面已经发过了 http://www.cnblogs.com/ty408/p/4344868.html
1楼喵呵呵
受用很大
  相关解决方案