几种Socket服务器模型比较!

来源:互联网 发布:人类登月是真是假 知乎 编辑:程序博客网 时间:2024/06/03 04:32

、异步BeginXXX,EndXXX

先看其实现的方式:

1.监听

[csharp] view plain copy
 print?
  1. //开启监听线程  
  2.        public void StartListenThread()  
  3.        {  
  4.            Thread listen_thread = new Thread(new ThreadStart(ListenThread));  
  5.            listen_thread.Start();  
  6.        }  
  7.   
  8.        public void ListenThread()  
  9.        {  
  10.            try  
  11.            {  
  12.                listener = new TcpListener(System.Net.IPAddress.Parse(地址),Convert.ToInt32(端口));  
  13.                listener.Start();  
  14.                while (IsLoopContinue)  
  15.                {  
  16.                    waithandle.Reset();  
  17.                    listener.BeginAcceptTcpClient(new AsyncCallback(ListenCallBack), listener);  
  18.                    waithandle.WaitOne();//用以同步异步接受连接的请求  
  19.                }  
  20.            }  
  21.            catch   
  22.            {  
  23.                IsLoopContinue = false;  
  24.            }  
  25.        }  
2.Accept回调

[csharp] view plain copy
 print?
  1. //异步接受连接回调函数  
  2.        public void ListenCallBack(IAsyncResult ar)  
  3.        {  
  4.            try  
  5.            {  
  6.                TcpListener mylistener = ar.AsyncState as TcpListener;  
  7.                TcpClient client = mylistener.EndAcceptTcpClient(ar);  
  8.                SocketStruct newclient = new SocketStruct(client);//封装socket                    
  9.                waithandle.Set();//接受下一个连接              
  10.                newclient._nws.BeginRead(newclient._readbt, 0, newclient._readbt.Length, new AsyncCallback(ReadCallBack), newclient);//首次读取  
  11.            }  
  12.            catch  
  13.            {  
  14.                //  
  15.            }  
  16.        }  
在accepet回调中才可获取时间完成后的信息,在异步accept完成后将得到新客户端socket,这时可以利用自定义的小型类去封装socket,该类可以包括该socket,端口,ip,流,缓冲区等等信息,或者其他有关业务的字段。并在首次获取客户端时进行第一次异步读取。异步读取时可以传入供socket操作的缓冲区,以及一个object对象,该对象原本只需传入一个socket对象即可,但是为了与业务交互,将自定义的小型类的实例newclient传入是再好不过了。

3.异步读的回调

[csharp] view plain copy
 print?
  1. //异步读取数据回调函数  
  2.       public void ReadCallBack(IAsyncResult ar)  
  3.       {  
  4.           try  
  5.           {  
  6.               SocketStruct client = (SocketStruct)ar.AsyncState;  
  7.               int count = client._nws.EndRead(ar);  
  8.               if (count == 0)  
  9.               {    
  10.                  //处理客户端正常断开              
  11.               }  
  12.               else  
  13.               {  
  14.                  //客户端有数据,此时可以在缓冲区找到传来的数据  
  15.                    
  16.                   if (IsLoopContinue)//只有监听线程存在,则继续读取  
  17.                   {   //递归死循环  
  18.                       client.InitReadBt();  
  19.                       client._nws.BeginRead(client._readbt, 0, client._readbt.Length, ReadCallBack, client);  
  20.                   }  
  21.               }  
  22.           }  
  23.           catch  
  24.           {               
  25.           }  
  26.   
  27.       }  
异步完成后则进入回调,在回调信息里最重要的就是EndRead后返回的字节数了,返回0则代表响应了一次I/0断开事件。这个0字节绝对不代表发送空数据,因为任何所谓的"空"数据都不是0字节,这个0字节是socket底层的一个返回值,可以有效的提示客户端断开。若大于0,则表明这次事件成功的读取到了数据,则可以递归进行下次读取。

4.异步发送和异步发送回调

发送 client._nws.BeginWrite(缓冲区, 0, count, SendCallBack, client);

回调  public void SendCallBack(IAsyncResult ar)
        {
            SocketStruct ss = ar.AsyncState as SocketStruct;
            ss._nws.EndWrite(ar);
        }
异步发送的回调没什么太多的信息,主要作用是看发送成功没。

总结:

这种方式主要利用递归生成while(true)来实现循环监听和读取。其中读取和发送均是利用socket中的流的异步读与写,每次读写均需要传入一个buffer,每个动作都有一个对应的完成事件,颇有IOCP的影子,至于是否支持大量客户端暂时不清楚,因为这种BeginXXX的操作到底是哪种线程类型,资源消耗如何还值得研究,但必须肯定的是绝对比new Thread这种方式要省资源和高效的多。


二、Select模型
为了方便select的实现,需要自定义一个列表,该列表需要具有线程安全级别

 

[csharp] view plain copy
 print?
  1. public class ListHandler<T>  
  2.    {  
  3.   
  4.       public List<T> list;  
  5.       public ListHandler()  
  6.       {  
  7.           list=new List<T>();  
  8.       }  
  9.       public void Add(T value)  
  10.       {  
  11.           lock (list)  
  12.           {  
  13.               list.Add(value);  
  14.           }  
  15.       }  
  16.       public void Remove(T value)  
  17.       {  
  18.           lock (list)  
  19.           {  
  20.               list.Remove(value);  
  21.           }  
  22.       }  
  23.   
  24.       public List<T> ToList()  
  25.       {  
  26.           lock (list)  
  27.           {  
  28.               return list.ToList();  
  29.           }  
  30.       }  
  31.   
  32.       public T Find(Predicate<T> match)  
  33.       {  
  34.           lock (list)  
  35.           {  
  36.               return list.Find(match);  
  37.           }  
  38.       }  
  39.    }  
其实也不需要一个泛型类,因为T肯定是用来存放socket类型的,当然,为了扩展,也可将封装socket的小型类做为T

在开始程序之前定义好该列表对象:

[csharp] view plain copy
 print?
  1. /// <summary>  
  2.        /// 客户端Socket列表  
  3.        /// </summary>  
  4.        private ListHandler<Socket> socketList;  

select模型代码实现如下:

[csharp] view plain copy
 print?
  1. /// <summary>  
  2. ///  监听线程  
  3. /// </summary>  
  4. public void StartListenThread()  
  5. {  
  6.     try  
  7.     {  
  8.         listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  9.         listener.Bind(  
  10.                       new IPEndPoint(IPAddress.Parse(readXML.GetIp()),  
  11.                       Convert.ToInt32(readXML.GetPort()))  
  12.                       );  
  13.         listener.Listen(10);  
  14.          
  15.         socketList.Add(listener);  
  16.         while (true)  
  17.         {  
  18.             List<Socket> checkReadList = socketList.ToList();                    
  19.             Socket.Select(checkReadList, nullnull, 1000);  
  20.             int count = checkReadList.Count;                  
  21.             if (count > 0)  
  22.             {  
  23.                 sb = "有数据的客户端个数为:" + count.ToString();  
  24.                 Console.WriteLine(sb);  
  25.                 for (int i = 0; i < count; i++)  
  26.                 {  
  27.                     if (checkReadList[i].Equals(listener))  
  28.                     {  
  29.                         listener.BeginAccept(new AsyncCallback(ListenCallBack), listener);  
  30.                     }  
  31.                     else  
  32.                     {  
  33.                         SocketStruct ss = clientInfoList.Find(s => s._socketClient == checkReadList[i]);                     
  34.                         checkReadList[i].BeginReceive(buffer.totalBytes, ss._offset, BUFFERSIZE, 0, new AsyncCallback(ReceiveCallBack), ss);  
  35.                     }  
  36.                 }  
  37.             }  
  38.         }  
  39.     }  
  40.     catch  
  41.     {  
  42.     }  
  43. }  
我想这几十行代码再霸气不过了,listen、accept、receive几个动作一气呵成,感觉直接就把服务器代码写完了。先来看下MSDN的解释:

Select 是一种静态方法,它可确定一个或多个 Socket 实例的状态。必须先将一个或多个套接字放入 IList 中,然后才能使用 Select 方法。通过调用 Select(将 IList 作为 checkRead 参数),可检查是否具有可读性。若要检查套接字是否具有可写性,请使用 checkWrite 参数。若要检测错误条件,请使用 checkError。在调用 Select 之后,IList 中将仅填充那些满足条件的套接字。

如果当前处于侦听状态,则可读意味着可成功地对 Accept 进行调用而没有阻止。如果当前已接受连接,则可读意味着有可读取的数据。这些情况下,所有的接收操作均可成功进行而没有阻止。可读性也可指示远程 Socket 是否已经关闭连接;如果连接已关闭,则对 Receive 的调用将立即返回,并返回零字节。

如果至少一个相关套接字(checkReadcheckWrite 和 checkError 列表中的套接字)符合其指定的条件,或者超过 microSeconds 参数,则无论先出现其中哪种情况,都会返回 Select。将microSeconds 设置为 -1 会指定无限大的超时值。

如果对 Connect 进行非阻止调用,则可写意味着已经成功连接。如果已经建立连接,则可写性意味着所有的发送操作均会成功完成而没有阻止。

如果对 Connect 进行非阻止调用,则 checkerror 参数将标识尚未成功连接的套接字。

如果只想确定单个 Socket 的状态,请使用 Poll 方法。


针对MSDN的详细解释,代码如此实现:

1.建立listensocket并且将其加入列表socketList;

2.调用Socket.Select方法

             参数:
        //   checkRead:
        //     要检查可读性的 System.NET.Sockets.Socket 实例的 System.Collections.IList。
        //
        //   checkWrite:
        //     一个 System.Net.Sockets.Socket 实例的 System.Collections.IList,用于检查可写性。
        //
        //   checkError:
        //     要检查错误的 System.Net.Sockets.Socket 实例的 System.Collections.IList。
        //
        //   microSeconds:
        //     超时值(以毫秒为单位)。A -1 值指示超时值为无限大。

在这里只需要找出有数据的socket即可,因此第二和三个参数均为null,超时设为1秒

此时列表中只有一个对象,其实就相当于Socket.Poll

3.判断数据的来源

注意Select的时候是将第一个参数中的对象做为check对象,即检查对象,去检测其是否有数据,方法执行完后会把所有符合条件的对象填充到第一个参数里,

所以第一个参数在执行完后肯定是多个对象的列表了。

对于监听socket,它有数据的情况只可能是有了新客户端,此时可以进行异步Accept,并且添加回调

[csharp] view plain copy
 print?
  1. /// <summary>  
  2.        /// 异步接受连接回调函数  
  3.        /// </summary>  
  4.        /// <param name="ia"></param>  
  5.        public void ListenCallBack(IAsyncResult ia)  
  6.        {  
  7.            Socket listener = (Socket)ia.AsyncState;  
  8.            Socket newClient = listener.EndAccept(ia);  
  9.            socketList.Add(newClient);  
  10.             
  11.        }  

新客户端加入列表后,对于下次的Select操作,第一个参数将不再是列表cout为1的lisent对象了,而是有listensocket和客户端socket,所以若非listensocket产生了数据,

则一定是有数据从客户端发送过来了,立刻进行接收操作,并且回调

[csharp] view plain copy
 print?
  1. /// <summary>  
  2.       /// 异步接收数据回调函数  
  3.       /// </summary>  
  4.       /// <param name="ia"></param>  
  5.       public void ReceiveCallBack(IAsyncResult ia)  
  6.       {  
  7.           SocketStruct ss = (SocketStruct)ia.AsyncState;            
  8.           Socket newClient = ss._socketClient;         
  9.           int endLength = newClient.EndReceive(ia);  
  10.           Console.WriteLine("读到的字节数:"+endLength.ToString());  
  11.           if (endLength > 0)  
  12.           {  
  13.               //在此处读取数据  
  14.           }  
  15.           //the client has disconnected  
  16.           else  
  17.           {  
  18.               try  
  19.               {  
  20.                   ss._socketClient.Shutdown(SocketShutdown.Send);  
  21.               }  
  22.               catch(Exception e)  
  23.               {  
  24.                   Console.WriteLine(e.Message);  
  25.               }  
  26.               ss._socketClient.Close();  
  27.                
  28.               socketList.Remove(ss._socketClient);//维护列表  
  29.        
  30.           }  
  31.       }  

若要发送数据,则在Select中设置检查可写操作即可筛选多个可发送的socket.

总结:

Select可以同时筛选出多个可读可写的对象,可以批量操作,对于某个时间段具有高并发的场合应该比较适合(没测过),我并没有选用它的原因是批量处理是它的优点也是缺点----由于它的这个超时时间的参数将导致若有客户端断开,则将在超时时间内不停的响应I/0,在这块儿的处理比较棘手,而且若一次筛选出很多有数据的对象,一般在服务器都要有很多业务处理,则可能会导致有些延迟的效果,暂时没有好的解决办法,于是换了另一种方式。不过它在批量筛选上确实很诱人,在某种特殊的场合一定有它的发挥之处。光看实现的方式就觉得很霸气了呢。


三、利用SocketAsyncEventArgs

对于SocketAsyncEventArgs在不用细说了,太多经典的代码和服务器算法模型均出自该类下的socket通信,这种方式虽然我不能去查证其是否用的是IOCP,但是个人认为应该是的,BeginXXX+回调的方式已经有一些完成事件的影子了,而SocketAsyncEventArgs又对每个对象进行了socket操作的封装。就拿MSDN上给出的例子来说这个SocketAsyncEventArgs实现的过程吧。

使用此类执行异步套接字操作的模式包含以下步骤:

  1. 分配一个新的 SocketAsyncEventArgs 上下文对象,或者从应用程序池中获取一个空闲的此类对象。

  2. 将该上下文对象的属性设置为要执行的操作(例如,完成回调方法、数据缓冲区、缓冲区偏移量以及要传输的最大数据量)。

  3. 调用适当的套接字方法 (xxxAsync) 以启动异步操作。

  4. 如果异步套接字方法 (xxxAsync) 返回 true,则在回调中查询上下文属性来获取完成状态。

  5. 如果异步套接字方法 (xxxAsync) 返回 false,则说明操作是同步完成的。 可以查询上下文属性来获取操作结果。

  6. 将该上下文重用于另一个操作,将它放回到应用程序池中,或者将它丢弃。

这是MSDN上的原话,它将这个步骤说的过于概括,以至于如果初次接触该类,真的很难下手,因此我将其添加注释如下:

  1. 分配一个新的 SocketAsyncEventArgs 上下文对象,或者从应用程序池中获取一个空闲的此类对象。

    每一个SocketAsyncEventArgs对象中有以下几个重要属性:

    public Socket AcceptSocket { get; set; }//accept后的socket对象赋给该对象,下次accept之前需要清除该对象(标为null)

    public byte[] Buffer { get; }//每个对象对应的缓冲区

    public int BytesTransferred { get; }//每次读取的字节数,类似于EndXXX完成后返回的字节数。

    public int Offset { get; }//缓冲区的偏移量

    public EndPoint RemoteEndPoint { get; set; }//目的源

    public object UserToken { get; set; }//类似于BeginXXX中的小型类Object对象,很有用

    public event EventHandler<SocketAsyncEventArgs> Completed;//响应IO的socket所有操作的事件

    public void SetBuffer(int offset, int count);//缓冲区已设好,只需调整大小

    public void SetBuffer(byte[] buffer, int offset, int count); //设置缓冲区,并设置大小

    这么多属性,自然需要很好的管理才行。第一个管理池就是SocketAsyncEventArgsPool,这就是上面步骤1中所说的应用程序池,该池用以管理每一个SocketAsyncEventArg对象,包括初始化,添加事件,出栈入栈(内部有栈)

  2. 将该上下文对象的属性设置为要执行的操作(例如,完成回调方法、数据缓冲区、缓冲区偏移量以及要传输的最大数据量)。

    在上面的池中设置Comleted事件,并且调用SetBuffer去设置缓冲区,这时,就有第二个管理池出现,BufferManager

    这个类创建了一大块内存区域,它将会被分成若干份,用以分配给每一个上下文对象的socket I/O操作,预分配内存可以很方便的进行内存重用,更重要的是这样可以防止内存碎片化。

    注意:暴露在BfferManager类上的操作是非线程安全的

  3. 调用适当的套接字方法 (xxxAsync) 以启动异步操作。

    若响应了IO操作,完成后则将进入Completed时间回调,这个时间回调就好比BeginXXX中的回调,但是比BeginXXX的回调要强大的多,该对象在这个回调中有一个LastOperation的枚举属性可以识别是哪种类型的操作,可以对此做出处理

  4. 如果异步套接字方法 (xxxAsync) 返回 true,则在回调中查询上下文属性来获取完成状态。

    注意是Socket.XXXAsync,类似于BeginXXX

  5. 如果异步套接字方法 (xxxAsync) 返回 false,则说明操作是同步完成的。 可以查询上下文属性来获取操作结果。

    这个我还真心没懂,因为还真没测出什么时候是同步的,有待研究

  6. 将该上下文重用于另一个操作,将它放回到应用程序池中,或者将它丢弃。

    若不需要则进栈,服务器始终不删除该对象,初始化时便已经建立好了所有必备类和对象

经过上面的分析,应该很明朗了,看实现的代码:

1.BufferManager类

[csharp] view plain copy
 print?
  1. class BufferManager  
  2.     {  
  3.         int m_numBytes;                 //buffer大小  
  4.         byte[] m_buffer;                  
  5.         Stack<int> m_freeIndexPool;     //管理池(栈操作)  
  6.         int m_currentIndex;  
  7.         int m_bufferSize;  
  8.   
  9.         public BufferManager(int totalBytes, int bufferSize)  
  10.         {  
  11.             m_numBytes = totalBytes;  
  12.             m_currentIndex = 0;  
  13.             m_bufferSize = bufferSize;  
  14.             m_freeIndexPool = new Stack<int>();  
  15.         }  
  16.   
  17.         public void InitBuffer()  
  18.         {  
  19.             m_buffer = new byte[m_numBytes];  
  20.         }  
  21.   
  22.         //为上下文对象分配内存;  
  23.         //若栈中有数据则表明有内存曾今被回收过,则新的上下文对象就用这块内存  
  24.         //否则就连续进行分配,直到内存满为止  
  25.         public bool SetBuffer(SocketAsyncEventArgs args)  
  26.         {  
  27.   
  28.             if (m_freeIndexPool.Count > 0)  
  29.             {  
  30.                 args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize);  
  31.             }  
  32.             else  
  33.             {  
  34.                 if ((m_numBytes - m_bufferSize) < m_currentIndex)  
  35.                 {  
  36.                     return false;  
  37.                 }  
  38.                 args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);  
  39.                 m_currentIndex += m_bufferSize;  
  40.             }  
  41.             return true;  
  42.         }  
  43.   
  44.         //从上下文中释放的内存都将放入管理池(栈)中  
  45.         public void FreeBuffer(SocketAsyncEventArgs args)  
  46.         {  
  47.             m_freeIndexPool.Push(args.Offset);  
  48.             args.SetBuffer(null, 0, 0);  
  49.         }  
  50.   
  51.     }  

2.SocketAsyncEventArgsPool类

[csharp] view plain copy
 print?
  1. /// <summary>  
  2.    /// 该类定义可用的上下文对象集合  
  3.    /// </summary>  
  4.    class SocketAsyncEventArgsPool  
  5.    {  
  6.        Stack<SocketAsyncEventArgs> m_pool;  
  7.   
  8.        /// <summary>  
  9.        /// 初始化对象池  
  10.        /// </summary>  
  11.        /// <param name="capacity">最大连接数量</param>  
  12.        public SocketAsyncEventArgsPool(int capacity)  
  13.        {  
  14.            m_pool = new Stack<SocketAsyncEventArgs>(capacity);  
  15.        }  
  16.   
  17.        //回收对对象  
  18.        public void Push(SocketAsyncEventArgs item)  
  19.        {  
  20.            if (item == null) { throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); }  
  21.            lock (m_pool)  
  22.            {  
  23.                m_pool.Push(item);  
  24.            }  
  25.        }  
  26.   
  27.        //分配对象  
  28.        public SocketAsyncEventArgs Pop()  
  29.        {  
  30.            lock (m_pool)  
  31.            {  
  32.                return m_pool.Pop();  
  33.            }  
  34.        }  
  35.   
  36.        //对象个数  
  37.        public int Count  
  38.        {  
  39.            get { return m_pool.Count; }  
  40.        }  
  41.   
  42.    }  

3.封装UserToken

[csharp] view plain copy
 print?
  1. //这个类是将UserToken进行了再次封装,MSDN对于异步Socket的介绍中总会提到:  
  2. //若在异步回调中需要查询更多的信息,则应该建立一个小型类来管理回调时传递的Object对象  
  3. //UserToken其实就是那个传递的参数,AsyncUserToken就是对UserToken的封装,建立的小型类  
  4.   
  5. public class AsyncUserToken  
  6. {  
  7.     private Socket socket;  
  8.     public Socket Socket  
  9.     {  
  10.         get { return this.socket; }  
  11.         set { this.socket = value; }  
  12.     }  
  13.     //自定义的一些内容  
  14.     private string id;  
  15.     public string ID  
  16.     {  
  17.         get { return this.id; }  
  18.         set { this.id = value; }  
  19.     }  
  20.       
  21.     private string name;  
  22.     public string Name  
  23.     {  
  24.         get { return this.name; }  
  25.         set { this.name = value; }  
  26.     }  
  27.   
  28.   
  29. }  

4.具体组装以及实现

初始化:

[csharp] view plain copy
 print?
  1. //通过预分配可用的缓冲区和上下文来对象初始化服务器  
  2.         //这些上下文对象大可不必在此预分配,它也暂时不会用到  
  3.         //但是这么去创建可用的上下文对象会提高服务器的性能(避免局部多次new)  
  4.         public void Init()  
  5.         {     
  6.         //分配一大块内存,每一个I/O操作只用其中的一小段  
  7.         //这将有效地防止内存碎片化  
  8.         m_bufferManager.InitBuffer();  
  9.   
  10.         // 预分配异步操作池---读、写池  
  11.         SocketAsyncEventArgs receiveArgs;  
  12.         SocketAsyncEventArgs sendArgs;  
  13.         for (int i = 0; i < m_numConnections; i++)  
  14.         {  
  15.             //接收  
  16.             receiveArgs = new SocketAsyncEventArgs();  
  17.             receiveArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceiveCompleted);  
  18.             receiveArgs.UserToken = new AsyncUserToken();  
  19.             m_bufferManager.SetBuffer(receiveArgs);  
  20.             receiveArgsPool.Push(receiveArgs);  
  21.             //发送  
  22.             sendArgs = new SocketAsyncEventArgs();  
  23.             sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);  
  24.             sendArgs.UserToken = new AsyncUserToken();  
  25.             m_bufferManager.SetBuffer(sendArgs);               
  26.             sendArgsPool.Push(sendArgs);  
  27.         }  
  28.   
  29.         }  

监听:

[csharp] view plain copy
 print?
  1. //构造服务器时就开始启动监听  
  2.        public void Start(IPEndPoint localEndPoint)  
  3.        {  
  4.            listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);  
  5.            listenSocket.Bind(localEndPoint);           
  6.            listenSocket.Listen(100);  
  7.            StartAccept(null);  
  8.            Console.WriteLine("请按任意键结束服务....");  
  9.            Console.ReadKey();  
  10.        }  
  11.   
  12.        //开始接收连接请求  
  13.        //当有数据时将会用上下文对象  
  14.        public void StartAccept(SocketAsyncEventArgs acceptEventArg)  
  15.        {  
  16.            if (acceptEventArg == null)  
  17.            {  
  18.                acceptEventArg = new SocketAsyncEventArgs();  
  19.                acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);  
  20.                  
  21.            }  
  22.            else  
  23.            {  
  24.                //AcceptSocket必须每次都要清空,不然将会报错  
  25.                acceptEventArg.AcceptSocket = null;  
  26.            }  
  27.            m_maxNumberAcceptedClients.WaitOne();//超过所连接客户端的数量后,将阻塞Accept操作  
  28.            try  
  29.            {  
  30.                bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);  
  31.                if (!willRaiseEvent)  
  32.                {  
  33.                    ProcessAccept(acceptEventArg);  
  34.                }  
  35.            }  
  36.            catch (Exception e)  
  37.            {  
  38.            }  
  39.        }  

当监听中有数据时则进入以下事件:

 

[csharp] view plain copy
 print?
  1. //这个方法是与Socket.AcceptAsync操作有关的回调方法  
  2.        //当一个accept操作完成后该方法将被调用  
  3.        void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)  
  4.        {  
  5.            ProcessAccept(e);  
  6.        }  
这个事件只和第一个startaccept中建立的对象绑定,也就说它只响应的是accept操作,这一点有点像Select模型中只有一个对象的情形。只是Select模型中当加入集合后一并处理,而这个模型是accept永远单独处理,剩下的操作自己做各种处理,觉得层次更清晰。

[csharp] view plain copy
 print?
  1. private void ProcessAccept(SocketAsyncEventArgs e)  
  2.        {  
  3.            try  
  4.            {  
  5.                Interlocked.Increment(ref m_numConnectedSockets);  
  6.                Console.WriteLine("Client connection accepted. There are {0} clients connected to the server",  
  7.                    m_numConnectedSockets);  
  8.                //获取接收到的客户端,并将其放入对应的上下文对象的UserToken里  
  9.                SocketAsyncEventArgs receiveArgs = receiveArgsPool.Pop();  
  10.                AsyncUserToken token1 = receiveArgs.UserToken as AsyncUserToken;  
  11.                token1.Socket = e.AcceptSocket;  
  12.   
  13.                SocketAsyncEventArgs sendArgs = sendArgsPool.Pop();  
  14.                AsyncUserToken token2 = sendArgs.UserToken as AsyncUserToken;  
  15.                token2.Socket = e.AcceptSocket;  
  16.                
  17.                lock (lockReceiveArgsList)  
  18.                {  
  19.                    receiveArgsList.Add(receiveArgs);  
  20.   
  21.                }  
  22.                lock (lockSendArgsList)  
  23.                {  
  24.                    sendArgsList.Add(sendArgs);  
  25.                }  
  26.   
  27.                //首先读一次,看有数据没  
  28.                bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(receiveArgs);  
  29.                if (!willRaiseEvent)  
  30.                {  
  31.                    ProcessReceive(receiveArgs);  
  32.                }  
  33.                //递归,接受下一次的请求  
  34.                StartAccept(e);  
  35.            }  
  36.            catch (Exception ex)  
  37.            {  
  38.                throw ex;  
  39.            }  
  40.   
  41.        }  
receive事件:

[csharp] view plain copy
 print?
  1. //接收数据成功回调事件  
  2.      void OnReceiveCompleted(object sender, SocketAsyncEventArgs e)  
  3.      {                     
  4.        ProcessReceive(e);                                       
  5.      }  

[csharp] view plain copy
 print?
  1. private void ProcessReceive(SocketAsyncEventArgs e)  
  2.        {  
  3.            try  
  4.            {  
  5.                // 查看客户端是否已经断掉  
  6.                AsyncUserToken token = (AsyncUserToken)e.UserToken;  
  7.                if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)  
  8.                {  
  9.                    //原子性的计算总接收数据量  
  10.                    Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);  
  11.                    Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead);  
  12.                        bool willRaiseEvent = token.Socket.ReceiveAsync(e);  
  13.                        if (!willRaiseEvent)  
  14.                        {  
  15.                            ProcessReceive(e);  
  16.                        }  
  17.                    }  
  18.                }  
  19.                else  
  20.                {  
  21.                    CloseClientSocket(e);  
  22.                }  
  23.            }  
  24.            catch (Exception ex)  
  25.            {  
  26.                throw ex;  
  27.            }  
  28.        }  

总结:

该模型将socket的操作封装成了事件,并且解决了局部多次new缓冲区导致内存碎片化的尴尬。不过值得注意一点的是,在上面我生成了2个socket,一个发送一个接收,是因为此模型的双工需要建立两个对象。比如MSDN上的例子是服务器收到客户端的信息后直接返回给该客户端,这没问题,因为是C--->S,S---->C的形式。而如果现在有两个客户端C1,C2,若C1--->S,S---->C2则会报错“该套接字实例已经在进行异步操作了”,想想也是,当C1--->S时,S的LastOperation处于Receive状态,这是一个Comleted事件,此时该委托并没有执行完毕,若S--->C2则肯定会报错,为了不影响该线程,另建立一个专门发送的对象去执行发送,虽然它也绑定了Comleted事件,但是对象已经不同了,更不存在并发和线程冲突问题了。对于官方给出的模型还是有很多问题的,比如很多示例都有FreeBuffer的操作,在里面将Buffer清楚,但是当新客户端上来的时候,若分配了此对象,那它连缓冲区都没有,如何Receive呢,肯定会报错的,所以我觉得没有必要FreeBuffer,以前该对象在Buffer中的子区域是什么样,丢弃入栈的时候还是什么样,OffSet永远不变,变的只是Count.

0 0
原创粉丝点击