Heim  >  Artikel  >  Backend-Entwicklung  >  Beispiele für die Lösung von Problemen mit TCP-Sticky-Paketen in C#

Beispiele für die Lösung von Problemen mit TCP-Sticky-Paketen in C#

黄舟
黄舟Original
2017-07-17 11:06:152541Durchsuche

Dieser Artikel stellt hauptsächlich die Lösung des TCP-Sticking-Problems in C# vor, die einen gewissen Referenzwert hat

Das Prinzip des TCP-Stickings

1. TCP-Sticky-Paket bedeutet, dass mehrere vom Sender gesendete Datenpakete in einem Paket stecken bleiben, wenn der Empfänger es empfängt. Der Header des letzteren Datenpakets folgt sofort Ende des vorherigen Datenpakets. Es gibt viele Gründe für das Phänomen der Sticky Packets. Es kann sowohl beim Absender als auch beim Empfänger liegen.

2. Das vom Absender verursachte Sticky Packet wird durch das TCP-Protokoll selbst verursacht. Um die Übertragungseffizienz von TCP zu verbessern, muss der Absender häufig genügend Daten sammeln, bevor er ein Datenpaket sendet. Wenn die mehrmals hintereinander gesendeten Daten sehr klein sind, kombiniert TCP die Daten normalerweise gemäß dem Optimierungsalgorithmus zu einem Paket und sendet es sofort aus, sodass der Empfänger die Sticky-Paketdaten empfängt. Das vom Empfänger verursachte Sticky-Paket ist darauf zurückzuführen, dass der Benutzerprozess des Empfängers die Daten nicht rechtzeitig empfängt, was zum Phänomen des Sticky-Pakets führt.

3. Dies liegt daran, dass der Empfänger die empfangenen Daten zuerst in den Empfangspuffer des Systems legt und der Benutzerprozess die Daten aus dem Puffer entnimmt. Wenn das nächste Datenpaket eintrifft, ist dies beim vorherigen Datenpaket nicht der Fall Wenn es vom Benutzerprozess empfangen wurde, wird das nächste Datenpaket nach dem Empfang des vorherigen Datenpakets im Systemempfangspuffer abgelegt, und der Benutzerprozess ruft Daten entsprechend der voreingestellten Puffergröße aus dem Systemempfangspuffer ab. so dass mehrere Datenpakete gleichzeitig abgerufen werden. ,

2. Lösungsprinzip und Code-Implementierung

1. Verwenden Sie den Header (feste Länge, die die Länge des Paketkörpers enthält, die beim Senden dynamisch erhalten wird) + Übertragungsmechanismus des Paketkörpers. Wie in der Abbildung gezeigt

HeaderSize speichert die Länge des Paketkörpers und die HeaderSize selbst hat eine feste Länge von 4 Bytes

Eine vollständige Datenmenge packet (L) = HeaderSize+BodySize;

2. Subpackaging-Algorithmus

Die Grundidee besteht darin, zunächst den zu verarbeitenden empfangenen Datenstrom, also die Systempufferdaten (Länge), zwangsweise umzuwandeln auf M gesetzt) ​​in eine vorgegebene Strukturdatenform umwandeln, das Strukturdatenlängenfeld L daraus extrahieren und dann die erste Paketdatenlänge basierend auf dem Paketheader berechnen.

M=Systempuffergröße; L=vom Benutzer gesendetes Datenpaket=HeaderSize+BodySize;

1) Wenn La8cd2e114c5cf19d08fe2f9cdd02d5c0M, bedeutet dies, dass der Inhalt des Datenstroms nicht ausreicht, um vollständige strukturierte Daten zu bilden, und mit dem nächsten Paket zusammengeführt werden muss Daten vor der Verarbeitung.

4) Das Folgende ist die Code-Implementierung (die Serverseite des HP-SOCKET-Frameworks zum Empfangen von Daten)

int headSize = 4;//包头长度 固定4
  byte[] surplusBuffer = null;//不完整的数据包,即用户自定义缓冲区
  /// <summary>
  /// 接收客户端发来的数据
  /// </summary>
  /// <param name="connId">每个客户的会话ID</param>
  /// <param name="bytes">缓冲区数据</param>
  /// <returns></returns>
  private HandleResult OnReceive(IntPtr connId, byte[] bytes) 
  {
   //bytes 为系统缓冲区数据
   //bytesRead为系统缓冲区长度
   int bytesRead = bytes.Length;
   if (bytesRead > 0)
   {
    if (surplusBuffer == null)//判断是不是第一次接收,为空说是第一次
     surplusBuffer = bytes;//把系统缓冲区数据放在自定义缓冲区里面
    else
     surplusBuffer = surplusBuffer.Concat(bytes).ToArray();//拼接上一次剩余的包
    //已经完成读取每个数据包长度
    int haveRead = 0;
    //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
    int totalLen = surplusBuffer.Length;
    while (haveRead <= totalLen)
    {
     //如果在N此拆解后剩余的数据包连一个包头的长度都不够
     //说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包
     if (totalLen - haveRead < headSize)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      //把剩下不够一个完整的数据包存起来
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      surplusBuffer = byteSub;
      totalLen = 0;
      break;
     }
     //如果够了一个完整包,则读取包头的数据
     byte[] headByte = new byte[headSize];
     Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节
     int bodySize = BitConverter.ToInt32(headByte, 0);//从包头里面分析出包体的长度

     //这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N
     //如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存
     if (haveRead + headSize + bodySize > totalLen)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      surplusBuffer = byteSub;
      break;
     }
     else
     {
      //挨个分解每个包,解析成实际文字
      String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
      //AddMsg(string.Format(" > [OnReceive] -> {0}", strc));
      //依次累加当前的数据包的长度
      haveRead = haveRead + headSize + bodySize;
      if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
      {
       surplusBuffer = null;//设置空 回到原始状态
       totalLen = 0;//清0
      }
     }
    }
   }
   return HandleResult.Ok;
  }

Zu diesem Zeitpunkt Das Entpacken und Parsen des Textes ist abgeschlossen. Aber es ist noch nicht wirklich fertig. Wenn dieser Code dafür vorgesehen ist, dass der Client Daten vom Server empfängt, ist das in Ordnung.

Sehen Sie sich die IntPtr-ConnId-Sitzungs-ID jeder Verbindung sorgfältig an

private HandleResult OnReceive(IntPtr connId, byte[] bytes)
{
}

Aber die Serverseite muss auch unterscheiden, aus welcher Sitzung jedes Datenpaket generiert wird, da die Serverseite mehrere Threaded und Multithreaded Im Benutzermodus können das erste und das zweite Paket aus unterschiedlichen Sitzungen stammen, sodass der obige Code nur im Einzelsitzungsmodus funktioniert.

Ich werde dieses Problem jetzt lösen.

Verwenden von c#sicherConaktuellWörterbuch,

neuester Code

//线程安全的字典
  ConcurrentDictionary<IntPtr, byte[]> dic = new ConcurrentDictionary<IntPtr, byte[]>();
  int headSize = 4;//包头长度 固定4
  /// <summary>
  /// 接收客户端发来的数据
  /// </summary>
  /// <param name="connId">每个客户的会话ID</param>
  /// <param name="bytes">缓冲区数据</param>
  /// <returns></returns>
  private HandleResult OnReceive(IntPtr connId, byte[] bytes) 
  {
   //bytes 为系统缓冲区数据
   //bytesRead为系统缓冲区长度
   int bytesRead = bytes.Length;
   if (bytesRead > 0)
   {
    byte[] surplusBuffer = null;
    if (dic.TryGetValue(connId, out surplusBuffer))
    {
     byte[] curBuffer = surplusBuffer.Concat(bytes).ToArray();//拼接上一次剩余的包
     //更新会话ID 的最新字节
     dic.TryUpdate(connId, curBuffer, surplusBuffer);
     surplusBuffer = curBuffer;//同步
    }
    else
    {
     //添加会话ID的bytes
     dic.TryAdd(connId, bytes);
     surplusBuffer = bytes;//同步
    }

    //已经完成读取每个数据包长度
    int haveRead = 0;
    //这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
    int totalLen = surplusBuffer.Length;
    while (haveRead <= totalLen)
    {
     //如果在N此拆解后剩余的数据包连一个包头的长度都不够
     //说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包
     if (totalLen - haveRead < headSize)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      //把剩下不够一个完整的数据包存起来
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      dic.TryUpdate(connId, byteSub, surplusBuffer);
      surplusBuffer = byteSub;
      totalLen = 0;
      break;
     }
     //如果够了一个完整包,则读取包头的数据
     byte[] headByte = new byte[headSize];
     Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节
     int bodySize = BitConverter.ToInt32(headByte, 0);//从包头里面分析出包体的长度

     //这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N
     //如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存
     if (haveRead + headSize + bodySize > totalLen)
     {
      byte[] byteSub = new byte[totalLen - haveRead];
      Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
      dic.TryUpdate(connId, byteSub, surplusBuffer);
      surplusBuffer = byteSub;
      break;
     }
     else
     {
      //挨个分解每个包,解析成实际文字
      String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
      AddMsg(string.Format(" > {0}[OnReceive] -> {1}", connId, strc));
      //依次累加当前的数据包的长度
      haveRead = haveRead + headSize + bodySize;
      if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
      {
       byte[] xbtye=null;
       dic.TryRemove(connId, out xbtye);
       surplusBuffer = null;//设置空 回到原始状态
       totalLen = 0;//清0
      }
     }
    }
   }
   return HandleResult.Ok;
  }

Dies löst das Problem, viel Empfang Verwirrung durch Client-Sitzung. Zu diesem Zeitpunkt sind alle Arbeiten abgeschlossen. Der obige Code dient nur als Referenz und zum Lernen, wenn Sie sich wirklich nicht solche Mühe machen wollen. Sie können direkt das PACK-Modell des HP-SOCKET-Kommunikationsframeworks verwenden, das das Problem der Sticky-Pakete automatisch löst.

Das obige ist der detaillierte Inhalt vonBeispiele für die Lösung von Problemen mit TCP-Sticky-Paketen in C#. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn