因为学习的需要,要求一个高性能的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包含有几个成员:
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
联系客服