高性能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类,是由于特定情况需要而添加了一些外加属性的类。

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

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

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

01internal sealed class SocketAsyncEventArgsWithId:IDisposable
02{
03private string uid = "-1";
04private bool state = false;
05private MySocketAsyncEventArgs receivesaea;
06private MySocketAsyncEventArgs sendsaea;
07internal string UID
08{
09get { return uid; }
10set
11{
12uid = value;
13ReceiveSAEA.UID = value;
14SendSAEA.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)方法:判断某个用户的连接是否在线。
   
01internal sealed class SocketAsyncEventArgsPool:IDisposable
02{
03internal Stack<SocketAsyncEventArgsWithId> pool;
04internal IDictionary<string, SocketAsyncEventArgsWithId> busypool;
05private string[] keys;
06internal Int32 Count
07{
08get
09{
10lock (this.pool)
11{
12return this.pool.Count;
13}
14}
15}
16internal string[] OnlineUID
17{
18get
19{
20lock (this.busypool)
21{
22busypool.Keys.CopyTo(keys, 0);
23}
24return keys;
25}
26}
27internal SocketAsyncEventArgsPool(Int32 capacity)
28{
29keys = new string[capacity];
30this.pool = new Stack<SocketAsyncEventArgsWithId>(capacity);
31this.busypool = new Dictionary<string, SocketAsyncEventArgsWithId>(capacity);
32}
33internal SocketAsyncEventArgsWithId Pop(string uid)
34{
35if (uid == string.Empty || uid == "")
36return null;
37SocketAsyncEventArgsWithId si = null;
38lock (this.pool)
39{
40si = this.pool.Pop();
41}
42si.UID = uid;
43si.State = true;    //mark the state of pool is not the initial step
44busypool.Add(uid, si);
45return si;
46}
47internal void Push(SocketAsyncEventArgsWithId item)
48{
49if (item == null)
50throw new ArgumentNullException("SocketAsyncEventArgsWithId对象为空");
51if (item.State == true)
52{
53if (busypool.Keys.Count != 0)
54{
55if (busypool.Keys.Contains(item.UID))
56busypool.Remove(item.UID);
57else
58throw new ArgumentException("SocketAsyncEventWithId不在忙碌队列中");
59}
60else
61throw new ArgumentException("忙碌队列为空");
62}
63item.UID = "-1";
64item.State = false;
65lock (this.pool)
66{
67this.pool.Push(item);
68}
69}
70internal SocketAsyncEventArgsWithId FindByUID(string uid)
71{
72if (uid == string.Empty || uid == "")
73return null;
74SocketAsyncEventArgsWithId si = null;
75foreach (string key in this.OnlineUID)
76{
77if (key == uid)
78{
79si = busypool[uid];
80break;
81}
82}
83return si;
84}
85internal bool BusyPoolContains(string uid)
86{
87lock (this.busypool)
88{
89return busypool.Keys.Contains(uid);
90}
91}
92}
 Note:这个类的设计缺陷是使用了太多的lock语句,对对象做了太多的互斥操作,所以我尽量的把lock内的语句化简或挪到lock外部执行。

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

01internal sealed class BufferManager:IDisposable
02{
03private Byte[] buffer;
04private Int32 bufferSize;
05private Int32 numSize;
06private Int32 currentIndex;
07private Stack<Int32> freeIndexPool;
08internal Boolean SetBuffer(SocketAsyncEventArgs args)
09{
10if (this.freeIndexPool.Count > 0)
11{
12args.SetBuffer(this.buffer, this.freeIndexPool.Pop(), this.bufferSize);
13}
14else
15{
16if ((this.numSize - this.bufferSize) < this.currentIndex)
17{
18return false;
19}
20args.SetBuffer(this.buffer, this.currentIndex, this.bufferSize);
21this.currentIndex += this.bufferSize;
22}
23return 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,所以理解起来也不算太难,只是要注意一点:就是接收和发送消息是在两个线程里的。

    
001public sealed class SocketListener:IDisposable
002{
003/// <summary>
004/// 缓冲区
005/// </summary>
006private BufferManager bufferManager;
007/// <summary>
008/// 服务器端Socket
009/// </summary>
010private Socket listenSocket;
011/// <summary>
012/// 服务同步锁
013/// </summary>
014private static Mutex mutex = new Mutex();
015/// <summary>
016/// 当前连接数
017/// </summary>
018private Int32 numConnections;
019/// <summary>
020/// 最大并发量
021/// </summary>
022private Int32 numConcurrence;
023/// <summary>
024/// 服务器状态
025/// </summary>
026private ServerState serverstate;
027/// <summary>
028/// 读取写入字节
029/// </summary>
030private const Int32 opsToPreAlloc = 1;
031/// <summary>
032/// Socket连接池
033/// </summary>
034private SocketAsyncEventArgsPool readWritePool;
035/// <summary>
036/// 并发控制信号量
037/// </summary>
038private Semaphore semaphoreAcceptedClients;
039/// <summary>
040/// 通信协议
041/// </summary>
042private RequestHandler handler;
043/// <summary>
044/// 回调委托
045/// </summary>
046/// <param name="IP"></param>
047/// <returns></returns>
048public delegate string GetIDByIPFun(string IP);
049/// <summary>
050/// 回调方法实例
051/// </summary>
052private GetIDByIPFun GetIDByIP;
053/// <summary>
054/// 接收到信息时的事件委托
055/// </summary>
056/// <param name="info"></param>
057public delegate void ReceiveMsgHandler(string uid, string info);
058/// <summary>
059/// 接收到信息时的事件
060/// </summary>
061public event ReceiveMsgHandler OnMsgReceived;
062/// <summary>
063/// 开始监听数据的委托
064/// </summary>
065public delegate void StartListenHandler();
066/// <summary>
067/// 开始监听数据的事件
068/// </summary>
069public event StartListenHandler StartListenThread;
070/// <summary>
071/// 发送信息完成后的委托
072/// </summary>
073/// <param name="successorfalse"></param>
074public delegate void SendCompletedHandler(string uid,string exception);
075/// <summary>
076/// 发送信息完成后的事件
077/// </summary>
078public event SendCompletedHandler OnSended;
079/// <summary>
080/// 获取当前的并发数
081/// </summary>
082public Int32 NumConnections
083{
084get { return this.numConnections; }
085}
086/// <summary>
087/// 最大并发数
088/// </summary>
089public Int32 MaxConcurrence
090{
091get { return this.numConcurrence; }
092}
093/// <summary>
094/// 返回服务器状态
095/// </summary>

原文链接: https://www.cnblogs.com/jxnclyk/archive/2010/09/03/1816664.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    高性能Socket设计实现

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/14656

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午2:22
下一篇 2023年2月7日 下午2:24

相关推荐