打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
高性能Socket设计实现 - 志良的技术博客 - 博客园

因为学习的需要,要求一个高性能的Socket服务器来提供多而繁杂的客户端连接请求,参考了许多资料和各位的思想,自己琢磨出了一套方案,觉的可行,于是拿出来晒晒,希望大家一起学习改进。(这个方案的1.0版本已经贴出来了,但是由于本人觉的1.0不太完美,做了下改进,本篇讲的主要是2.0)

1.0的文章参考:http://www.cnblogs.com/niuchenglei/archive/2009/07/23/1529462.html
1.0和2.0性能上基本没有变化,只是针对某些地方做了改进性的修改,本篇主要介绍原理,并贴出部分代码,上一篇是一个Overview。

设计原则:使用.net的SocketAsyncEventArgs(原因是这个比较简单,而且性能也很好,当然要是c++的话就用IOCP了)。考虑到能快速的反应用户的连接请求我采用了连接池的技术,类似于sqlserver的连接池,当然我的“池”还不够好,为了能快速的处理接受的数据我又加入了一个缓冲区池,说白了就是给每一个连接对象事先开辟好了空间。在传输方面,为了保证数据的有效性我们采用客户端和服务器端的验证(当然也不是太复杂)。

具体分析:分析的顺序是自底向上的

1.MySocketAsyncEventArgs类:这个类是一个继承自System.Net.Socket.SocketAsyncEventArgs类,是由于特定情况需要而添加了一些外加属性的类。

1 internal sealed class MySocketAsyncEventArgs : SocketAsyncEventArgs
2 {
3 internal string UID;
4 private string Property;
5 internal MySocketAsyncEventArgs(string property){
6 this.Property = property;
7 }
8 }

UID:用户标识符,用来标识这个连接是那个用户的。
Property:标识该连接是用来发送信息还是监听接收信息的。param:Receive/Send,MySocketAsyncEventArgs类只带有一个参数的构造函数,说明类在实例化时就被说明是用来完成接收还是发送任务的。

2.SocketAsyncEventArgsWithId类:该类是一个用户的连接的最小单元,也就是说对一个用户来说有两个SocketAsyncEventArgs对象,这两个对象是一样的,但是有一个用来发送消息,一个接收消息,这样做的目的是为了实现双工通讯,提高用户体验。默认的用户标识是"-1”,状态是false表示不可用

01 internal sealed class SocketAsyncEventArgsWithId:IDisposable
02 {
03 private string uid = "-1";
04 private bool state = false;
05 private MySocketAsyncEventArgs receivesaea;
06 private MySocketAsyncEventArgs sendsaea;
07 internal string UID
08 {
09 get { return uid; }
10 set
11 {
12 uid = value;
13 ReceiveSAEA.UID = value;
14 SendSAEA.UID = value;
15 }
16 }
17 }
UID:用户标识,跟MySocketAsyncEventArgs的UID是一样的,在对SocketAsycnEventArgsWithId的UID属性赋值的时候也对MySocketAsyncEventArgs的UID属性赋值。
State:表示连接的可用与否,一旦连接被实例化放入连接池后State即变为True

3.SocketAsyncEventArgsPool类:这个类才是真正的连接池类,这个类真正的为server提供一个可用的用户连接,并且维持这个连接直到用户断开,并把不用的连接放回连接池中供下一用户连接。

这个类是最核心的东西了,当然它设计的好坏影响着总体性能的好坏,它的各项操作也可能成为整个服务器性能的瓶颈。Pool包含有几个成员:

  • Stack<SocketAsyncEventArgsWithId> pool : 从字面意思上就知道这是一个连接栈,用来存放空闲的连接的,使用时pop出来,使用完后push进去。
  • IDictionary<string, SocketAsyncEventArgsWithId> busypool :这个也很好理解,busypool是一个字典类型的,用来存放正在使用的连接的,key是用户标识,设计的目的是为了统计在线用户数目和查找相应用户的连接,当然这是很重要的,为什么设计成字典类型的,是因为我们查找时遍历字典的关键字就行了而不用遍历每一项的UID,这样效率会有所提高。
  • string[] keys:这是一个存放用户标识的数组,起一个辅助的功能。
  • Count属性:返回连接池中可用的连接数。
  • OnlineUID属性:返回在线用户的标识列表。
  • Pop(string uid)方法:用于获取一个可用连接给用户。
  • Push(SocketAsyncEventArgsWithId item)方法:把一个使用完的连接放回连接池。
  • FindByUID(string uid)方法:查找在线用户连接,返回这个连接。
  • BusyPoolContains(string uid)方法:判断某个用户的连接是否在线。
01 internal sealed class SocketAsyncEventArgsPool:IDisposable
02 {
03 internal Stack<SocketAsyncEventArgsWithId> pool;
04 internal IDictionary<string, SocketAsyncEventArgsWithId> busypool;
05 private string[] keys;
06 internal Int32 Count
07 {
08 get
09 {
10 lock (this.pool)
11 {
12 return this.pool.Count;
13 }
14 }
15 }
16 internal string[] OnlineUID
17 {
18 get
19 {
20 lock (this.busypool)
21 {
22 busypool.Keys.CopyTo(keys, 0);
23 }
24 return keys;
25 }
26 }
27 internal SocketAsyncEventArgsPool(Int32 capacity)
28 {
29 keys = new string[capacity];
30 this.pool = new Stack<SocketAsyncEventArgsWithId>(capacity);
31 this.busypool = new Dictionary<string, SocketAsyncEventArgsWithId>(capacity);
32 }
33 internal SocketAsyncEventArgsWithId Pop(string uid)
34 {
35 if (uid == string.Empty || uid == "")
36 return null;
37 SocketAsyncEventArgsWithId si = null;
38 lock (this.pool)
39 {
40 si = this.pool.Pop();
41 }
42 si.UID = uid;
43 si.State = true;    //mark the state of pool is not the initial step
44 busypool.Add(uid, si);
45 return si;
46 }
47 internal void Push(SocketAsyncEventArgsWithId item)
48 {
49 if (item == null)
50 throw new ArgumentNullException("SocketAsyncEventArgsWithId对象为空");
51 if (item.State == true)
52 {
53 if (busypool.Keys.Count != 0)
54 {
55 if (busypool.Keys.Contains(item.UID))
56 busypool.Remove(item.UID);
57 else
58 throw new ArgumentException("SocketAsyncEventWithId不在忙碌队列中");
59 }
60 else
61 throw new ArgumentException("忙碌队列为空");
62 }
63 item.UID = "-1";
64 item.State = false;
65 lock (this.pool)
66 {
67 this.pool.Push(item);
68 }
69 }
70 internal SocketAsyncEventArgsWithId FindByUID(string uid)
71 {
72 if (uid == string.Empty || uid == "")
73 return null;
74 SocketAsyncEventArgsWithId si = null;
75 foreach (string key in this.OnlineUID)
76 {
77 if (key == uid)
78 {
79 si = busypool[uid];
80 break;
81 }
82 }
83 return si;
84 }
85 internal bool BusyPoolContains(string uid)
86 {
87 lock (this.busypool)
88 {
89 return busypool.Keys.Contains(uid);
90 }
91 }
92 }
 Note:这个类的设计缺陷是使用了太多的lock语句,对对象做了太多的互斥操作,所以我尽量的把lock内的语句化简或挪到lock外部执行。

4.BufferManager类:该类是一个管理连接缓冲区的类,职责是为每一个连接维持一个接收数据的区域。它的设计也采用了类似与池的技术,先实例化好多内存区域,并把每一块的地址放入栈中,每执行依次pop时拿出一块区域来给SocketAsyncEventArgs对象作为Buffer.

01 internal sealed class BufferManager:IDisposable
02 {
03 private Byte[] buffer;
04 private Int32 bufferSize;
05 private Int32 numSize;
06 private Int32 currentIndex;
07 private Stack<Int32> freeIndexPool;
08 internal Boolean SetBuffer(SocketAsyncEventArgs args)
09 {
10 if (this.freeIndexPool.Count > 0)
11 {
12 args.SetBuffer(this.buffer, this.freeIndexPool.Pop(), this.bufferSize);
13 }
14 else
15 {
16 if ((this.numSize - this.bufferSize) < this.currentIndex)
17 {
18 return false;
19 }
20 args.SetBuffer(this.buffer, this.currentIndex, this.bufferSize);
21 this.currentIndex += this.bufferSize;
22 }
23 return true;
24 }
25 }

 

5.RequestHandler类:这里代码就不贴了,这个类也比较简单。比如发送方要发送的内容为:hello,nice to meet you那么真正发送的内容是:[length=22]hello,nice to meet you,length后的数字是字符串的长度,接收方接收到消息后根据长度检验和获取信息。

强烈推荐这篇文章:http://www.cnblogs.com/JimmyZhang/archive/2008/09/16/1291854.html

6.SocketListener类:终于到了最重要的部分了,也是一个对外部真正有意义的类,这个类监听用户的连接请求并从连接池取出一个可用连接给用户,并且时刻监听用户发来的数据并处理。在设计这个类时为了迎合双工通信我把监听的任务放到另一个线程中去,这也是我为什么要给每个用户两个SocketAsyncEventArgs的原因。当然两个线程是不够的还要异步。比较重要的语句我都用粗体标注了。socket的方法都是成对出现的,ReceiveAsync对应OnReceiveCompleted,SendAsync对应OnSendCompleted,所以理解起来也不算太难,只是要注意一点:就是接收和发送消息是在两个线程里的。

001 public sealed class SocketListener:IDisposable
002 {
003 /// <summary>
004 /// 缓冲区
005 /// </summary>
006 private BufferManager bufferManager;
007 /// <summary>
008 /// 服务器端Socket
009 /// </summary>
010 private Socket listenSocket;
011 /// <summary>
012 /// 服务同步锁
013 /// </summary>
014 private static Mutex mutex = new Mutex();
015 /// <summary>
016 /// 当前连接数
017 /// </summary>
018 private Int32 numConnections;
019 /// <summary>
020 /// 最大并发量
021 /// </summary>
022 private Int32 numConcurrence;
023 /// <summary>
024 /// 服务器状态
025 /// </summary>
026 private ServerState serverstate;
027 /// <summary>
028 /// 读取写入字节
029 /// </summary>
030 private const Int32 opsToPreAlloc = 1;
031 /// <summary>
032 /// Socket连接池
033 /// </summary>
034 private SocketAsyncEventArgsPool readWritePool;
035 /// <summary>
036 /// 并发控制信号量
037 /// </summary>
038 private Semaphore semaphoreAcceptedClients;
039 /// <summary>
040 /// 通信协议
041 /// </summary>
042 private RequestHandler handler;
043 /// <summary>
044 /// 回调委托
045 /// </summary>
046 /// <param name="IP"></param>
047 /// <returns></returns>
048 public delegate string GetIDByIPFun(string IP);
049 /// <summary>
050 /// 回调方法实例
051 /// </summary>
052 private GetIDByIPFun GetIDByIP;
053 /// <summary>
054 /// 接收到信息时的事件委托
055 /// </summary>
056 /// <param name="info"></param>
057 public delegate void ReceiveMsgHandler(string uid, string info);
058 /// <summary>
059 /// 接收到信息时的事件
060 /// </summary>
061 public event ReceiveMsgHandler OnMsgReceived;
062 /// <summary>
063 /// 开始监听数据的委托
064 /// </summary>
065 public delegate void StartListenHandler();
066 /// <summary>
067 /// 开始监听数据的事件
068 /// </summary>
069 public event StartListenHandler StartListenThread;
070 /// <summary>
071 /// 发送信息完成后的委托
072 /// </summary>
073 /// <param name="successorfalse"></param>
074 public delegate void SendCompletedHandler(string uid,string exception);
075 /// <summary>
076 /// 发送信息完成后的事件
077 /// </summary>
078 public event SendCompletedHandler OnSended;
079 /// <summary>
080 /// 获取当前的并发数
081 /// </summary>
082 public Int32 NumConnections
083 {
084 get { return this.numConnections; }
085 }
086 /// <summary>
087 /// 最大并发数
088 /// </summary>
089 public Int32 MaxConcurrence
090 {
091 get { return this.numConcurrence; }
092 }
093 /// <summary>
094 /// 返回服务器状态
095 /// </summary>
096 public ServerState State
097 {
098 get
099 {
100 return serverstate;
101 }
102 }
103 /// <summary>
104 /// 获取当前在线用户的UID
105 /// </summary>
106 public string[] OnlineUID
107 {
108 get { return readWritePool.OnlineUID; }
109 }
110 /// <summary>
111 /// 初始化服务器端
112 /// </summary>
113 /// <param name="numConcurrence">并发的连接数量(1000以上)</param>
114 /// <param name="receiveBufferSize">每一个收发缓冲区的大小(32768)</param>
115 public SocketListener(Int32 numConcurrence, Int32 receiveBufferSize, GetIDByIPFun GetIDByIP)
116 {
117 serverstate = ServerState.Initialing;
118 this.numConnections = 0;
119 this.numConcurrence = numConcurrence;
120 this.bufferManager = new BufferManager(receiveBufferSize * numConcurrence * opsToPreAlloc, receiveBufferSize);
121 this.readWritePool = new SocketAsyncEventArgsPool(numConcurrence);
122 this.semaphoreAcceptedClients = new Semaphore(numConcurrence, numConcurrence);
123 handler = new RequestHandler();
124 this.GetIDByIP = GetIDByIP;
125 }
126 /// <summary>
127 /// 服务端初始化
128 /// </summary>
129 public void Init()
130 {
131 this.bufferManager.InitBuffer();
132 SocketAsyncEventArgsWithId readWriteEventArgWithId;
133 for (Int32 i = 0; i < this.numConcurrence; i++)
134 {
135 readWriteEventArgWithId = new SocketAsyncEventArgsWithId();
136 readWriteEventArgWithId.ReceiveSAEA.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceiveCompleted);
137 readWriteEventArgWithId.SendSAEA.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
138 //只给接收的SocketAsyncEventArgs设置缓冲区
139 this.bufferManager.SetBuffer(readWriteEventArgWithId.ReceiveSAEA);
140 this.readWritePool.Push(readWriteEventArgWithId);
141 }
142 serverstate = ServerState.Inited;
143 }
144 /// <summary>
145 /// 启动服务器
146 /// </summary>
147 /// <param name="data">端口号</param>
148 public void Start(Object data)
149 {
150 Int32 port = (Int32)data;
151 IPAddress[] addresslist = Dns.GetHostEntry(Environment.MachineName).AddressList;
152 IPEndPoint localEndPoint = new IPEndPoint(addresslist[addresslist.Length - 1], port);
153 this.listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
154 if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
155 {
156 this.listenSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);
157 this.listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));
158 }
159 else
160 {
161 this.listenSocket.Bind(localEndPoint);
162 }
163 this.listenSocket.Listen(100);
164 this.StartAccept(null);
165 //开始监听已连接用户的发送数据
166 StartListenThread();
167 serverstate = ServerState.Running;
168 mutex.WaitOne();
169 }
170 /// <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 }
189 /// <summary>
190 /// 发送信息
191 /// </summary>
192 /// <param name="uid">要发送的用户的uid</param>
193 /// <param name="msg">消息体</param>
194 public void Send(string uid, string msg)
195 {
196 if (uid == string.Empty || uid == "" || msg == string.Empty || msg == "")
197 return;
198 SocketAsyncEventArgsWithId socketWithId = readWritePool.FindByUID(uid);
199 if (socketWithId == null)
200 //说明用户已经断开
201 //100   发送成功
202 //200   发送失败
203 //300   用户不在线
204 //其它  表示异常的信息
205 OnSended(uid, "300");
206 else
207 {
208 MySocketAsyncEventArgs e = socketWithId.SendSAEA;
209 if (e.SocketError == SocketError.Success)
210 {
211 int i = 0;
212 try
213 {
214 string message = @"[lenght=" + msg.Length + @"]" + msg;
215 byte[] sendbuffer = Encoding.Unicode.GetBytes(message);
216 e.SetBuffer(sendbuffer, 0, sendbuffer.Length);
217 Boolean willRaiseEvent = (e.UserToken as Socket).SendAsync(e);
218 if (!willRaiseEvent)
219 {
220 this.ProcessSend(e);
221 }
222 }
223 catch (Exception ex)
224 {
225 if (i <= 5)
226 {
227 i++;
228 //如果发送出现异常就延迟0.01秒再发
229 Thread.Sleep(10);
230 Send(uid, msg);
231 }
232 else
233 {
234 OnSended(uid, ex.ToString());
235 }
236 }
237 }
238 else
239 {
240 OnSended(uid, "200");
241 this.CloseClientSocket(((MySocketAsyncEventArgs)e).UID);
242 }
243 }
244 }
245 /// <summary>
246 /// 停止服务器
247 /// </summary>
248 public void Stop()
249 {
250 if(listenSocket!=null)
251 listenSocket.Close();
252 listenSocket = null;
253 Dispose();
254 mutex.ReleaseMutex();
255 serverstate = ServerState.Stoped;
256 }
257 private void StartAccept(SocketAsyncEventArgs acceptEventArg)
258 {
259 if (acceptEventArg == null)
260 {
261 acceptEventArg = new SocketAsyncEventArgs();
262 acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
263 }
264 else
265 acceptEventArg.AcceptSocket = null;
266 this.semaphoreAcceptedClients.WaitOne();
267 Boolean willRaiseEvent = this.listenSocket.AcceptAsync(acceptEventArg);
268 if (!willRaiseEvent)
269 {
270 this.ProcessAccept(acceptEventArg);
271 }
272 }
273 private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e)
274 {
275 this.ProcessAccept(e);
276 }
277 private void ProcessAccept(SocketAsyncEventArgs e)
278 {
279 if (e.LastOperation != SocketAsyncOperation.Accept)    //检查上一次操作是否是Accept,不是就返回
280 return;
281 if (e.BytesTransferred <= 0)    //检查发送的长度是否大于0,不是就返回
282 return;
283 string UID = GetIDByIP((e.AcceptSocket.RemoteEndPoint as IPEndPoint).Address.ToString());   //根据IP获取用户的UID
284 if (UID == string.Empty || UID == null || UID == "")
285 return;
286 if (readWritePool.BusyPoolContains(UID))    //判断现在的用户是否已经连接,避免同一用户开两个连接
287 return;
288 SocketAsyncEventArgsWithId readEventArgsWithId = this.readWritePool.Pop(UID);
289 readEventArgsWithId.ReceiveSAEA.UserToken = e.AcceptSocket;
290 readEventArgsWithId.SendSAEA.UserToken = e.AcceptSocket;
291 Interlocked.Increment(ref this.numConnections);
292 this.StartAccept(e);
293 }
294 private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e)
295 {
296 ProcessReceive(e);
297 }
298 private void OnSendCompleted(object sender, SocketAsyncEventArgs e)
299 {
300 ProcessSend(e);
301 }
302 private void ProcessReceive(SocketAsyncEventArgs e)
303 {
304 if (e.LastOperation != SocketAsyncOperation.Receive)
305 return;
306 if (e.BytesTransferred > 0)
307 {
308 if (e.SocketError == SocketError.Success)
309 {
310 Int32 byteTransferred = e.BytesTransferred;
311 string received = Encoding.Unicode.GetString(e.Buffer, e.Offset, byteTransferred);
312 //检查消息的准确性
313 string[] msg = handler.GetActualString(received);
314 foreach (string m in msg)
315 OnMsgReceived(((MySocketAsyncEventArgs)e).UID, m);
316 //可以在这里设一个停顿来实现间隔时间段监听,这里的停顿是单个用户间的监听间隔
317 //发送一个异步接受请求,并获取请求是否为成功
318 Boolean willRaiseEvent = (e.UserToken as Socket).ReceiveAsync(e);
319 if (!willRaiseEvent)
320 ProcessReceive(e);
321 }
322 }
323 else
324 this.CloseClientSocket(((MySocketAsyncEventArgs)e).UID);
325 }
326 private void ProcessSend(SocketAsyncEventArgs e)
327 {
328 if (e.LastOperation != SocketAsyncOperation.Send)
329 return;
330 if (e.BytesTransferred > 0)
331 {
332 if (e.SocketError == SocketError.Success)
333 OnSended(((MySocketAsyncEventArgs)e).UID, "100");
334 else
335 OnSended(((MySocketAsyncEventArgs)e).UID, "200");
336 }
337 else
338 this.CloseClientSocket(((MySocketAsyncEventArgs)e).UID);
339 }
340 private void CloseClientSocket(string uid)
341 {
342 if (uid == string.Empty || uid == "")
343 return;
344 SocketAsyncEventArgsWithId saeaw = readWritePool.FindByUID(uid);
345 if (saeaw == null)
346 return;
347 Socket s = saeaw.ReceiveSAEA.UserToken as Socket;
348 try
349 {
350 s.Shutdown(SocketShutdown.Both);
351 }
352 catch (Exception)
353 {
354 //客户端已经关闭
355 }
356 this.semaphoreAcceptedClients.Release();
357 Interlocked.Decrement(ref this.numConnections);
358 this.readWritePool.Push(saeaw);
359 }
360 #region IDisposable Members
361 public void Dispose()
362 {
363 bufferManager.Dispose();
364 bufferManager = null;
365 readWritePool.Dispose();
366 readWritePool = null;
367 }
368 #endregion
369 }
 
关于所有的类已经介绍完了,相信各位都已经很明白了,如果不是太清楚就看源代码,别忘了源代码是最好的文档!

当然这个2.0仍然还有很多缺陷,比如职责划分不太OO,运行不太稳定,处理异常能力较差,处理超负载的连接能力较差,主动拒绝,可测试性差等,希望大家多给点建议,改进才对啊。

源代码下载:http://files.cnblogs.com/niuchenglei/socketlib.rar

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Socket连接池
【用SocketAsyncEventArgs 池 线程构建服务器“推”】的源代码分析1
C# 获得网页js执行之后的Html代码(body)部分(修订版)
C# 简易异步日志类 [ C# | Log | TextWriterTraceListen...
每日定时自动备份的服务小工具源码分享
C#开发高性能Log Help类设计开发
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服