6

クライアントから送信している TCP パケットがサーバーに到着していないように見える単純なクライアント サーバーをセットアップしました。

通常はすべて正常に動作しますが、クライアントで 50 のスレッドをスピンアップして同じ小さなデータ パケット (わずか 39 バイト) でサーバーを「同時に」ヒットすると、サーバーがすべてのバイトを受信しないランダムな回数が発生します。さらに奇妙なのは、それらを受信しない方法が非常に一貫していることです... 5バイトしか受信されません。

私はtcpdumptcpflowを使用して、両端で何が起こっているかをキャプチャしています ( tcp フローに慣れていない場合は、大量の TCP SYN/ACK/FIN/etc ノイズを TCP ストリームから削除し、送信されたデータを表示するだけです)。いずれかの方向)

クライアント側では、39 バイトのパケットを起動する 50 のスレッドの場合、完璧に見えます。具体的には、tcpflow (libpcap を使用) は、50 の同一のデータ転送を示しています。

07 B6 00 01 | 00 1E 00 00 | <etc>

私が理解しているように、libpcap/tcpdump はかなり低いレベル (TCP スタックの下) からデータを取得するため、これは、データが正常に送信されたか、少なくともカーネル バッファーにスタックされていなかったことを意味します。

ただし、サーバー側を見ると、すべてが完璧というわけではありません。乱数が失敗しており、高い割合です。たとえば、50 のソケット接続のうち 30 は正常に動作しますが、そのうちの 20 では、サーバーsocket.recvがバイトの待機中にタイムアウトになるというプロトコル エラーが発生します (プロトコルは正確なパケット長を示します)。

失敗する方法は非常に一貫しています。30/20 の場合、30 個のソケットが送信された 39 バイトを完全に受信します。残りの 20 個の ALL はこの部分的なデータを受け取り、その後socket.recvタイムアウトします。

07 B6 00 01 | 00

20 接続ごとに 5 バイトしか到着していません。tcpdump も 5 バイトしか到着していないことを示しているため、カーネル レベルにあるようです。

これはどのように起こりますか?

この 5 バイト境界は 100% 一致するわけではありません。これはヘッダーの最初の部分で、次に 34 バイトのペイロードが続きますが、到着していません。クライアント側では、このように分割されます。

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
sock.sendall(HEADER)  # 5 bytes
sock.sendall(PAYLOAD) #34 bytes

そして、すべてのスレッドで両方のsock.sendall呼び出しが正常に完了しました。私の tcp ロギングは、50 回の実行すべてが 39 バイトを完全に「ドアの外」に送信することを示していることが証明されています。

これの根本的な原因に関するアイデアはありますか? 私は何が欠けていますか?

4

3 に答える 3

5

私自身の質問に答える...

簡単に言えば、TCP だけでは、送信されたバイトを意図した受信者が実際に受信したかどうかをクライアントが知る方法がないということです。

つまり、クライアントが「喜んで」バイトを送信したかどうかは関係ありません... TCP を使用しても、バイトが到着しない可能性があり、意図した受信者にいつ到達するかについてはまったくわかりません。とにかく、アプリケーション層に何らかの承認を組み込まないわけではありません。

私の特定のケースでは、クライアントが DID を送信したバイトが実際にサーバーに到着することが判明しましたが、到着するまでに約 30 秒 (!!!) かかり、その時点でクライアントとサーバーの両方のアプリケーション プロトコル コードがタイムアウトしていました。

クライアント側とサーバー側のログのビュー (失敗した接続の 1 つ) は次のとおりです。

これらの画像は、 tcpdumpキャプチャ ファイルからの 1 つの特定の TCP ストリームのWiresharkビューです。大量の再送信が発生したことがわかります。これらの再送信が必要になった根本的な原因は何ですか? 私にはまったくわかりません(でも知りたいです!)。

データは、送信から 30 秒後に最後の 2 番目のエントリ (#974) でサーバーに到着し、その間に多数の再送信が試行されました。サーバー側の #793 に興味がある場合、これはアプリケーション レイヤー プロトコルがクライアントにメッセージを送り返し、「データ待ちでタイムアウトしました... データはどこにありますか?」というメッセージを送信しようとする試みです。

固有の遅延に加えて、データがtcpdumpサーバーのログに表示されなかった理由の 1 つは、 tcpdump. 要するに、ファイルに表示される内容に大きな違いがあるように見えるためtcpdump、キャプチャ ファイル (スイッチで作成されたファイル) を見る前に、必ず Ctrl-C でキャプチャを終了してください。-wこれはフラッシュ/同期の問題だと思いますが、推測しています。ただし、Ctrl-C がないと、間違いなくデータが欠落していました。

今後の参考のために詳細...

TCPが次のことを行うことをよく読んだり聞いたりしますが:

  1. パケットが到着することを保証します (vs UDP、そうではありません)
  2. パケットが順番に到着することを保証する

最初のことが実際にはまったく真実ではないことは明らかです。TCP は目的の受信者にバイトを送信するために最善を尽くしますが(長時間の再試行を含む)、これは保証ではありませんsend。送信文字数」。後者は正しくなく、非常に誤解を招きます (以下を参照)。

sendこれの根源は、さまざまなソケット呼び出し (特に) の動作方法と、オペレーティング システムの TCP/IP スタックとの対話方法に主に由来しています...

TCP 交換の送信側では、進行は非常に単純です。最初にあなたconnect()、次にあなたsend()

connect()正常に返されたということは、サーバーへの接続を確立できたことを意味するため、少なくともこの時点でサーバーがそこにあり、リッスンしていたことがわかります (つまり、3 部構成の TCP オープニング ハンドシェイクが成功しました)。

'send` の場合、呼び出しのドキュメントでは戻り値 (正の場合) は「送信された [バイト] の数」であると示されていますが、これは単純に間違っています。戻り値が示すのは、基礎となる OS の TCP スタックが発信バッファーに受け入れたバイト数だけです。この時点以降、OS は最初に接続した受信者にこれらのバイトを配信するために最善を尽くします。しかし、これは決して起こらないかもしれないので、そうではありませこれらのバイトが送信されることを期待できるということです。少し驚くべきことに、TCP には ACK メッセージが組み込まれていますが、少なくとも TCP ソケット層では、これが発生した (または発生しなかった!) かどうかを判断する実際の方法さえありません。送信されたバイトの完全な受信を確認するには、アプリケーション層で何らかの確認を追加する必要があります。nos は、これについて少し説明している別の質問で素晴らしい答えを持っています。

補遺...

ここで私が抱えている興味深いジレンマの 1 つは、アプリケーション層プロトコルに何らかの再試行機能を組み込む必要があるかどうかということです。現在、サーバーでのデータ待機中にタイムアウトが発生した場合は、接続を閉じて、同じリクエストで新しい接続を開くと効果的です。低レベルの TCP 再試行が成功しなかったため、このように見えますが、その間に他のクライアント側スレッドが適切な時間で通過していました。しかし、これはひどく間違っているように感じます... TCP の再試行で十分だと思うでしょう。しかし、そうではありませんでした。これを解決するには、TCP の問題の根本原因を調べる必要があります。

于 2012-04-22T16:21:20.110 に答える
4

非常に少数のバイトを送信しているため、Nagle アルゴリズムに違反している可能性があります。これは、適切な量のデータがバッファリングされ、送信準備が整うまで、送信したいデータを保持します。

ソケットを作成したら、データを送信する前に次の行を追加してみてください。

sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)

ただし、これを行うと、通信のオーバーヘッドが大幅に増えることに注意してください。

于 2012-04-22T16:48:03.127 に答える
1

(バッファリングのために) send と recv は、利用可能であると期待するほど多くのデータを送受信しない可能性があるため、十分に注意する必要があります。また、送信されたと思われる量のデータを受信できる「はず」であっても、スレッドがいつでもブロックできるように注意する必要があります。

于 2012-04-20T17:01:45.610 に答える