使用 Windows 套接字 API 程序将数据复制到 TCP 服务器时性能缓慢

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

原始 KB 数: 823764

现象

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

如果使用网络探查器(例如Microsoft网络监视器)进行网络跟踪,TCP 服务器会将 TCP ACK 段发送到延迟确认计时器(也称为延迟的 ACK 计时器)中 TCP 数据流中的最后一个 TCP 段。 默认情况下,对于 Windows 操作系统,此计时器的值为 200 毫秒(ms)。 用于发送 64 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 套接字程序使用非阻塞套接字。

  • 单个发送调用或 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 段之前不会将套接字报告为可写。 默认情况下,在 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,使此更改生效。

Status

Microsoft 已经确认这是一个列于“适用范围”部分的 Microsoft 产品问题。

参考

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