C# High Performance Large Capacity SOCKET Concurrency (10): SocketAsyncEventArgs Thread Model

threading model

The SocketAsyncEventArgs programming mode does not support setting the number of simultaneous worker threads. The NET IO thread used is provided by the NET bottom layer, which is different from the direct use of the completion port API programming. NET underlying IO thread is also that each asynchronous event is returned to the Completed event by a different thread, so the user object needs to be locked in the Completed event to prevent the same user object from triggering two Completed events at the same time.

[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)  //Avoid multiple thread operations on the same userToken at the same time  
  8.         {  
  9.             if (asyncEventArgs.LastOperation == SocketAsyncOperation.Receive)  
  10.                 ProcessReceive(asyncEventArgs);  
  11.             elseif (asyncEventArgs.LastOperation == SocketAsyncOperation.Send)   
  12.                 ProcessSend(asyncEventArgs);  
  13.             else  
  14.                 thrownew 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) //Avoid multiple thread operations on the same userToken at the same time
                {
                    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);
            }                     
        }
Using ProceXP, you can see that when the server is busy, there will be more threads of the service. The advantage of locking in the Completed event is that the subsequent logic processing is serial, and thread synchronization can be ignored. Another point to note is to disconnect the timeout connection. Since the timeout connection will call the Shutdown function to forcibly interrupt the SOCKET, the userToken object also needs to be locked when the daemon thread is called.

[csharp] view plain copy
print ?
  1. publicvoid 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++)  //check every minute  
  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++) //check every minute
                {
                    if (!m_thread.IsAlive)
                        break;
                    Thread.Sleep(10);
                }
            }
        }
In the CloseClientSocket method, there is a lock when processing m_asyncSocketUserTokenPool and m_asyncSocketUserTokenList. The code is as follows:

[csharp] view plain copy
print ?
  1. publicvoid 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 //Release the reference and clean up the cache, including releasing resources such as protocol objects  
  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; //Release the reference and clean up the cache, including releasing resources such as protocol objects

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

[csharp] view plain copy
print ?
  1. publicvoid Push(AsyncSocketUserToken item)   
  2. {  
  3.     if (item == null)  
  4.     {  
  5.         thrownew 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. publicvoid 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);
            }
        }

In some systems with higher performance requirements, especially in some completion ports written in C++, atomic operations are used instead of locks. The advantage of this is that there is no need to switch between the system kernel and user mode, and the performance will be high. However, the technology is relatively partial, difficult to maintain, and the actual performance needs to be tested in many aspects. This kind of optimization recommends optimizing business logic and minimizing memory allocation and release.


DEMO download address: http://download.csdn.net/detail/sqldebug_fan/7467745
Disclaimer: This code is only to demonstrate C# complete port programming, only for learning and research, not for commercial purposes. The level is limited, and C# is also a beginner. Errors are inevitable. Corrections and guidance are welcome. Email address: [email protected].

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325993078&siteId=291194637