當您使用 Windows 套接字 API 程式將資料複製到 TCP 伺服器時,效能變慢

本文提供使用 Windows 套接字 API 程式將數據複製到 TCP 伺服器時效能變慢的問題因應措施。

適用於:Windows Server 2012 R2、Windows 10 - 所有版本
原始 KB 編號: 823764

徵狀

當您執行使用 Windows 套接字 API 的程式時,當您將資料複製到 TCP 伺服器時,效能可能會變慢。

如果您使用 Microsoft 網路監視器之類的網路探查程式進行網路追蹤,TCP 伺服器會將 TCP ACK 區段傳送至延遲通知定時器中 TCP 數據流中的最後一個 TCP 區段 (也稱為延遲的 ACK 定時器) 。 根據預設,針對 Windows 作業系統,此定時器的值為 200 毫秒, (ms) 。 傳送 64 KB (KB) 數據的一般數據流看起來類似下列順序:

Client-Server> 1460 位元組
Client-Server> 1460 位元組
Server-Client> ACK
Client-Server> 1460 位元組
Client-Server> 1460 位元組
Server-Client> ACK
....
Client-Server> 1460 位元組
Client-Server> 1460 位元組
Server-Client> ACK-PUSH
Client-Server> 1296 位元組
-> 延遲 ACK 200 毫秒

原因

發生此問題的原因是 Windows 套接字 API 和 afd.sys 的架構行為。 如果下列所有條件都成立,就會發生此問題:

  • Windows Sockets 程式會使用非封鎖套接字。

  • 單一傳送呼叫或 WSASend 呼叫會填滿整個基礎套接字傳送緩衝區。

    例如,程式會在套接字初始化例程期間,使用 Windows Sockets 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 Sockets 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 設定

重要事項

這個章節、方法或工作包含修改登錄的步驟。 然而,不當修改登錄可能會發生嚴重的問題。 因此,請務必謹慎地依照這些步驟執行。 為了有多一層保護,請先備份登錄再進行修改。 如此一來,您就可以在發生問題時還原登錄。 如需有關如何備份和還原登錄的詳細資訊,請按一下下列文章編號,檢視「Microsoft 知識庫」中的文章:
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 知識庫中的文章: 322756 如何在 Windows 中備份和還原登錄

注意事項

此登錄機碼僅適用於 Service Pack 1 和後續 Service Pack 的 Windows Server 2003。

  1. 按兩下 [開始],輸入 regedit.exe],然後按兩下 [ 確定]
  2. 找出並按一下下列登錄子機碼:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters
  3. [編輯] 功能表中,指向 [新增],然後按一下 [DWORD 值]
  4. 將新值命名為 NonBlockingSendSpecialBuffering,並將值指派為 1。
  5. 結束登錄 編輯器
  6. 重新啟動 Windows,讓這項變更生效。

狀態

Microsoft 已確認<適用於>一節所列的 Microsoft 產品確實有上述問題。

參考資料

328890 在 Windows XP 和 Windows Server 2003 中控制 TCP 通知 (ACK) 行為的新登錄專案