我知道这样的文章在博客园已经多的大家都不想看了,但是这是我的系列文章开始,请各位大神见谅了。
多线程,线程执行器,(详见),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
class="code_img_closed" src="/Upload/Images/2015041523/0015B68B3C38AA5B.gif" alt="" />
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源码,自行查看情况。