一步一步开发Game服务器(二)完成登陆,聊天
我知道这样的文章在博客园已经多的大家都不想看了,但是这是我的系列文章开始,请各位大神见谅了。
多线程,线程执行器,(详见),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服务器(二)完成登陆,聊天的更多相关文章
- 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版
上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新 可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文 ...
- 一步一步开发Game服务器(二)登陆2
上一篇文章,讲解了简单的登陆情况.接下来我们继续讲解登陆模块. 在正常的游戏服务器情况下.在尚未登录前可以查看服务器大区情况,登陆后也可以查看服务器大区情况,然后选择大区服务器.进行登录操作. 这样的 ...
- 一步一步开发Game服务器(四)地图线程
时隔这么久 才再一次的回归正题继续讲解游戏服务器开发. 开始讲解前有一个问题需要修正.之前讲的线程和定时器线程的时候是分开的. 但是真正地图线程与之前的线程模型是有区别的. 为什么会有区别呢?一个地图 ...
- 一步一步跟我学DeviceOne开发 - 仿微信应用(一,二,三)
这是一个系列的文档,长期目标是利用DeviceOne开发一些目前使用广泛的优质手机应用,我们会最大化的实现这些应用的每一个功能和细节,不只停留在简单的UI模仿和Demo阶段,而是一个基本可以使用的实际 ...
- 一步一步开发Game服务器(一)
什么是服务器?对于很多人来说也许只是简单成为在服务器端运行的程序的确如此,服务器通常意义就是说在服务器端运行的程序而已.那么我们怎么理解和分析游戏服务器哪? 传统意义上来说,程序运行后,正常流程, 启 ...
- JAVA+PHP+阿里云组件纯手工实现POP、SMTP、IMAP开发邮件服务器(二)
java开发邮件服务器的接收模块 用java建立socket服务端,监听端口25,实现SMTP协议.即可完成邮件服务器的接收模块. 这里要注意的是,SMTP协议其实可以分为两种.一种是你用手机.PC等 ...
- 跟我一步一步开发自己的Openfire插件
http://www.blogjava.net/hoojo/archive/2013/03/07/396146.html 跟我一步一步开发自己的Openfire插件 这篇是简单插件开发,下篇聊天记录插 ...
- xmppmini 项目详解:一步一步从原理跟我学实用 xmpp 技术开发 2.登录的实现
第二章登录的实现 金庸<倚天屠龙记> 张三丰缓缓摇头,说道:“少林派累积千年,方得达成这等绝技,决非一蹴而至,就算是绝顶聪明之人,也无法自创.”他顿了一顿,又道:“我当年在少林寺中住过,只 ...
- 一步一步实现HTTP服务器-开篇
缘起 翻开清单,一条条计划一直列在那里,一天又一天,不知道什么时候写下了它,也知不道什么时候完成它,它一直在那静静的等待着. 静下心来,反思自己,才发现自己是多么的无知,多么的没有毅力.设定了无数目标 ...
随机推荐
- PYTHON之全局变量
应该尽量避免使用全局变量.不同的模块都可以自由的访问全局变量,可能会导致全局变量的不可预知性.对全局变量,如果程序员甲修改了_a的值,程序员乙同时也要使用_a,这时可能导致程序中的错误.这种错误是很难 ...
- vs2013 无法打开 源 文件 "SDKDDKVer.h"
使用vs2013开发 win32相关的程序的时候,回报很多属性未定义或者文件找不到的错误 例如构建 控制台程序就会报: vs2013 无法打开 源 文件 "SDKDDKVer.h" ...
- ajax知识整理
HTTP服务 1.服务器 服务器类型 服务类型:文件服务器.数据库服务器.邮件服务器.Web服务器等: 操作系统:Linux服务器.Windows服务器等: 应用软件:Apache服务器.Nginx ...
- 基于Mono跨平台移动应用开发框架发布Xamarin 3.0
跨平台移动应用开发框架Xamarin可以让你完全用C#编写你的应用程序,在iOS.Android.Windows Phone 8.Windows8和mac平台上共享相同的代码.你可以重用你最喜欢的.N ...
- Hadoop 和 HDInsight:Windows Azure 中的大数据
世界的大数据包含一个庞大而充满活力的生态系统,但一个开放源码项目上面有这一切,那就是 Hadoop 的王朝. Hadoop 是事实上的标准的分布式的数据运算.Hadoop 提供了一个 MapReduc ...
- 简单明了区分escape、encodeURI和encodeURIComponent
一.前言 讲这3个方法区别的文章太多了,但是大部分写的都很绕.本文试图从实践角度去讲这3个方法. 二.escape和它们不是同一类 简单来说,escape是对字符串(string)进行编码(而另外两种 ...
- C++的性能C#的产能?! - .Net Native 系列向导
之前一文<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥> 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方 ...
- MySQL 启动服务报错解决方案
标签:ERROR! The server quit without updating PID file (/var/lib/mysql/localhost.localdomain.pid) 概述 文章 ...
- 开始研究web,mark一下
之前想要搞引擎,经过思考之后,定位为webgl方面的引擎,这个决定早就做了,只是没有写下来 做了一些调研之后,确定使用babylon.js 和typescript 和c# 来开发 Babylo ...
- 《精通正则表达式》笔记 --- “验证”Email格式
写一个正则表达式的三个步骤: 理解需求并找出你需要验证的数据的特征: 写一个还可以用的正则表达式: 看看能不能达到你的目的,同时想想会不会匹配到一些不想要的数据: [可选]性能优化 我觉得写一个正则表 ...