Maison  >  Article  >  développement back-end  >  Exemples de résolution de problèmes avec les paquets persistants TCP en C#

Exemples de résolution de problèmes avec les paquets persistants TCP en C#

黄舟
黄舟original
2017-07-17 11:06:152556parcourir

Cet article présente principalement en détail la solution au problème de collage TCP en C#, qui a une certaine valeur de référence. Les amis intéressés peuvent s'y référer

1. 🎜>

1. Le paquet collant TCP signifie que plusieurs paquets de données envoyés par l'expéditeur sont bloqués dans un seul paquet lorsque le destinataire le reçoit du tampon de réception, l'en-tête de ce dernier paquet de données suit immédiatement. fin du paquet de données précédent. Il existe de nombreuses raisons au phénomène collant. Il peut être provoqué par l'expéditeur ou le destinataire.

2. Le paquet collant provoqué par l'expéditeur est causé par le protocole TCP lui-même. Afin d'améliorer l'efficacité de la transmission TCP, l'expéditeur doit souvent collecter suffisamment de données avant d'envoyer un paquet de données. Si les données envoyées plusieurs fois de suite sont très petites, TCP combinera généralement les données en un seul paquet selon l'algorithme d'optimisation et l'enverra en même temps, afin que le récepteur reçoive les données du paquet persistant. Le paquet collant provoqué par le récepteur est dû au fait que le processus utilisateur du récepteur ne reçoit pas les données à temps, ce qui conduit au phénomène de paquet collant.

3. En effet, le récepteur place d'abord les données reçues dans le tampon de réception du système, et le processus utilisateur extrait les données du tampon. Si le paquet de données suivant arrive, le paquet de données précédent ne l'est pas. a été reçu par le processus utilisateur. Retirez-le, puis le paquet de données suivant est placé dans le tampon de réception du système après avoir reçu le paquet de données précédent, et le processus utilisateur récupère les données du tampon de réception du système en fonction de la taille du tampon prédéfinie, afin que plusieurs paquets soient récupérés en même temps. ,

2. Principe de la solution et implémentation du code

1. Utiliser l'en-tête (longueur fixe, qui contient la longueur du corps du colis, obtenue dynamiquement lors de l'envoi) + mécanisme de transmission du corps du colis. Comme le montre la figure

HeaderSize stocke la longueur du corps du paquet, et HeaderSize lui-même est une longueur fixe de 4 octets

Une donnée complète ; packet (L) = HeaderSize+BodySize;

2. Algorithme de sous-packaging

L'idée de base est de convertir d'abord de force le flux de données reçu à traiter, c'est-à-dire les données du tampon système. (longueur définie sur M) dans une forme de données de structure prédéterminée, et en extrayez le champ de longueur de données structurelles L, puis calculez la longueur de données du premier paquet en fonction de l'en-tête du paquet.

M=taille du tampon système ; L=paquet de données envoyé par l'utilisateur=HeaderSize+BodySize;

1) Si L2c8b03e7ee5eb7a58cc6c67f725fb8b8M, cela signifie que le contenu du flux de données n'est pas suffisant pour former des données structurées complètes et doit être fusionné avec le prochain paquet de données. données avant leur traitement.

4) Ce qui suit est l'implémentation du code (le côté serveur du framework HP-SOCKET pour recevoir des données)

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;
  }
À l'heure actuelle, le déballage et l'analyse du texte sont terminés. Travail. Mais ce n'est pas encore terminé. Si ce code permet au client de recevoir des données du serveur, tout ira bien.

Examinez attentivement l'ID de session IntPtr connId de chaque connexion

private HandleResult OnReceive(IntPtr connId, byte[] bytes)
{
}
Mais le côté serveur doit également distinguer de quelle session chaque paquet de données est généré, car le côté serveur est multi- threadé et multi-thread En mode utilisateur, le premier paquet et le second peuvent provenir de sessions différentes, le code ci-dessus ne fonctionne donc qu'en mode session unique.

Je vais résoudre ce problème maintenant.

Utilisation de c#

safeConcurrentDictionnaire,

le dernier 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;
  }
Cela résout le problème, beaucoup de réception confusion causée par la session client. À ce stade, tous les travaux sont terminés. Le code ci-dessus est juste à titre de référence et d’apprentissage, si vous ne voulez vraiment pas vous donner autant de problèmes. Vous pouvez directement utiliser le modèle PACK

du framework de communication HP-SOCKET, qui résout automatiquement le problème des paquets collants.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn