ホームページ バックエンド開発 C#.Net チュートリアル C# での TCP スティッキー パケットの問題の解決例

C# での TCP スティッキー パケットの問題の解決例

Jul 17, 2017 am 11:06 AM
.net

この記事では主に C# における TCP スティッキー パケットの問題の解決策を詳しく紹介します。興味のある方は参考にしてください。

1 TCP スティッキー パケットの原理。送信者が送信した複数のデータ パケットが、受信者が受信バッファから受信したときに 1 つのパケットにまとめられ、次のデータ パケットの先頭が前のデータ パケットの末尾の直後に来ることを意味します。スティッキー パケットの現象には、送信者または受信者が原因である可能性があります。

2. 送信者によって発生するスティッキー パケットは、TCP プロトコル自体によって発生します。TCP の送信効率を向上させるために、送信者はデータ パケットを送信する前に十分なデータを収集する必要があります。連続して数回送信されるデータが非常に小さい場合、通常、TCP は最適化アルゴリズムに従ってデータを 1 つのパケットに結合して一度に送信し、受信側がスティッキー パケット データを受信できるようにします。受信側によってスティッキー パケットが発生するのは、受信側のユーザー プロセスがデータを時間内に受信しないことが原因で、スティッキー パケット現象が発生します。

3. これは、受信側が最初に受信したデータをシステムの受信バッファーに置き、次のパケットがユーザー プロセスによって前のデータ パケットが取り除かれていない場合、ユーザー プロセスがバッファーからデータを取得するためです。データの次のパケットがシステム受信バッファに配置されると、前のデータ パケットの後に受信され、ユーザー プロセスは事前に設定されたバッファ サイズに従ってシステム受信バッファからデータをフェッチし、次のデータ パケットを取得します。一度に複数のデータパケット。 ,

2. 解決原理とコード実装

1. パケットヘッダー(送信時に動的に取得される、パケットボディの長さを含む固定長)+パケットボディの送信メカニズムを使用します。図に示すように、

HeaderSize にはパケット本体の長さが格納され、HeaderSize 自体は 4 バイトの固定長です。

完全なデータ パケット (L) = HeaderSize+BodySize;アルゴリズム

基本的な考え方は、まず処理対象となる受信データストリーム、つまりシステムバッファデータ(長さをMとする)を所定の構造データ形式に強制変換し、その後構造データ長を取り出すことである。そこからフィールド L を抽出し、パケット ヘッダーに基づいて最初のパケットのデータ長を計算します。

M=システムバッファサイズ; L=ユーザーによって送信されたデータパケット=HeaderSize+BodySize;

1) L

2) L=M の場合、データ ストリームのコンテンツがたまたまデータの完全な構造である (つまり、ユーザー定義のバッファーがシステムの受信バッファー サイズと等しい) ことを意味します。一時バッファに直接保存できます。

3) L>M の場合、データ ストリームのコンテンツが完全な構造化データを形成するには十分ではなく、処理する前に次のデータ パケットとマージする必要があることを意味します。

4) コードの実装は次のとおりです (データの受信には HP-SOCKET フレームワークのサーバー側が使用されます)

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;
  }
ログイン後にコピー

この時点で、テキストの解凍と解析の作業は完了です。しかし、実際にはまだ完成していません。このコードがクライアントがサーバーからデータを受信するためのものであれば、問題ありません。

各接続の IntPtr connId セッション ID を注意深く確認してください

private HandleResult OnReceive(IntPtr connId, byte[] bytes)
{
}
ログイン後にコピー

ただし、サーバー側はマルチスレッド、マルチユーザー モードであるため、最初のデータが生成される各データ パケットがどのセッションであるかを区別する必要もあります。 2 番目のパケットは別のセッションからのデータである可能性があるため、上記のコードはシングル セッション モードでのみ機能します。

以下でこの問題を解決していきます。

c#

safe

Con

current

Dictionaryを使用して、

最新のコード

//线程安全的字典
  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;
  }
これにより、複数のクライアントセッションによって引き起こされる受信の混乱が解決されます。この時点ですべての作業は完了です。上記のコードは、本当に面倒な作業をしたくない場合の参照と学習用です。 HP-SOCKET 通信フレームワークの PACK モデル を直接使用でき、スティッキー パケットの問題を自動的に解決します。

以上がC# での TCP スティッキー パケットの問題の解決例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Python 関数入門: exec 関数の概要と例 Python 関数入門: exec 関数の概要と例 Nov 03, 2023 pm 02:09 PM

Python 関数の紹介: exec 関数の概要と例 はじめに: Python では、exec は、文字列またはファイルに格納されている Python コードを実行するために使用される組み込み関数です。 exec 関数はコードを動的に実行する方法を提供し、プログラムが実行時に必要に応じてコードを生成、変更、実行できるようにします。この記事では、exec 関数の使い方と実用的なコード例を紹介します。 exec 関数の使用方法: exec 関数の基本的な構文は次のとおりです。

Go 言語のインデントの仕様と例 Go 言語のインデントの仕様と例 Mar 22, 2024 pm 09:33 PM

Go 言語のインデント仕様と例 Go 言語は Google によって開発されたプログラミング言語であり、その簡潔で明確な構文で知られており、インデント仕様はコードの読みやすさと美しさに重要な役割を果たします。この記事ではGo言語のインデントの仕様を紹介し、具体的なコード例を通して詳しく解説します。インデントの仕様 Go 言語では、スペースの代わりにタブがインデントに使用されます。インデントの各レベルは 1 つのタブで、通常はスペース 4 個の幅に設定されます。このような仕様により、コーディング スタイルが統一され、チームが協力してコンパイルできるようになります。

Oracle DECODE関数の詳細説明と使用例 Oracle DECODE関数の詳細説明と使用例 Mar 08, 2024 pm 03:51 PM

Oracle の DECODE 関数は、クエリ ステートメントのさまざまな条件に基づいてさまざまな結果を返すためによく使用される条件式です。この記事ではDECODE関数の構文・使い方・サンプルコードを詳しく紹介します。 1. DECODE 関数の構文 DECODE(expr,search1,result1[,search2,result2,...,default]) expr: 比較する式またはフィールド。検索1、

Python関数入門:abs関数の使い方と例 Python関数入門:abs関数の使い方と例 Nov 03, 2023 pm 12:05 PM

Python 関数入門: abs 関数の使い方と例 1. abs 関数の使い方の概要 Python では、abs 関数は、指定された値の絶対値を計算するために使用される組み込み関数です。数値引数を受け入れ、その数値の絶対値を返すことができます。 abs 関数の基本構文は次のとおりです。 abs(x) ここで、x は絶対値を計算する数値パラメータであり、整数または浮動小数点数を指定できます。 2. abs 関数の例 以下に、いくつかの具体的な例を通して abs 関数の使用法を示します。 例 1: 計算

C# の雇用の見通しはどのようなものですか? C# の雇用の見通しはどのようなものですか? Oct 19, 2023 am 11:02 AM

初心者でも経験豊富なプロフェッショナルでも、C# をマスターすることでキャリアへの道が開かれます。

いくつかの .NET オープンソース AI および LLM 関連プロジェクト フレームワークを共有する いくつかの .NET オープンソース AI および LLM 関連プロジェクト フレームワークを共有する May 06, 2024 pm 04:43 PM

現在、人工知能(AI)技術の開発は本格化しており、さまざまな分野で大きな可能性と影響力を発揮しています。本日、Dayao は、参考にしていただけるよう、4 つの .NET オープン ソース AI モデル LLM 関連プロジェクト フレームワークを共有します。 https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/DotNetProjectPicks.mdSemanticKernelSemanticKernel は、OpenAI、Azure などの大規模言語モデル (LLM) を統合するように設計されたオープン ソース ソフトウェア開発キット (SDK) です。

Python関数入門:isinstance関数の使い方と例 Python関数入門:isinstance関数の使い方と例 Nov 04, 2023 pm 03:15 PM

Python 関数の紹介: isinstance 関数の使用法と例 Python は、プログラミングをより便利かつ効率的にするための多くの組み込み関数を提供する強力なプログラミング言語です。非常に便利な組み込み関数の 1 つは isinstance() 関数です。この記事では、isinstance関数の使い方と例を紹介し、具体的なコード例を紹介します。 isinstance() 関数は、オブジェクトが指定されたクラスまたは型のインスタンスであるかどうかを判断するために使用されます。この関数の構文は次のとおりです

Python 関数の概要: eval 関数の関数と例 Python 関数の概要: eval 関数の関数と例 Nov 04, 2023 pm 12:24 PM

Python 関数入門: eval 関数の関数と例 Python プログラミングにおいて、eval 関数は非常に便利な関数です。 eval関数は文字列をプログラムコードとして実行することができ、その機能は非常に強力です。この記事では、eval関数の詳しい機能と使用例を紹介します。 1. eval関数の機能 eval関数の機能は非常にシンプルで、文字列をPythonコードとして実行することができます。これは、文字列を変換できることを意味します

See all articles