Windows ソケット API プログラムを使用して TCP サーバーにデータをコピーすると、パフォーマンスが低下します

この記事では、Windows Sockets API プログラムを使用して TCP サーバーにデータをコピーするときにパフォーマンスが低下する問題の回避策について説明します。

適用対象:Windows Server 2012 R2、Windows 10 - すべてのエディション
元の KB 番号: 823764

現象

Windows Sockets API を使用するプログラムを実行すると、TCP サーバーにデータをコピーするときにパフォーマンスが低下する可能性があります。

Microsoft Network Monitor などのネットワーク スニファを使用してネットワーク トレースを作成した場合、TCP サーバーは、遅延受信確認タイマー (遅延 ACK タイマーとも呼ばれます) 内の TCP データ ストリーム内の最後の TCP セグメントに TCP ACK セグメントを送信します。 既定では、Windows オペレーティング システムの場合、このタイマーの値は 200 ミリ秒 (ミリ秒) です。 64 キロバイト (KB) のデータを送信するための一般的なデータ フローは、次のシーケンスのようになります。

Client-Server> 1460 バイト
Client-Server> 1460 バイト
サーバー クライアント> ACK
Client-Server> 1460 バイト
Client-Server> 1460 バイト
サーバー クライアント> ACK
....
Client-Server> 1460 バイト
Client-Server> 1460 バイト
サーバー クライアント> ACK-PUSH
Client-Server> 1296 バイト
-> 遅延 ACK 200 ミリ秒

原因

この問題は、Windows ソケット API と afd.sys のアーキテクチャ動作が原因で発生します。 この問題は、次の条件がすべて満たされている場合に発生します。

  • Windows ソケット プログラムでは、非ブロッキング ソケットが使用されます。

  • 単一の送信呼び出しまたは WSASend 呼び出しによって、基になるソケット送信バッファー全体が満たされます。

    たとえば、プログラムは Windows ソケット setsockopt 関数を使用して、ソケット初期化ルーチン中に既定のソケット送信バッファーを 32 KB に変更します。

    setsockopt( sock, SOL_SOCKET, 32768, (char *) &val, sizeof( int ));
    

    その後、プログラムはデータを送信するときに、送信呼び出しまたは WSASend 呼び出しを発行し、送信ごとに 64 KB のデータを送信します。

    send(socket, pWrBuffer, 65536, 0);
    

    このシナリオでは、プログラムが 64 KB のデータの送信呼び出しを発行するたびに、基になる 32 KB ソケット バッファーが入力されている場合、プログラムはSOCKET_ERRORエラー コードを返します。 WSAGetLastError 関数を呼び出した後、プログラムは WSAEWOULDBLOCK エラー コードを受け取ります。 ほとんどのプログラムでは、Windows ソケット select 関数を使用してソケットの状態をチェックします。 このシナリオでは、クライアントが未処理の TCP ACK セグメントを受信するまで、select 関数はソケットを書き込み可能として報告しません。 Windows 環境では、遅延受信確認アルゴリズムのため、既定では 200 ミリ秒かかる場合があります。

  • リモート TCP サーバーは、クライアントがプッシュ ビットが設定された最後の TCP セグメントを送信する前にすべての TCP セグメントを確認します。

回避策

この問題を回避するには、次のいずれかの方法を使用します。

方法 1: ブロッキング ソケットを使用する

この問題は、非ブロッキング ソケットでのみ発生します。 ブロッキング ソケットを使用する場合、ソケット バッファーの処理方法 afd.sys 異なるため、この問題は発生しません。 ブロッキングおよび非ブロッキング ソケット プログラミングの詳細については、Microsoft Platform SDK のドキュメントを参照してください。

方法 2: ソケット送信バッファー サイズをプログラム送信バッファー サイズより大きくする

ソケット送信バッファーを変更するには、Windows Sockets getsockopt 関数を使用して現在のソケット送信バッファー サイズ (SO_SNDBUF) を決定し、関数を setsockopt 使用してソケット送信バッファー サイズを設定します。 完了したら、SO_SNDBUF値は、プログラム送信バッファー・サイズより少なくとも 1 バイト大きい必要があります。

送信呼び出しまたは WSASend 呼び出しを変更して、SO_SNDBUF値より少なくとも 1 バイト小さいバッファー サイズを指定します。 この記事の「原因」セクションの前の例では、setsockopt 呼び出しを次の値に変更できます。

setsockopt( sock, SOL_SOCKET, 65537, (char *) &val, sizeof( int ));

または、送信呼び出しを次の値に変更できます。

send(socket, pWrBuffer, 32767, 0);

これらの値の任意の組み合わせを使用することもできます。

方法 3: TCP サーバーの TCP/IP 設定を変更する

重要

このセクション、方法、またはタスクには、レジストリの編集方法が記載されています。 レジストリを誤って変更すると、深刻な問題が発生することがあります。 レジストリを変更する際には十分に注意してください。 保護を強化するため、レジストリを変更する前にレジストリをバックアップします。 こうしておけば、問題が発生した場合にレジストリを復元できます。 レジストリのバックアップ方法および復元方法の詳細を参照するには、以下のサポート技術情報番号をクリックしてください。
322756 Windows でレジストリをバックアップおよび復元する方法

TCP サーバーの TCP/IP 設定を変更して、受信 TCP セグメントをすぐに確認します。 この回避策は、クライアントのインストール ベースが大きく、プログラムの動作を変更できない環境で最適です。 リモート TCP サーバーが Windows ベースのサーバー上で実行されるシナリオでは、リモート サーバーのレジストリを変更する必要があります。 その他のオペレーティング システムについては、遅延受信確認タイマーを変更する方法については、オペレーティング システムのドキュメントを参照してください。

Windows 2000 を実行するサーバーで、次の手順を実行します。

  1. レジストリ エディター (Regedit.exe) を起動します。
  2. 次のレジストリ サブキーを見つけてクリックします。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\<Interface GUID>
  3. [ 編集 ] メニューの [ 値の追加] をクリックし、次のレジストリ値を作成します。
    値名: TcpDelAckTicks
    データ型: REG_DWORD
    値データ: 0
  4. レジストリ エディターを終了します。
  5. この変更を有効にするには、Windows を再起動します。

Windows XP または Windows Server 2003 を実行するサーバーでは、次の手順に従います。

  1. レジストリ エディターを起動します。
  2. 次のレジストリ サブキーを見つけてクリックします。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\<Interface GUID>
  3. [編集] メニューの [新規] をポイントし、[DWORD 値] をクリックします。
  4. 新しい値 に TcpAckFrequency という名前を付け、値 1 を割り当てます。
  5. レジストリ エディターを終了します。
  6. この変更を有効にするには、Windows を再起動します。

方法 4: 非ブロッキング ソケットの afd.sys でのバッファリング動作を変更する

重要

このセクション、方法、またはタスクには、レジストリの編集方法が記載されています。 レジストリを誤って変更すると、深刻な問題が発生することがあります。 レジストリを変更する際には十分に注意してください。 保護を強化するため、レジストリを変更する前にレジストリをバックアップします。 こうしておけば、問題が発生した場合にレジストリを復元できます。 レジストリをバックアップおよび復元する方法の詳細については、次の記事番号をクリックして、「Microsoft サポート技術情報: Windows でレジストリをバックアップおよび復元する方法 」322756 の記事を参照してください

注:

このレジストリ キーは、Service Pack 1 以降のサービス パックを使用する Windows Server 2003 でのみ使用できます。

  1. [ スタート] をクリック し、「regedit.exe」と入力し、[OK] をクリック します
  2. 次のレジストリ サブキーを見つけてクリックします。
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters
  3. [編集] メニューの [新規] をポイントし、[DWORD 値] をクリックします。
  4. 新しい値 に NonBlockingSendSpecialBuffering という名前を付け、値 1 を割り当てます。
  5. レジストリ エディターを終了します。
  6. この変更を有効にするには、Windows を再起動します。

状態

マイクロソフトでは、この問題をこの資料の対象製品として記載されているマイクロソフト製品の問題として認識しています。

関連情報

328890 Windows XP および Windows Server 2003 で TCP 受信確認 (ACK) の動作を制御するための新しいレジストリ エントリ