4

Socket の TcpListener/TcpClient ラッパーを使用して作成した C# の非同期 TCP ソケット サーバーがあります。私は一般的にネットワーキングにかなり慣れていないので、TCP が送信されたデータをどのように処理するか、およびメッセージの境界をどのように保持しないかを知りませんでした。少し調査した結果、確かな解決策を思いついたと思いますが、誰か私にもっとアドバイスがあるかどうか疑問に思っています。

現在、送信しているデータは、protobuf (Google のデータ交換ライブラリ) https://code.google.com/p/protobuf/を使用してシリアル化されたクラス オブジェクトの byte[] です。

データをシリアル化した後、送信する前に、バイト配列の先頭に 4 バイトの Int32 を追加することにしました。私の考えでは、パケットがサーバーに送信されると、Int32 が解析され、そのバイト数を受信するまで待ってから、データを処理します。それ以外の場合は、BeginRead を再度呼び出すだけです。

データを書き込む前に実行したコードは次のとおりです。正常に動作しているように見えますが、パフォーマンスに関する提案は受け付けています。

public byte[] PackByteArrayForSending(byte[] data)
{
    // [0-3] Packet Length 
    // [3-*] original data

    // Get Int32 of the packet length
    byte[] packetLength = BitConverter.GetBytes(data.Length);
    // Allocate a new byte[] destination array the size of the original data length plus the packet length array size
    byte[] dest = new byte[packetLength.Length + data.Length];

    // Copy the packetLength array to the dest array
    Buffer.BlockCopy(packetLength, 0, dest, 0, packetLength.Length);
    // Copy the data array to the dest array
    Buffer.BlockCopy(data, 0, dest, packetLength.Length, data.Length);

    return dest;
}

私はサーバー側で少し立ち往生しています。Buffer.BlockCopy を使用して最初の 4 バイトをコピーし、次に BitConverter.ToInt32 を使用して取得する長さを読み取ることで、packetLength 変数を読み取らせます。着信データを常にクライアント固有の Stream オブジェクトに読み込む必要があるのか​​、それとも while ループを使用するだけなのかがわかりません。これまでにサーバー側にあるコードの例を次に示します。

NetworkStream networkStream = client.NetworkStream;
int bytesRead = networkStream.EndRead(ar);

if (bytesRead == 0)
{
  Console.WriteLine("Got 0 bytes from {0}, marking as OFFLINE.", client.User.Username);
  RemoveClient(client);
}

Console.WriteLine("Received {0} bytes.", bytesRead);

// Allocate a new byte array the size of the data that needs to be deseralized.
byte[] data = new byte[bytesRead];

// Copy the byte array into the toDeserialize buffer
Buffer.BlockCopy(
  client.Buffer,
  0,
  data,
  0,
  bytesRead);

// Read the first Int32 tp get the packet length and then use the byte[4] to get the packetLength
byte[] packetLengthBytes = new byte[4];
Buffer.BlockCopy(
  data,
  0,
  packetLengthBytes,
  0,
  packetLengthBytes.Length);

int packetLength = BitConverter.ToInt32(packetLengthBytes, 0);

// Now what do you recommend?

// If not all data is received, call 
// networkStream.BeginRead(client.Buffer, 0, client.Buffer.Length, ReadCallback, client);
// and preserve the initial data in the client object

お時間とアドバイスをいただきありがとうございます。このテーマについてさらに学ぶことを楽しみにしています。

4

1 に答える 1

2

TCP は、一方の端でストリームに詰め込まれたバイトがもう一方の端で同じ順序で損失や重複なしに落ちることを保証します。確かに、1 バイトを超えるエンティティはサポートされていません。

通常、メッセージの長さ、タイプ、およびサブタイプを含むヘッダーを先頭に追加しました。多くの場合、リクエストとレスポンスを照合するために相関 ID が提供されます。

基本的なパターンは、バイトを取得してバッファーに追加することです。バッファー内のデータがメッセージ ヘッダーを格納するのに十分な場合は、メッセージの長さを抽出します。バッファ内のデータがメッセージを格納するのに十分な場合は、メッセージをバッファから削除して処理します。処理する完全なメッセージがなくなるまで、残りのデータで繰り返します。アプリケーションによっては、これが読み取りを待機するか、ストリームで追加データをチェックするポイントになる場合があります。一部のアプリケーションでは、送信者のスロットリングを回避するために、別のスレッドからストリームを読み取り続ける必要がある場合があります。

完全なメッセージ長フィールドがあると仮定できないことに注意してください。メッセージの処理後に 3 バイトが残っている可能性があり、int.

メッセージによっては、メッセージが処理されるたびに残りのバイトをシャッフルするよりも循環バッファーを使用する方が効率的な場合があります。

于 2013-04-14T17:10:05.587 に答える