設計の問題 - Winsock を使用して TCP 経由で小さなデータ セグメントを送信する
TCP 経由で小さなデータ パケットを送信する必要がある場合は、Winsock アプリケーションの設計が特に重要です。 遅延受信確認、Nagle アルゴリズム、Winsock バッファリングの相互作用を考慮しない設計は、パフォーマンスに大きく影響する可能性があります。 この記事では、いくつかのケース スタディを使用して、これらの問題について説明します。 また、Winsock アプリケーションから小さなデータ パケットを効率的に送信するための一連の推奨事項も導き出されます。
元の製品バージョン: Winsock
元の KB 番号: 214397
背景
Microsoft TCP スタックがデータ パケットを受信すると、200 ミリ秒の遅延タイマーがオフになります。 ACK が送信されると、遅延タイマーがリセットされ、次のデータ パケットが受信されたときにさらに 200 ミリ秒の遅延が開始されます。 インターネットアプリケーションとイントラネットアプリケーションの両方の効率を向上させるために、TCP スタックは次の条件を使用して、受信したデータ パケットに対して 1 つの ACK を送信するタイミングを決定します。
- 遅延タイマーの有効期限が切れる前に 2 番目のデータ パケットを受信すると、ACK が送信されます。
- 2 番目のデータ パケットを受信する前に ACK と同じ方向に送信するデータがあり、遅延タイマーが期限切れになると、ACK はデータ セグメントでピギーバックされ、すぐに送信されます。
- 遅延タイマーの有効期限が切れると、ACK が送信されます。
小さなデータ パケットがネットワークを混雑させないようにするために、TCP スタックは既定で Nagle アルゴリズムを有効にします。これにより、複数の送信呼び出しから小さなデータ バッファーが結合され、送信された前のデータ パケットの ACK がリモート ホストから受信されるまで送信が遅延されます。 Nagle アルゴリズムの 2 つの例外を次に示します。
スタックが最大伝送ユニット (MTU) より大きいデータ バッファーを結合した場合、リモート ホストからの ACK を待たずに、フルサイズのパケットがすぐに送信されます。 イーサネット ネットワークでは、TCP/IP の MTU は 1460 バイトです。
TCP_NODELAY
ソケット オプションは、Nagle アルゴリズムを無効にして、小さなデータ パケットがリモート ホストに遅延なく配信されるようにするために適用されます。
アプリケーション層のパフォーマンスを最適化するために、Winsock はアプリケーション送信呼び出しから Winsock カーネル バッファーへのデータ バッファーをコピーします。 次に、スタックは独自のヒューリスティック (Nagle アルゴリズムなど) を使用して、パケットを実際にワイヤに配置するタイミングを決定します。 オプション (既定では 8K) を使用して SO_SNDBUF
、ソケットに割り当てられた Winsock カーネル バッファーの量を変更できます。 必要に応じて、Winsock はバッファー サイズを超えるバッファーを SO_SNDBUF
バッファーできます。 ほとんどの場合、アプリケーションの送信完了は、アプリケーション送信呼び出しのデータ バッファーが Winsock カーネル バッファーにコピーされ、データがネットワーク メディアにヒットしたことを示すだけではありません。 唯一の例外は、0 に設定 SO_SNDBUF
して Winsock バッファリングを無効にした場合です。
Winsock では、次の規則を使用して、アプリケーションへの送信完了を示します (送信の呼び出し方法によっては、完了通知は、ブロック呼び出しから返される関数、イベントの通知、または通知関数の呼び出しなどです)。
ソケットがまだクォータSO_SNDBUF内にある場合、Winsock はアプリケーション送信からデータをコピーし、アプリケーションへの送信完了を示します。
ソケットがクォータを超え
SO_SNDBUF
、スタック カーネル バッファーにバッファーされた送信がまだ 1 つだけである場合、Winsock はアプリケーション送信からデータをコピーし、アプリケーションへの送信完了を示します。ソケットがクォータを超え
SO_SNDBUF
、スタック カーネル バッファーに以前にバッファーされた送信が複数ある場合、Winsock はアプリケーション送信からデータをコピーします。 Winsock は、スタックがクォータ内SO_SNDBUF
のソケットを戻すのに十分な送信を完了するか、未処理の送信条件を 1 つだけ完了するまで、アプリケーションへの送信完了を示しません。
ケース スタディ 1
Winsock TCP クライアントは、データベースに格納するために 1,0000 レコードを Winsock TCP サーバーに送信する必要があります。 レコードのサイズは、20 バイトから 100 バイトの長さまで異なります。 アプリケーション ロジックを簡略化するために、設計は次のとおりです。
- クライアントは送信のみをブロックします。 サーバーはブロック
recv
のみを行います。 - クライアント ソケットは、
SO_SNDBUF
を 0 に設定して、各レコードが 1 つのデータ セグメントに出力されるようにします。 - サーバーはループ内でを呼び出します
recv
。 にポストされたrecv
バッファーは 200 バイトであるため、各レコードを 1 回recv
の呼び出しで受信できます。
パフォーマンス
テスト中に、開発者はクライアントがサーバーに 1 秒あたり 5 つのレコードしか送信できなかったことを検出します。 合計 1,0000 レコード (最大 976 kb のデータ (10000 * 100/1024) は、サーバーへの送信に 30 分以上かかります。
分析
クライアントはオプションを TCP_NODELAY
設定しないため、Nagle アルゴリズムは、ネットワーク上で別のパケットを送信する前に、TCP スタックに ACK を強制的に待機させます。 ただし、クライアントはオプションを 0 に設定して Winsock バッファリングを SO_SNDBUF
無効にしています。 そのため、1,0000 の送信呼び出しを個別に送信し、ACK を送信する必要があります。 各 ACK は、サーバーの TCP スタックで次の処理が行われるため、200 ミリ秒遅延します。
- サーバーがパケットを取得すると、200 ミリ秒の遅延タイマーがオフになります。
- サーバーは何も送信する必要がないため、ACK をピギーバックすることはできません。
- 前のパケットが確認されない限り、クライアントは別のパケットを送信しません。
- サーバーの遅延タイマーが期限切れになり、ACK が送信されます。
改善方法
この設計には 2 つの問題があります。 まず、遅延タイマーの問題があります。 クライアントは、200 ミリ秒以内に 2 つのパケットをサーバーに送信できる必要があります。 クライアントは既定で Nagle アルゴリズムを使用するため、既定の Winsock バッファリングのみを使用し、0 に設定 SO_SNDBUF
しないでください。 TCP スタックが最大伝送ユニット (MTU) より大きいバッファーを結合すると、リモート ホストからの ACK を待機することなく、フルサイズのパケットがすぐに送信されます。
次に、この設計では、このような小さいサイズのレコードごとに 1 つの送信が呼び出されます。 この小さなサイズの送信は効率的ではありません。 この場合、開発者は各レコードを 100 バイトに埋め込み、1 回のクライアント送信呼び出しから一度に 80 レコードを送信することができます。 合計で送信されるレコードの数をサーバーに知らせるために、クライアントは、フォローするレコードの数を含む修正サイズのヘッダーとの通信を開始したい場合があります。
ケース スタディ 2
Winsock TCP クライアント アプリケーションは、株価情報サービスを提供する Winsock TCP サーバー アプリケーションとの 2 つの接続を開きます。 最初の接続は、ストック シンボルをサーバーに送信するためのコマンド チャネルとして使用されます。 2 番目の接続は、株価情報を受け取るデータ チャネルとして使用されます。 2 つの接続が確立されると、クライアントはコマンド チャネルを介してストック シンボルをサーバーに送信し、株価がデータ チャネルを通じて戻ってくるのを待ちます。 最初の株価情報を受け取った後にのみ、次のストック シンボル要求がサーバーに送信されます。 クライアントとサーバーは、 オプションと TCP_NODELAY
オプションをSO_SNDBUF
設定しません。
パフォーマンス
テスト中に、開発者はクライアントが 1 秒あたり 5 つの引用符しか取得できなかったことを検出します。
分析
この設計では、一度に 1 つの未処理の株価情報要求のみが許可されます。 最初のストック シンボルはコマンド チャネル (接続) を介してサーバーに送信され、応答はデータ チャネル (接続) 経由でサーバーからクライアントに直ちに送信されます。 次に、クライアントは直ちに 2 番目のストック シンボル要求を送信し、送信呼び出しの要求バッファーが Winsock カーネル バッファーにコピーされると、すぐに送信が返されます。 ただし、コマンド チャネル経由の最初の送信がまだ確認されていないため、クライアント TCP スタックはカーネル バッファーから要求をすぐに送信できません。 サーバー コマンド チャネルの 200 ミリ秒遅延タイマーが期限切れになると、最初のシンボル要求の ACK がクライアントに返されます。 その後、2 番目の見積もり要求は、200 ミリ秒の遅延後にサーバーに正常に送信されます。 2 つ目のストック シンボルの見積もりは、現時点ではクライアント データ チャネルの遅延タイマーの有効期限が切れているため、データ チャネルを通じてすぐに返されます。 前の見積もり応答の ACK がサーバーによって受信されます。 (クライアントは 200 ミリ秒の 2 回目の株価要求を送信できなかったため、クライアントの遅延タイマーが期限切れになり、ACK をサーバーに送信する時間が与えられなかったことに注意してください)。その結果、クライアントは 2 番目の見積もり応答を取得し、同じサイクルの対象となる別の見積もり要求を発行できます。
改善方法
ここでは、2 つの接続 (チャネル) 設計は不要です。 株価の要求と応答に 1 つの接続のみを使用する場合、見積もり要求の ACK を見積もり応答でピギーバックし、すぐに戻ることができます。 パフォーマンスをさらに向上させるために、クライアントは複数の株価情報要求をサーバーへの 1 回の送信呼び出しに 多重化 し、サーバーは複数の見積もり応答をクライアントへの 1 回の送信呼び出しに 多重化 することもできます。 何らかの理由で 2 つの一方向チャネル設計が必要な場合は、前の
TCP_NODELAY
パケットの ACK を待機することなく、小さなパケットをすぐに送信できるように、両側でオプションを設定する必要があります。
推奨事項
これら 2 つのケース スタディは作成されていますが、いくつかの最悪のシナリオを示すのに役立ちます。 広範な小さなデータ セグメント送信と を含むアプリケーションを設計する場合は、 recvs
次のガイドラインを考慮する必要があります。
データ セグメントがタイム クリティカルでない場合、アプリケーションはそれらをより大きなデータ ブロックに結合して送信呼び出しに渡す必要があります。 送信バッファーは Winsock カーネル バッファーにコピーされる可能性があるため、バッファーが大きすぎないようにしてください。 8K未満が有効です。 Winsock カーネルが MTU より大きいブロックを取得する限り、複数のフルサイズのパケットと残りのパケットを含む最後のパケットが送信されます。 送信側 (最後のパケットを除く) は、200 ミリ秒の遅延タイマーではヒットしません。 最後のパケットが奇数のパケットである場合でも、遅延受信確認アルゴリズムの対象となります。 送信側スタックが MTU より大きい別のブロックを取得した場合でも、Nagle アルゴリズムをバイパスできます。
可能であれば、単方向データ フローを使用したソケット接続は避けてください。 一方向ソケット経由の通信は、Nagle および遅延受信確認アルゴリズムの影響を受けやすくなります。 通信が要求と応答フローに従う場合は、1 つのソケットを使用して送信と
recvs
、応答で ACK をピギーバックできるようにする必要があります。すべての小さなデータ セグメントをすぐに送信する必要がある場合は、送信側でオプションを設定
TCP_NODELAY
します。送信完了が Winsock によって示されたときにパケットがネットワーク上で送信されるようにする場合を除き、 を
SO_SNDBUF
0 に設定しないでください。 実際、既定の 8K バッファーは、ほとんどの状況で正常に動作するようにヒューリスティックに決定されており、新しい Winsock バッファー設定で既定よりもパフォーマンスが向上することをテストしていない限り、変更しないでください。 また、一括データ転送を行うアプリケーションでは、主にゼロに設定SO_SNDBUF
すると便利です。 それでも、効率を最大限に高めるには、ダブル バッファリング (特定の時点で複数の未処理の送信) と重複した I/O と組み合わせて使用する必要があります。データ配信を保証する必要がない場合は、UDP を使用します。
関連情報
遅延受信確認と Nagle アルゴリズムの詳細については、次を参照してください。
Braden,R.[1989], RFC 1122, Internet Hosts--Communication Layers の要件, インターネット エンジニアリング タスク フォース.