我知道这样的文章在博客园已经多的大家都不想看了,但是这是我的系列文章开始,请各位大神见谅了。

多线程,线程执行器,(详见),socket通信相关 (详见

本人blog相关文章测试代码,示例,完整版svn地址。(http://code.taobao.org/svn/flynetwork_csharp/trunk/Flynetwork/BlogTest)

提供全部源码功能块。希望各位大神,提供宝贵意见。

莫倩,完成了多线程辅助类库完整功能(或许后期会有bug需要修复或者优化),socket完成了tcp和http服务监听功能,udp和websocket还在完善的状态中。

如果有通信愿意和我一起完善这个辅助类库,请联系我,开通svn授权。

所以源码免费提供使用,欢迎各位爱好者,加入到项目中,无论是个人,企业,商用,都不限制。唯一要求请保留以下字样。谢谢合作~!

 /**
  *
  * @author 失足程序员
  * @Blog http://www.cnblogs.com/ty408/
  * @mail 492794628@qq.com
  * @phone 13882122019
  *
  */

好了开始我们的话题

在服务器项目开发中,最总要的也就是登陆问题了或者说叫授权问题。

我们先创建一个console的程序

引用我的两个库 Sz.Network.SocketPool ,Sz.Network.ThreadPool 分别是socket 帮助库线程帮助库。

Sz表示失足的意思。请见谅。偶喜欢上这个代号了“失足程序员”

我们先创建一个消息处理器 MessagePool

  public class MessagePool : ISocketPool
     {
         public void ActiveSocket(IOSession client)
         {
         }

         public void CloseSocket(IOSession client)
         {

         }

         public void ReadMessage(IOSession client, SocketMessage message)
         {

         }

         public void ActiveHttp(HttpClient client, string bind, Dictionary<string, string> parms)
         {
             if (bind.Equals("/test/"))
             {
                 ThreadManager.GetInstance.AddTask(ServerManager.LoginThreadID, new LoginHttpHandler(client, parms));
             }
         }

         public void IOSessionException(IOSession client, Exception exception)
         {
             Logger.Error("内部错误", exception);
         }

         public void HttpException(HttpClient client, Exception exception)
         {
             Logger.Error("内部错误", exception);
         }
     }

然后在mian函数里面加入

  Sz.Network.SocketPool.ListenersBox.GetInstance.SetParams(new MessagePool(), typeof(MarshalEndian));
             Sz.Network.SocketPool.ListenersBox.GetInstance.Start("tcp:*:9527", "http://*:8001/test/");

这样我们就开启了服务器的监听,这里简单介绍一下为什么我创建了tcp和http的两个监听呢?

这是因为经验和工作关系,因为我是致力于游戏开发的,服务器端需要创建两个tcp通常是用于正常通信的,而http监听的登陆模块,或者一些非总要性数据交换以及第三方登陆授权需要开启的。同样还因为http是短连接,无需保存通信对象,减少了系统消耗,和tcp数量级消耗。

如果你不理解可以不加入http的监听的。直接看tcp的socket。

 [-- ::::Info ] Start Listen Tcp Socket ->
 [-- ::::Info ] Start Listen Http Socket -> /test/

运行程序,输出。

开启了tcp和http的监听,http我们今天暂时忽略其作用吧。

我们开始准备登陆模块的开发,同学都知道登陆首先要面临的就是多点同时登陆问题。

如果加入 lock 来防止多点同时登陆,那么势必照成服务器卡顿。吞吐量不高等因素。那么我们考虑把这一块加入到单独的线程惊喜处理。也就是帮登陆和登出,放到同一个线程处理。不用加锁,也能做到防止多点同时登陆。

接下来我们需要从 Sz.Network.ThreadPool 库中取出一个线程

public static readonly long LoginThreadID = ThreadManager.GetInstance.GetThreadModel("登陆处理器");

用于控制登陆和登出

创建一个 CloseTcpHandler 处理链接端口的处理程序 需要继承 Sz.Network.ThreadPool 下面的 TaskBase

  public class CloseTcpHandler : TaskBase
     {

         IOSession client;
         SocketMessage message;

         public CloseTcpHandler(IOSession client)
             : base("Tcp登陆处理")
         {
             this.client = client;
         }

         public override void TaskRun()
         {

         }
     }

那么我们修改一下 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 类 处理登陆消息

 public void ReadMessage(IOSession client, SocketMessage message)
         {
             switch (message.MsgID)
             {
                 ://登陆
                 :
                     ThreadManager.GetInstance.AddTask(ServerManager.LoginThreadID, new LoginTcpHandler(client, message));
                     break;
                 default:
                     Logger.Error("未绑定消息ID " + message.MsgID);
                     break;
             }
         }

这里是我自定义消息ID是1和2的一个是登陆一个是登出。

这样就把登陆的事件也交给了ServerManager.LoginThreadID 这个线程里面处理了

LoginTcpHandler taskrun方法

  public override void TaskRun()
         {
             using (MemoryStream msReader = new MemoryStream(message.MsgBuffer))
             {
                 using (System.IO.BinaryReader srReader = new BinaryReader(msReader, UTF8Encoding.Default))
                 {
                     using (MemoryStream msWriter = new MemoryStream())
                     {
                         using (System.IO.BinaryWriter srWriter = new BinaryWriter(msWriter, UTF8Encoding.Default))
                         {
                             switch (message.MsgID)
                             {
                                 ://登陆
                                     string username = srReader.ReadString();
                                     if (!LoginManager.GetInstance.LoginNames.Contains(username))
                                     {
                                         LoginManager.GetInstance.LoginNames.Add(username);
                                         if (!LoginManager.GetInstance.LoginIPs.ContainsKey(client.ID))
                                         {
                                             LoginManager.GetInstance.LoginIPs[client.ID] = username;
                                             LoginManager.GetInstance.Sessions.Add(client);
                                         }
                                         srWriter.Write(true);
                                         srWriter.Write(username + " 登陆聊天室");
                                         Logger.Info(client.RemoteEndPoint + " " + username + " 登陆成功");
                                         SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer());
                                         ServerManager.GetInstance.Tell_All(sm);
                                     }
                                     else
                                     {
                                         srWriter.Write(false);
                                         srWriter.Write("登录名称重复,请换一个");
                                         Logger.Info(client.RemoteEndPoint + " " + username + " 登录名称重复!");
                                         SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer());
                                         client.SendMsg(sm);
                                     }
                                     break;
                                 :// 退出登陆

                                     break;
                                 default:

                                     break;
                             }
                         }
                     }
                 }
             }
         }
CloseTcpHandler taskrun方法

 public override void TaskRun()
         {
             if (LoginManager.GetInstance.LoginIPs.ContainsKey(client.ID))
             {
                 string username = LoginManager.GetInstance.LoginIPs[client.ID];
                 LoginManager.GetInstance.LoginIPs.Remove(client.ID);
                 LoginManager.GetInstance.LoginIPs.Remove(username);
                 LoginManager.GetInstance.Sessions.Remove(client);
                 using (MemoryStream msWriter = new MemoryStream())
                 {
                     using (System.IO.BinaryWriter srWriter = new BinaryWriter(msWriter, UTF8Encoding.Default))
                     {
                         srWriter.Write(username + "退出聊天室");
                         SocketMessage sm = , msWriter.GetBuffer());//3表示发送消息
                         ServerManager.GetInstance.Tell_All(sm);
                     }
                 }
             }
         }

这里由于代码没有贴全,有兴趣的可以下载源码试试

启动两个客户端后,看到创建了两个链接,并且登陆到服务器。

由于聊天和登陆所以不同的两个模块,为了提高效率 我们再次创建一个聊天线程

public static readonly long ChatThreadID = ThreadManager.GetInstance.GetThreadModel("聊天处理器");

接下来我们在 MessagePool 的 ReadMessage 方法的switch里面加入

://聊天
                    ThreadManager.GetInstance.AddTask(ServerManager.ChatThreadID, new Chat.ChatHandler(client, message));
                    break;

这样我们就把所有的聊天消息转发的ServerManager.ChatThreadID这个线程处理。就是卡顿情况,也不会影响客户端聊天发送消息和新客户端请求登陆。

这里我们还可以考虑分组,聊天分组功能。比如私聊,群聊等分组线程进行执行。

创建一个 ChatHandler  需要继承 Sz.Network.ThreadPool 下面的 TaskBase

 public class ChatHandler : TaskBase
     {
         IOSession client;

         SocketMessage message;

         public ChatHandler(IOSession client, SocketMessage message)
             : base("聊天处理任务")
         {
             this.client = client;
             this.message = message;
         }

         public override void TaskRun()
         {
             using (MemoryStream msWriter = new MemoryStream())
             {
                 using (System.IO.BinaryWriter srWriter = new BinaryWriter(msWriter, UTF8Encoding.Default))
                 {
                     //构建输入buffer
                     //验证登陆情况
                     if (LoginManager.GetInstance.LoginIPs.ContainsKey(client.ID))
                     {
                         string username = LoginManager.GetInstance.LoginIPs[client.ID];
                         using (MemoryStream msReader = new MemoryStream(message.MsgBuffer))
                         {
                             using (System.IO.BinaryReader srReader = new BinaryReader(msReader, UTF8Encoding.Default))
                             {
                                 string msg = srReader.ReadString();
                                 msg = client.RemoteEndPoint + " " + username + " " + msg;
                                 Logger.Info(msg);
                                 srWriter.Write(msg);
                                 SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer());
                                 ServerManager.GetInstance.Tell_All(sm);
                             }
                         }
                     }
                     else
                     {
                         srWriter.Write("尚未登陆");
                         SocketMessage sm = new SocketMessage(message.MsgID, msWriter.GetBuffer());
                         client.SendMsg(sm);
                     }
                 }
             }
         }
     }

发个消息试试

这里一个简单的聊天服务器,登陆到聊天就算完成了,客户端是wpf的程序,没有贴出源码和过程,有需要或者要研究的亲请下载svn源码,自行查看情况。

一步一步开发Game服务器(二)完成登陆,聊天的更多相关文章

  1. 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版

    上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新 可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文 ...

  2. 一步一步开发Game服务器(二)登陆2

    上一篇文章,讲解了简单的登陆情况.接下来我们继续讲解登陆模块. 在正常的游戏服务器情况下.在尚未登录前可以查看服务器大区情况,登陆后也可以查看服务器大区情况,然后选择大区服务器.进行登录操作. 这样的 ...

  3. 一步一步开发Game服务器(四)地图线程

    时隔这么久 才再一次的回归正题继续讲解游戏服务器开发. 开始讲解前有一个问题需要修正.之前讲的线程和定时器线程的时候是分开的. 但是真正地图线程与之前的线程模型是有区别的. 为什么会有区别呢?一个地图 ...

  4. 一步一步跟我学DeviceOne开发 - 仿微信应用(一,二,三)

    这是一个系列的文档,长期目标是利用DeviceOne开发一些目前使用广泛的优质手机应用,我们会最大化的实现这些应用的每一个功能和细节,不只停留在简单的UI模仿和Demo阶段,而是一个基本可以使用的实际 ...

  5. 一步一步开发Game服务器(一)

    什么是服务器?对于很多人来说也许只是简单成为在服务器端运行的程序的确如此,服务器通常意义就是说在服务器端运行的程序而已.那么我们怎么理解和分析游戏服务器哪? 传统意义上来说,程序运行后,正常流程, 启 ...

  6. JAVA+PHP+阿里云组件纯手工实现POP、SMTP、IMAP开发邮件服务器(二)

    java开发邮件服务器的接收模块 用java建立socket服务端,监听端口25,实现SMTP协议.即可完成邮件服务器的接收模块. 这里要注意的是,SMTP协议其实可以分为两种.一种是你用手机.PC等 ...

  7. 跟我一步一步开发自己的Openfire插件

    http://www.blogjava.net/hoojo/archive/2013/03/07/396146.html 跟我一步一步开发自己的Openfire插件 这篇是简单插件开发,下篇聊天记录插 ...

  8. xmppmini 项目详解:一步一步从原理跟我学实用 xmpp 技术开发 2.登录的实现

    第二章登录的实现 金庸<倚天屠龙记> 张三丰缓缓摇头,说道:“少林派累积千年,方得达成这等绝技,决非一蹴而至,就算是绝顶聪明之人,也无法自创.”他顿了一顿,又道:“我当年在少林寺中住过,只 ...

  9. 一步一步实现HTTP服务器-开篇

    缘起 翻开清单,一条条计划一直列在那里,一天又一天,不知道什么时候写下了它,也知不道什么时候完成它,它一直在那静静的等待着. 静下心来,反思自己,才发现自己是多么的无知,多么的没有毅力.设定了无数目标 ...

随机推荐

  1. Fedora17安装MySQL及配置

    一.安装 以下操作均是在root用户下进行操作.首先,如果以前有安装过,这次卸载后安装的话,最好是先将/var/lib/mysql/里面的内容全部删掉(如果不重要的话),我就遇到过没有删除这些东西,结 ...

  2. linux内核分析作业5:分析system_call中断处理过程

    1.增加 Menu 内核命令行 调试系统调用. 步骤:删除menu git clone        (tab) make rootfs 这就是我们将 fork 函数写入 Menu 系统内核后的效果, ...

  3. APP漏洞扫描器之本地拒绝服务检测详解

    APP漏洞扫描器之本地拒绝服务检测详解 阿里聚安全的Android应用漏洞扫描器有一个检测项是本地拒绝服务漏洞的检测,采用的是静态分析加动态模糊测试的方法来检测,检测结果准确全面.本文将讲一下应用漏洞 ...

  4. android-plugmgr源代码分析

    android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...

  5. WPF+通过配置文件生成菜单(Menu)+源码

    这个月做项目,遇到过一个通过配置文件来生成菜单的解决方案,感觉挺优雅的,特地放到博客园来,以飨读者. 说来惭愧,以前做的项目都没有这样用过,都是固定死了.如果后续有需要加入菜单,还得在重新修改UI,然 ...

  6. .Net组件程序设计之序列化

     .Net组件程序设计之序列化 自动序列化 本篇给大家讲解一下在.NET中的序列化,它的用处非常之多,特别是对于某种环境下保存某种状态是很好的方法,接下来就来看一下吧. Serializable属性 ...

  7. static与并发

    在java中static用来修饰Class类中属性和方法. 被static修饰的成员属性和成员方法独立于该类的任何对象,它们在内存空间上会被放在描述Class的位置中,也就是说它们为此类(Class) ...

  8. RequireJS与Backbone简单整合

    前言 昨天我们一起学习了Backbone,最后做了一个备忘录的例子,说是做了不如说是看了下官方提供的例子,所以最终我感觉我们还是没能掌握Backbone,今天还得做个其它例子先. 然后前面也只是草草学 ...

  9. ionic中关于ionicView 的生命周期

    当我们来回切换页面时候,视图被缓存下来,不用每次再去new一个新的视图,可以大大地提高性能.当跳出一个视图后,视图的元素被保存在DOM中,它的作用域也就不在$watch的作用域内,当我们访问一个已经被 ...

  10. java IO流 之 字节流

    一.file类的常用操作 File file=new File("E:\\test\\javaIo"); System.out.println(file.isDirectory() ...