zoukankan      html  css  js  c++  java
  • 一步一步开发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 492794628@qq.com
    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源码,自行查看情况。

    跪求保留标示符
    /**
     * @author: Troy.Chen(失足程序员, 15388152619)
     * @version: 2021-07-20 10:55
     **/
    
    C#版本代码 vs2010及以上工具可以
    
    java 开发工具是netbeans 和 idea 版本,只有项目导入如果出现异常,请根据自己的工具调整
    
    
    提供免费仓储。
    最新的代码地址:↓↓↓
    https://gitee.com/wuxindao
    
    觉得我还可以,打赏一下吧,你的肯定是我努力的最大动力
        
    
  • 相关阅读:
    ubuntu一般软件安装在什么目录
    Swing是一把刀
    eclipse 3.6的VE配置 Visual Editor for eclipse3.6
    有关import sun.audio.AudioPlayer(或者其它文件)的问题
    ubuntu 下安装jdk
    linux查看java jdk安装路径
    关于绿色
    Swing如何正确的处理界面中的线程(EDT)
    在Eclipse4.2x中安装最新版插件WindowBuilder
    Eclipse java swing开发环境
  • 原文地址:https://www.cnblogs.com/shizuchengxuyuan/p/4429663.html
Copyright © 2011-2022 走看看