C#高性能大容量SOCKET并发(十):SocketAsyncEventArgs线程模型

线程模型

SocketAsyncEventArgs编程模式不支持设置同时工作线程个数,使用的NET的IO线程,由NET底层提供,这点和直接使用完成端口API编程不同。NET底层IO线程也是每个异步事件都是由不同的线程返回到Completed事件,因此在Completed事件需要对用户对象进行加锁,避免同一个用户对象同时触发两个Completed事件。

[csharp] view plain copy
print ?
  1. void IO_Completed(object sender, SocketAsyncEventArgs asyncEventArgs)  
  2. {  
  3.     AsyncSocketUserToken userToken = asyncEventArgs.UserToken as AsyncSocketUserToken;  
  4.     userToken.ActiveDateTime = DateTime.Now;  
  5.     try  
  6.     {                  
  7.         lock (userToken) //避免同一个userToken同时有多个线程操作  
  8.         {  
  9.             if (asyncEventArgs.LastOperation == SocketAsyncOperation.Receive)  
  10.                 ProcessReceive(asyncEventArgs);  
  11.             else if (asyncEventArgs.LastOperation == SocketAsyncOperation.Send)  
  12.                 ProcessSend(asyncEventArgs);  
  13.             else  
  14.                 throw new ArgumentException("The last operation completed on the socket was not a receive or send");  
  15.         }     
  16.     }  
  17.     catch (Exception E)  
  18.     {  
  19.         Program.Logger.ErrorFormat("IO_Completed {0} error, message: {1}", userToken.ConnectSocket, E.Message);  
  20.         Program.Logger.Error(E.StackTrace);  
  21.     }                       
  22. }  
        void IO_Completed(object sender, SocketAsyncEventArgs asyncEventArgs)
        {
            AsyncSocketUserToken userToken = asyncEventArgs.UserToken as AsyncSocketUserToken;
            userToken.ActiveDateTime = DateTime.Now;
            try
            {                
                lock (userToken) //避免同一个userToken同时有多个线程操作
                {
                    if (asyncEventArgs.LastOperation == SocketAsyncOperation.Receive)
                        ProcessReceive(asyncEventArgs);
                    else if (asyncEventArgs.LastOperation == SocketAsyncOperation.Send)
                        ProcessSend(asyncEventArgs);
                    else
                        throw new ArgumentException("The last operation completed on the socket was not a receive or send");
                }   
            }
            catch (Exception E)
            {
                Program.Logger.ErrorFormat("IO_Completed {0} error, message: {1}", userToken.ConnectSocket, E.Message);
                Program.Logger.Error(E.StackTrace);
            }                     
        }
使用ProceXP可以看到服务端在比较忙的时候,服务的线程会越多。在Completed事件加锁有好处是后续逻辑处理都是串行的,可以不用考虑线程同步。还有一个地方需要注意的是断开超时连接,由于超时连接会调用Shutdown函数来强行中断SOCKET,因此在守护线程调用时,也需要锁住userToken对象。

[csharp] view plain copy
print ?
  1. public void DaemonThreadStart()  
  2. {  
  3.     while (m_thread.IsAlive)  
  4.     {  
  5.         AsyncSocketUserToken[] userTokenArray = null;  
  6.         m_asyncSocketServer.AsyncSocketUserTokenList.CopyList(ref userTokenArray);  
  7.         for (int i = 0; i < userTokenArray.Length; i++)  
  8.         {  
  9.             if (!m_thread.IsAlive)  
  10.                 break;  
  11.             try  
  12.             {  
  13.                 if ((DateTime.Now - userTokenArray[i].ActiveDateTime).Milliseconds > m_asyncSocketServer.SocketTimeOutMS) //超时Socket断开  
  14.                 {  
  15.                     lock (userTokenArray[i])  
  16.                     {  
  17.                         m_asyncSocketServer.CloseClientSocket(userTokenArray[i]);  
  18.                     }  
  19.                 }  
  20.             }                      
  21.             catch (Exception E)  
  22.             {  
  23.                 Program.Logger.ErrorFormat("Daemon thread check timeout socket error, message: {0}", E.Message);  
  24.                 Program.Logger.Error(E.StackTrace);  
  25.             }  
  26.         }  
  27.   
  28.         for (int i = 0; i < 60 * 1000 / 10; i++) //每分钟检测一次  
  29.         {  
  30.             if (!m_thread.IsAlive)  
  31.                 break;  
  32.             Thread.Sleep(10);  
  33.         }  
  34.     }  
  35. }  
        public void DaemonThreadStart()
        {
            while (m_thread.IsAlive)
            {
                AsyncSocketUserToken[] userTokenArray = null;
                m_asyncSocketServer.AsyncSocketUserTokenList.CopyList(ref userTokenArray);
                for (int i = 0; i < userTokenArray.Length; i++)
                {
                    if (!m_thread.IsAlive)
                        break;
                    try
                    {
                        if ((DateTime.Now - userTokenArray[i].ActiveDateTime).Milliseconds > m_asyncSocketServer.SocketTimeOutMS) //超时Socket断开
                        {
                            lock (userTokenArray[i])
                            {
                                m_asyncSocketServer.CloseClientSocket(userTokenArray[i]);
                            }
                        }
                    }                    
                    catch (Exception E)
                    {
                        Program.Logger.ErrorFormat("Daemon thread check timeout socket error, message: {0}", E.Message);
                        Program.Logger.Error(E.StackTrace);
                    }
                }

                for (int i = 0; i < 60 * 1000 / 10; i++) //每分钟检测一次
                {
                    if (!m_thread.IsAlive)
                        break;
                    Thread.Sleep(10);
                }
            }
        }
在CloseClientSocket方法中,对m_asyncSocketUserTokenPool和m_asyncSocketUserTokenList进行处理的时候都有加锁,代码如下:

[csharp] view plain copy
print ?
  1. public void CloseClientSocket(AsyncSocketUserToken userToken)  
  2. {  
  3.     if (userToken.ConnectSocket == null)  
  4.         return;  
  5.     string socketInfo = string.Format("Local Address: {0} Remote Address: {1}", userToken.ConnectSocket.LocalEndPoint,  
  6.         userToken.ConnectSocket.RemoteEndPoint);  
  7.     Program.Logger.InfoFormat("Client connection disconnected. {0}", socketInfo);  
  8.     try  
  9.     {  
  10.         userToken.ConnectSocket.Shutdown(SocketShutdown.Both);  
  11.     }  
  12.     catch (Exception E)   
  13.     {  
  14.         Program.Logger.ErrorFormat("CloseClientSocket Disconnect client {0} error, message: {1}", socketInfo, E.Message);  
  15.     }  
  16.     userToken.ConnectSocket.Close();  
  17.     userToken.ConnectSocket = null//释放引用,并清理缓存,包括释放协议对象等资源  
  18.   
  19.     m_maxNumberAcceptedClients.Release();  
  20.     m_asyncSocketUserTokenPool.Push(userToken);  
  21.     m_asyncSocketUserTokenList.Remove(userToken);  
  22. }  
        public void CloseClientSocket(AsyncSocketUserToken userToken)
        {
            if (userToken.ConnectSocket == null)
                return;
            string socketInfo = string.Format("Local Address: {0} Remote Address: {1}", userToken.ConnectSocket.LocalEndPoint,
                userToken.ConnectSocket.RemoteEndPoint);
            Program.Logger.InfoFormat("Client connection disconnected. {0}", socketInfo);
            try
            {
                userToken.ConnectSocket.Shutdown(SocketShutdown.Both);
            }
            catch (Exception E) 
            {
                Program.Logger.ErrorFormat("CloseClientSocket Disconnect client {0} error, message: {1}", socketInfo, E.Message);
            }
            userToken.ConnectSocket.Close();
            userToken.ConnectSocket = null; //释放引用,并清理缓存,包括释放协议对象等资源

            m_maxNumberAcceptedClients.Release();
            m_asyncSocketUserTokenPool.Push(userToken);
            m_asyncSocketUserTokenList.Remove(userToken);
        }

[csharp] view plain copy
print ?
  1. public void Push(AsyncSocketUserToken item)  
  2. {  
  3.     if (item == null)  
  4.     {  
  5.         throw new ArgumentException("Items added to a AsyncSocketUserToken cannot be null");  
  6.     }  
  7.     lock (m_pool)  
  8.     {  
  9.         m_pool.Push(item);  
  10.     }  
  11. }  
        public void Push(AsyncSocketUserToken item)
        {
            if (item == null)
            {
                throw new ArgumentException("Items added to a AsyncSocketUserToken cannot be null");
            }
            lock (m_pool)
            {
                m_pool.Push(item);
            }
        }

[csharp] view plain copy
print ?
  1. public void Remove(AsyncSocketUserToken userToken)  
  2. {  
  3.     lock (m_list)  
  4.     {  
  5.         m_list.Remove(userToken);  
  6.     }  
  7. }  
        public void Remove(AsyncSocketUserToken userToken)
        {
            lock (m_list)
            {
                m_list.Remove(userToken);
            }
        }

在有些性能要求更高的系统,特别是在一些C++写的完成端口中,会使用原子操作来代替锁,这样做的好处是不用进行系统内核和用户态切换,性能会高。不过技术比较偏门,不易维护,而且实际表现需要进行多方面测试,这类优化更建议优化业务逻辑,并尽量减少内存分配和释放。


DEMO下载地址:http://download.csdn.net/detail/sqldebug_fan/7467745
免责声明:此代码只是为了演示C#完成端口编程,仅用于学习和研究,切勿用于商业用途。水平有限,C#也属于初学,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]

猜你喜欢

转载自blog.csdn.net/andrewniu/article/details/80234551