用SocketAsyncEventArgs+池+线程构建服务器“推”
2009-07-23 15:46 by Creason New, 3573 visits, 收藏, 编辑下篇文章:【用SocketAsyncEventArgs+池+线程构建服务器“推”】的源代码分析1
说到服务器推技术,可谓是让人们又爱又恨,爱的是它能实现特殊的功能效果,恨的是它性能太低实现起来太复杂。大家知道Framework里有Socket、异步Socket,但实现起来只能是权且观赏,要真实际应用却问题重重,于是我拜读了园子里的很多大侠们的文章,从中找到了一些线索,于是参考兼独创弄出来一套方案。好,进入正题。
先来个引子。要做服务器推,当然要保证几点:连接数、性能、稳定性、灵活性,不能一台服务器只能连个百八十个用户,也不能一推就弄个CPU占用率100%甚至直接down掉,稳定嘛,自然就要保证服务器不能因为用户的变化而断开服务,而且还要有判断用户的能力,不能那边用户断开半个小时了,你服务器还以为人家还在线呢。因此,考虑到这些因素,我采用了SocketAsyncEventArgs对象(实际上就是BeginXX,EndXX一个变种)作为异步传输,构建连接池和缓冲区来提高性能,以及多线程来管理接收与发送同时进行的情况。
再介绍一下原理吧。首先为了不在Accept一个用户之后new一个对象,聪明一点的办法就是在服务一启动就把所有的对象创建好了,然后Accept一个用户之后从“池子”里拿出一个对象给这个用户,当这个用户断开了再把这个对象放回“池子”里,这也是线程池的原理。而且在创建连接池的时候我们把接收的缓冲区也给创建好了,也就是说缓冲区也不用new了,直接拿来用(嘿嘿,性能应该不会太差吧)。当然连接池里对象的个数根据你的服务器允许的并发的连接数在创建连接池的时候指定。再就是接收和发送同时进行的问题了,我把接收和发送放到两个线程里,这样就可以一边发送一边接收了,摆脱了对讲机式的通话,而且性能上多线程要好些。
贴个类图:
各个类说明:
MySocketAsyncEventArgs:为了给SocketAsyncEventArgs对象一个用户和收发的属性,我让MySocketAsyncEventArgs继承自SocketAsyncEventArgs,并添加了UID和Property属性,UID保持用户标识,Property值为“Send”或“Receive”,就是标记“我”是管发送信息还是接收信息的。
SocketAsyncEventArgsWithId:连接池里的一个连接单元,它有一个用户标识UID(跟MySocketAsyncEventArgs对象的UID是相同的值)和两个分别用于接收和发送的MySocketAsyncEventArgs对象。
RequestHandler:所传输数据的协议。
BufferManager:给SocketAsyncEventArgsWithId对象分配和管理缓冲区的类。
SocketListener和SocketClient就不用解释了,是地球人都能看懂。
服务器端代码
1 class Program
3 static SocketListener server;
4 static string command;
5 static bool exit = true;
6 static void Main(string[] args)
7 {
8 Console.WriteLine("Server is Starting"+Environment.NewLine);
9 try
10 {
11 SocketListener.GetIDByIPFun getid = new SocketListener.GetIDByIPFun(GetId);//创建一个类库可以调用的根据IP返回用户标识的方法的委托,比如bill gate的ip是192.168.1.1,那么类库不知道bill gate,只知道ip,所以在用的时候要让类库知道这么一个对应关系
12 server = new SocketListener(20000, 32768, getid);//参数为:并行连接的数目,每个连接的缓冲区大小,ip/id对应的函数委托
13 server.StartListenThread += new SocketListener.StartListenHandler(server_listen);//可以开始监听用户发送的信息时触发的事件
14 server.OnMsgReceived += new SocketListener.ReceiveMsgHandler(server_recInfo);//服务器收到来着客户端的信息时触发的事件
15 server.OnSended += new SocketListener.SendCompletedHandler(server_sendcompleted);//服务器发送完信息时触发的事件
16 server.Init();
17 server.Start(2008);
18 Console.WriteLine("Server is Running" + Environment.NewLine);
19 Console.WriteLine("1-send 2-stop" + Environment.NewLine);
20
21 while (exit)
22 {
23 Console.WriteLine("command:");
24 command = Console.ReadLine();
25 if (command == "1")
26 {
27 Console.WriteLine("input your msg:");
28 string msg = Console.ReadLine();
29 Console.WriteLine("who will you send:");
30 string uid = Console.ReadLine();
31 server.Send(uid, msg);
32 continue;
33 }
34 else if (command == "2")
35 {
36 server.Stop();
37 continue;
38 }
39 else
40 {
41 Console.WriteLine("command not found");
42 continue;
43 }
44 }
45 }
46 catch (Exception e)
47 {
48 Console.WriteLine(e.Message + Environment.NewLine);
49 Console.ReadLine();
50 }
51 }
52 static void server_listen()
53 {
54 Thread listenthread = new Thread(new ThreadStart(server.Listen));
55 listenthread.Start();//开启新的线程用于监听发来的数据
56 }
57 static void server_recInfo(string uid, string info)
58 {
59 Console.WriteLine("msg:" + info + "\n from:" + uid + Environment.NewLine);
60 }
61 static void server_sendcompleted(bool successorfalse)
62 {
63 Console.WriteLine("your msg send success" + successorfalse + Environment.NewLine);
64 }
65 static string GetId(string IP)
66 {
67 return "110";
68 }
69 }
客户器端代码
2 class Program
3 {
4 static SocketClient client;
5 static string command;
6 static bool exit = true;
7 static void Main(string[] args)
8 {
9 Console.WriteLine("Client is Starting" + Environment.NewLine);
10 try
11 {
12 client = new SocketClient("192.168.170.1", 2008);
13 client.StartListenThread += new SocketClient.StartListenHandler(client_listen);
14 client.OnMsgReceived += new SocketClient.ReceiveMsgHandler(client_recInfo);
15 client.OnSended += new SocketClient.SendCompleted(client_sendcompleted);
16 Console.WriteLine("Client is Running" + Environment.NewLine);
17 Console.WriteLine("1-connect 2-disconnect 3-send" + Environment.NewLine);
18
19 while (exit)
20 {
21 Console.WriteLine("command:");
22 command = Console.ReadLine();
23 if (command == "1")
24 {
25 client.Connect();
26 continue;
27 }
28 else if (command == "2")
29 {
30 client.Disconnect();
31 continue;
32 }
33 else if (command == "3")
34 {
35 Console.WriteLine("input your msg:");
36 string msg = Console.ReadLine();
37 client.Send(msg);
38 }
39 else
40 {
41 Console.WriteLine("command not found");
42 continue;
43 }
44 }
45 }
46 catch (Exception e)
47 {
48 Console.WriteLine(e.Message + Environment.NewLine);
49 Console.ReadLine();
50 }
51 }
52
53 static void client_listen()
54 {
55 Thread listenthread = new Thread(new ThreadStart(client.Listen));
56 listenthread.Start();
57 }
58
59 static void client_recInfo(string info)
60 {
61 Console.WriteLine(info);
62 }
63
64 static void client_sendcompleted(bool successorfalse)
65 {
66 Console.WriteLine("msg send success:" + successorfalse + Environment.NewLine);
67 }
68 }



参考文章:http://www.cnblogs.com/jeriffe/articles/1407603.html
http://www.cnblogs.com/chuncn/archive/2009/06/22/1508018.html
http://www.cnblogs.com/dabing/archive/2009/07/10/1520586.html
http://www.cnblogs.com/JimmyZhang/archive/2008/09/16/1291854.html
附上源代码:http://files.cnblogs.com/niuchenglei/SocketLib.zip
#1楼 povy 2009-07-23 16:25
看看~~#2楼 Jeffrey Zhao 2009-07-23 17:08
没有仔细看实现,你这个25M是什么内存?看看虚拟内存?// 异步Socket应该用了IOCP,性能的确会很好。
#3楼 大 兵 2009-07-23 17:45
写的不错。1:其实客户端的链接,发送和接收以及服务端的检测端口,接收以及发送我们都是用了异步。我们并没有等一个操作完成了才进行下个操作。所以还是可以保证同一时刻同一端口接收和发送在同时进行的。
2:服务器推我猜想你是在解决客户端关闭而服务端不知道这个问题的吧。修正接收过程【e.BytesTransferred > 0 && e.SocketError == SocketError.Success)】否则就关闭客户端连接。
图画的不错。
#4楼 小黑三 2009-07-23 18:09
不错,能否把代码上传啊?#5楼 非空 2009-07-23 22:15
那个并发连接数的问题解决了,能否透露下你怎么解决通讯中出现的各种异常啊,比如积极拒绝啊 那边突然掉了 拔网线了等等#6楼 Galactica 2009-07-24 08:36
图很漂亮,用啥画的?#7楼[楼主] 小老牛 2009-07-24 09:03
@Jeffrey Zhao25M内存就是任务管理器里面的内存啊,虚拟内存从哪里看啊,你看看我贴的那个任务管理器的截图的第三个和第四个进程。
#8楼[楼主] 小老牛 2009-07-24 09:05
@大 兵其实在我的类库里面已经封装了检测机制了,就是用e.BytesTransferred > 0 && e.SocketError == SocketError.Success来检测连接状态的。把源代码贴上,里面的注释很详细,你看看吧。谢谢回贴
#9楼[楼主] 小老牛 2009-07-24 09:06
@小黑三贴上代码地址了,看看吧,谢谢
#10楼[楼主] 小老牛 2009-07-24 09:08
@非空我用了e.BytesTransferred > 0 和 e.SocketError == SocketError.Success来确定连接的状态,我想这足以解决各种连接中断的问题了吧,你有什么高招吗?请指教。
#11楼[楼主] 小老牛 2009-07-24 09:08
@Galactica那Enterprise Architect做的
#12楼 Jeffrey Zhao 2009-07-24 09:17
任务管理器里select column,内存有许多种。
#13楼 Autumn770[未注册用户]2009-07-29 12:05
老兄,我看了你的服务器后,我试了一下,在客户端断开后,再连接以后发消息服务器就收不到了。#14楼 songcan 2009-10-16 10:31
代码无法下载#15楼 heihoo 2010-11-26 20:47
急需这个代码,谢谢。heihoo@163.com#16楼 qiuqingpo 2010-12-27 11:57
70 /// <summary>171 /// 开始监听线程的入口函数
172 /// </summary>
173 public void Listen()
174 {
175 while (true)
176 {
177 string[] keys = readWritePool.OnlineUID;
178 foreach (string uid in keys)
179 {
180 if (uid != null && readWritePool.busypool[uid].ReceiveSAEA.LastOperation != SocketAsyncOperation.Receive)
181 {
182 Boolean willRaiseEvent = (readWritePool.busypool[uid].ReceiveSAEA.UserToken as Socket).ReceiveAsync(readWritePool.busypool[uid].ReceiveSAEA);
183 if (!willRaiseEvent)
184 ProcessReceive(readWritePool.busypool[uid].ReceiveSAEA);
185 }
186 }
187 }
188 }
大哥.你这样不让我CPU 100% 才怪呢.真不知道你是怎么测试的?
#17楼 l_rain 2011-10-21 18:03
写的很详细,最新的代码可以给我发一份吗? email : l_rain@foxmail.楼上说的没错,CPU 40% 以上,内存25M,这个效率太低了,不实用。内存不是那样算的