C# High-performance and large-capacity SOCKET concurrency (5): sticking, sub-packaging, and unpacking

sticky bag

Using TCP long connection will introduce the problem of sticky packets. Sticky packets refer to the fact that several packets of data sent by the sender are glued into one packet when received by the receiver. From the perspective of the receiving buffer, the header of the next packet of data is immediately followed by the previous packet of data. tail. Sticky packets can be caused by the sender or by the receiver. In order to improve the transmission efficiency of TCP, the sender often needs to collect enough data before sending a packet of data, resulting in the adhesion of multiple data packets. If the receiving process does not receive the data in time, the received data is placed in the system receive buffer, and the user process may read multiple data packets at the same time when reading the data.

The general solution to sticky packets is to formulate a communication protocol, which specifies how to unpack and unpack.

subcontract

In the NETIOCPDemo example program, the logic of our subcontracting is to send a length first, followed by the content of the data packet, so that each packet can be separated.

The application layer packet format is as follows:

Application Layer Packet Format  
Packet length Len: Cardinal (4-byte unsigned integer) Packet content, length Len
The main code of AsyncSocketInvokeElement subcontracting processing, the data we receive is processed in the ProcessReceive method, the processing method is to store the received data in the buffer array, and then take the first 4 bytes as the length, if the rest If the number of bytes is greater than or equal to the length, a complete package will be fetched, and subsequent logical processing will be carried out. If the fetched package is not enough, it will not be processed and will wait for subsequent packets to be received. The specific code is as follows:
[csharp] view plain copy
print ?
  1. public virtual bool  ProcessReceive( byte [] buffer,  int  offset,  int  count)  //Receive data returned by asynchronous events for caching and packetizing data    
  2. {  
  3.     m_activeDT = DateTime.UtcNow;  
  4.     DynamicBufferManager receiveBuffer = m_asyncSocketUserToken.ReceiveBuffer;  
  5.   
  6.     receiveBuffer.WriteBuffer(buffer, offset, count);  
  7.     if (receiveBuffer.DataCount > sizeof(int))  
  8.     {  
  9.         // package by length  
  10.         int  packetLength = BitConverter.ToInt32(receiveBuffer.Buffer, 0);  //Get the packet length  
  11.         if (NetByteOrder)  
  12.             packetLength = System.Net.IPAddress.NetworkToHostOrder(packetLength);  // Convert network byte order to local byte order  
  13.   
  14.   
  15.         if  ((packetLength > 10 * 1024 * 1024) | (receiveBuffer.DataCount > 10 * 1024 * 1024))  //Maximum Buffer exception protection  
  16.             returnfalse;   
  17.   
  18.         if ((receiveBuffer.DataCount - sizeof(int)) >= packetLength) //收到的数据达到包长度  
  19.         {  
  20.             bool result = ProcessPacket(receiveBuffer.Buffer, sizeof(int), packetLength);  
  21.             if (result)  
  22.                 receiveBuffer.Clear(packetLength + sizeof(int)); //从缓存中清理  
  23.             return result;  
  24.         }  
  25.         else  
  26.         {  
  27.             return true;  
  28.         }  
  29.     }  
  30.     else  
  31.     {  
  32.         return true;  
  33.     }  
  34. }  
        public virtual bool ProcessReceive(byte[] buffer, int offset, int count) //接收异步事件返回的数据,用于对数据进行缓存和分包
        {
            m_activeDT = DateTime.UtcNow;
            DynamicBufferManager receiveBuffer = m_asyncSocketUserToken.ReceiveBuffer;

            receiveBuffer.WriteBuffer(buffer, offset, count);
            if (receiveBuffer.DataCount > sizeof(int))
            {
                //按照长度分包
                int packetLength = BitConverter.ToInt32(receiveBuffer.Buffer, 0); //获取包长度
                if (NetByteOrder)
                    packetLength = System.Net.IPAddress.NetworkToHostOrder(packetLength); //把网络字节顺序转为本地字节顺序


                if ((packetLength > 10 * 1024 * 1024) | (receiveBuffer.DataCount > 10 * 1024 * 1024)) //最大Buffer异常保护
                    return false;

                if ((receiveBuffer.DataCount - sizeof(int)) >= packetLength) //收到的数据达到包长度
                {
                    bool result = ProcessPacket(receiveBuffer.Buffer, sizeof(int), packetLength);
                    if (result)
                        receiveBuffer.Clear(packetLength + sizeof(int)); //clean from buffer
                    return result;
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return true;
            }
        }
unpack

Since our application layer data packets can transmit both commands and data, we unpack each packet, and separate the command and data for separate processing. Therefore, each Socket service object needs to be unpacked. The logic of our unpacking is In ProcessPacket, the packet format of commands and data is:

Command length Len: Cardinal (4-byte unsigned integer) Order data
[csharp] view plain copy
print ?
  1. public virtual bool  ProcessPacket( byte [] buffer,  int  offset,  int  count)  //Process the packetized data, separate the command from the data, and parse the command    
  2. {  
  3.     if (count < sizeof(int))  
  4.         return false;  
  5.     int commandLen = BitConverter.ToInt32(buffer, offset); //取出命令长度  
  6.     string tmpStr = Encoding.UTF8.GetString(buffer, offset + sizeof(int), commandLen);  
  7.     if (!m_incomingDataParser.DecodeProtocolText(tmpStr)) //解析命令  
  8.       return false;  
  9.   
  10.     return ProcessCommand(buffer, offset + sizeof(int) + commandLen, count - sizeof(int) - commandLen); //处理命令  
  11. }  
        public virtual bool ProcessPacket(byte[] buffer, int offset, int count) //处理分完包后的数据,把命令和数据分开,并对命令进行解析
        {
            if (count < sizeof(int))
                return false;
            int commandLen = BitConverter.ToInt32(buffer, offset); //取出命令长度
            string tmpStr = Encoding.UTF8.GetString(buffer, offset + sizeof(int), commandLen);
            if (!m_incomingDataParser.DecodeProtocolText(tmpStr)) //解析命令
              return false;

            return ProcessCommand(buffer, offset + sizeof(int) + commandLen, count - sizeof(int) - commandLen); //处理命令
        }
每个包中包含多个协议关键字,每个协议关键字用回车换行分开,因此我们需要调用文本分开函数,然后针对每条命令解析出关键字和值,具体代码在IncomingDataParser.DecodeProtocolText如下:
[csharp] view plain copy
print ?
  1. public bool DecodeProtocolText(string protocolText)  
  2. {  
  3.     m_header = "";  
  4.     m_names.Clear();  
  5.     m_values.Clear();  
  6.     int speIndex = protocolText.IndexOf(ProtocolKey.ReturnWrap);  
  7.     if (speIndex < 0)  
  8.     {  
  9.         return false;  
  10.     }  
  11.     else  
  12.     {  
  13.         string[] tmpNameValues = protocolText.Split(new string[] { ProtocolKey.ReturnWrap }, StringSplitOptions.RemoveEmptyEntries);  
  14.         if (tmpNameValues.Length < 2) //每次命令至少包括两行  
  15.             return false;  
  16.         for (int i = 0; i < tmpNameValues.Length; i++)  
  17.         {  
  18.             string[] tmpStr = tmpNameValues[i].Split(new string[] { ProtocolKey.EqualSign }, StringSplitOptions.None);  
  19.             if (tmpStr.Length > 1) //存在等号  
  20.             {  
  21.                 if (tmpStr.Length > 2) //超过两个等号,返回失败  
  22.                     return false;  
  23.                 if (tmpStr[0].Equals(ProtocolKey.Command, StringComparison.CurrentCultureIgnoreCase))  
  24.                 {  
  25.                     m_command = tmpStr[1];  
  26.                 }  
  27.                 else  
  28.                 {  
  29.                     m_names.Add(tmpStr[0].ToLower());  
  30.                     m_values.Add(tmpStr[1]);  
  31.                 }  
  32.             }  
  33.         }  
  34.         return true;  
  35.     }  
  36. }  
        public bool DecodeProtocolText(string protocolText)
        {
            m_header = "";
            m_names.Clear();
            m_values.Clear();
            int speIndex = protocolText.IndexOf(ProtocolKey.ReturnWrap);
            if (speIndex < 0)
            {
                return false;
            }
            else
            {
                string[] tmpNameValues = protocolText.Split(new string[] { ProtocolKey.ReturnWrap }, StringSplitOptions.RemoveEmptyEntries);
                if (tmpNameValues.Length < 2) //每次命令至少包括两行
                    return false;
                for (int i = 0; i < tmpNameValues.Length; i++)
                {
                    string[] tmpStr = tmpNameValues[i].Split(new string[] { ProtocolKey.EqualSign }, StringSplitOptions.None);
                    if (tmpStr.Length > 1) //存在等号
                    {
                        if (tmpStr.Length > 2) //超过两个等号,返回失败
                            return false;
                        if (tmpStr[0].Equals(ProtocolKey.Command, StringComparison.CurrentCultureIgnoreCase))
                        {
                            m_command = tmpStr[1];
                        }
                        else
                        {
                            m_names.Add(tmpStr[0].ToLower());
                            m_values.Add(tmpStr[1]);
                        }
                    }
                }
                return true;
            }
        }
process command

After parsing the commands, each command needs to be processed. Each protocol implementation class inherits from AsyncSocketInvokeElement.ProcessCommand, and then writes its own protocol processing logic. For example, the throughput test protocol logic implementation code is as follows:

[csharp] view plain copy
print ?
  1. namespace SocketAsyncSvr  
  2. {  
  3.     class ThroughputSocketProtocol : BaseSocketProtocol  
  4.     {  
  5.         public ThroughputSocketProtocol(AsyncSocketServer asyncSocketServer, AsyncSocketUserToken asyncSocketUserToken)  
  6.             : base(asyncSocketServer, asyncSocketUserToken)  
  7.         {  
  8.             m_socketFlag = "Throughput";  
  9.         }  
  10.   
  11.         public override void Close()  
  12.         {  
  13.             base.Close();  
  14.         }  
  15.   
  16.         public override bool ProcessCommand(byte[] buffer, int offset, int count) //处理分完包的数据,子类从这个方法继承  
  17.         {  
  18.             ThroughputSocketCommand command = StrToCommand(m_incomingDataParser.Command);  
  19.             m_outgoingDataAssembler.Clear();  
  20.             m_outgoingDataAssembler.AddResponse();  
  21.             m_outgoingDataAssembler.AddCommand(m_incomingDataParser.Command);  
  22.             if (command == ThroughputSocketCommand.CyclePacket)  
  23.                 return DoCyclePacket(buffer, offset, count);  
  24.             else  
  25.             {  
  26.                 Program.Logger.Error("Unknow command: " + m_incomingDataParser.Command);  
  27.                 return false;  
  28.             }  
  29.         }  
  30.   
  31.         public ThroughputSocketCommand StrToCommand(string command)  
  32.         {  
  33.             if (command.Equals(ProtocolKey.CyclePacket, StringComparison.CurrentCultureIgnoreCase))  
  34.                 return ThroughputSocketCommand.CyclePacket;  
  35.             else  
  36.                 return ThroughputSocketCommand.None;  
  37.         }  
  38.   
  39.         public bool DoCyclePacket(byte[] buffer, int offset, int count)  
  40.         {  
  41.             int cycleCount = 0;  
  42.             if (m_incomingDataParser.GetValue(ProtocolKey.Count, ref cycleCount))  
  43.             {  
  44.                 m_outgoingDataAssembler.AddSuccess();  
  45.                 cycleCount = cycleCount + 1;  
  46.                 m_outgoingDataAssembler.AddValue(ProtocolKey.Count, cycleCount);  
  47.             }  
  48.             else  
  49.                 m_outgoingDataAssembler.AddFailure(ProtocolCode.ParameterError, "");  
  50.             return DoSendResult(buffer, offset, count);  
  51.         }  
  52.     }  
  53. }  
namespace SocketAsyncSvr
{
    class ThroughputSocketProtocol : BaseSocketProtocol
    {
        public ThroughputSocketProtocol(AsyncSocketServer asyncSocketServer, AsyncSocketUserToken asyncSocketUserToken)
            : base(asyncSocketServer, asyncSocketUserToken)
        {
            m_socketFlag = "Throughput";
        }

        public override void Close()
        {
            base.Close();
        }

        public override bool ProcessCommand(byte[] buffer, int offset, int count) //处理分完包的数据,子类从这个方法继承
        {
            ThroughputSocketCommand command = StrToCommand(m_incomingDataParser.Command);
            m_outgoingDataAssembler.Clear();
            m_outgoingDataAssembler.AddResponse();
            m_outgoingDataAssembler.AddCommand(m_incomingDataParser.Command);
            if (command == ThroughputSocketCommand.CyclePacket)
                return DoCyclePacket(buffer, offset, count);
            else
            {
                Program.Logger.Error("Unknow command: " + m_incomingDataParser.Command);
                return false;
            }
        }

        public ThroughputSocketCommand StrToCommand(string command)
        {
            if (command.Equals(ProtocolKey.CyclePacket, StringComparison.CurrentCultureIgnoreCase))
                return ThroughputSocketCommand.CyclePacket;
            else
                return ThroughputSocketCommand.None;
        }

        public bool DoCyclePacket(byte[] buffer, int offset, int count)
        {
            int cycleCount = 0;
            if (m_incomingDataParser.GetValue(ProtocolKey.Count, ref cycleCount))
            {
                m_outgoingDataAssembler.AddSuccess();
                cycleCount = cycleCount + 1;
                m_outgoingDataAssembler.AddValue(ProtocolKey.Count, cycleCount);
            }
            else
                m_outgoingDataAssembler.AddFailure(ProtocolCode.ParameterError, "");
            return DoSendResult(buffer, offset, count);
        }
    }
}
DEMO download address: http://download.csdn.net/detail/sqldebug_fan/7467745
Disclaimer: This code is only to demonstrate C# completion port programming, only for learning and research, not for commercial use. 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=325989810&siteId=291194637