使用 Windows 套接字 API 程序将数据复制到 TCP 服务器时,性能会降低

本文提供了使用 Windows 套接字 API 程序将数据复制到 TCP 服务器时性能缓慢的问题的解决方法。

适用于:Windows Server 2012 R2、Windows 10 - 所有版本
原始 KB 编号: 823764

症状

运行使用 Windows 套接字 API 的程序时,在将数据复制到 TCP 服务器时,可能会遇到性能降低的问题。

如果使用网络探查器(如 Microsoft 网络监视器)进行网络跟踪,则 TCP 服务器会将 TCP ACK 段发送到延迟确认计时器 (也称为延迟 ACK 计时器) 的 TCP 数据流中的最后一个 TCP 段。 默认情况下,对于 Windows 操作系统,此计时器的值为 200 毫秒 (毫秒) 。 用于发送 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 字节
服务器客户端> 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 平台 SDK 文档。

方法 2:使套接字发送缓冲区大小大于程序发送缓冲区大小

若要修改套接字发送缓冲区,请使用 Windows 套接字 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 产品问题。

References

328890 在 Windows XP 和 Windows Server 2003 中控制 TCP 确认 (ACK) 行为的新注册表项