[セットトップ]C#高性能大容量SOCKET同時(五):粘着、分包、解包


ねんちゃく
TCP長接続を使用すると粘着パケットが導入されるという問題があり、粘着パケットとは、送信側から送信されたいくつかのパケットデータが受信側に受信されたときに1パケットに粘着し、受信バッファから見ると、後のパケットデータの先頭が前のパケットデータの末尾に続くことを意味する.粘着パケットは、送信者によって発生するか、受信者によって発生する可能性があります.TCPは伝送効率を向上させるため、送信側は十分なデータを収集してから1パケットのデータを送信し、複数のパケットの結合をもたらすことが多い.受信プロセスがタイムリーにデータを受信しない場合、受信したデータはシステム受信バッファに配置され、ユーザープロセスがデータを読み取ると同時に複数のパケットを読むことができる.
粘着パケットの一般的な解決策は,通信プロトコルを制定し,プロトコルによってパケットをどのように分割してパケットを解くかを規定することである.
下請け
NETIOCPDemoインスタンスプログラムでは、パケットを分割する論理は、まず1つの長さを送信し、次にパケットの内容を送信することで、各パケットを分離することができます.
アプリケーション層のパケット形式は次のとおりです.
アプリケーション層パケットフォーマット
 
パケット長Len:Cardinal(4バイト符号なし整数)
パケット内容、長さLen
AsyncSocketInvokeElementパケット処理プライマリコード、私たちが受け取ったデータはすべてProcessReceiveメソッドで処理され、処理方法は受け取ったデータをバッファ配列に格納し、最初の4バイトを長さとし、残りのバイト数が長さ以上であれば、完全なパケットを取り出し、後続の論理処理を行い、1つのパケットが足りない場合は処理せず、後続のパケットの受信を待つ.具体的なコードは以下の通りです.
        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)); // 
                    return result;
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return true;
            }
        }

かばんを解く
アプリケーション層のパケットはコマンドもデータも伝送できるので、各パケットに対して解パケットを行い、コマンドとデータを別々に処理するので、各Socketサービスオブジェクトは解パケットする必要があります.私たちの解パケットの論理はProcessPacketに置いて、コマンドとデータのパケットフォーマットは:
コマンド長Len:Cardinal(4バイト符号なし整数)
コマンド#コマンド#
データ#データ#
        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は次のとおりです.
        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;
            }
        }

プロセスコマンド
コマンドを解析すると、各コマンドを処理する必要があり、各プロトコル実装クラスはAsyncSocketInvokeElement.ProcessCommandは継承し、スループットのテストプロトコルロジック実装コードなど、それぞれのプロトコル処理ロジックを記述します.
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ダウンロードアドレス:
http://download.csdn.net/detail/sqldebug_fan/7467745
免責声明:このコードは、C#がポートプログラミングを完了したことを示すために使用され、学習と研究にのみ使用され、ビジネス用途には使用されません.レベルが限られていて、C#も初学に属して、間違いは避けられないので、指摘と指導を歓迎します.メールアドレス:[email protected].