使用 Windows 套接字 API 程序将数据复制到 TCP 服务器时,会出现性能缓慢的情况
本文提供了使用 Windows 套接字 API 程序将数据复制到 TCP 服务器时出现性能缓慢的问题的解决方法。
症状
运行使用 Windows 套接字 API 的程序时,将数据复制到 TCP 服务器时可能会遇到性能降低的问题。
如果使用网络探查器(如 Microsoft 网络监视器)进行网络跟踪,TCP 服务器会向延迟确认计时器中 TCP 数据流中的最后一个 TCP 段发送 TCP ACK 段 (也称为延迟 ACK 计时器) 。 默认情况下,对于 Windows 操作系统,此计时器的值为 200 毫秒 (毫秒) 。 用于发送 64 KB (KB) 的典型数据流类似于以下序列:
Client->Server 1460 字节
Client->Server 1460 字节
Server->客户端 ACK
Client->Server 1460 字节
Client->Server 1460 字节
Server->客户端 ACK
....
Client->Server 1460 字节
Client->Server 1460 字节
Server->客户端 ACK-PUSH
Client->Server 1296 字节
->延迟 ACK 200 毫秒
原因
出现此问题的原因是 Windows 套接字 API 和 windows 套接字 API 的体系结构afd.sys。 如果满足以下所有条件,则会出现此问题:
-
Windows 套接字程序使用非阻止套接字。
-
单个发送呼叫或 WSASend 呼叫将填满整个基础套接字发送缓冲区。
例如,程序在其套接字初始化例程期间使用 Windows Sockets 函数将默认套接字发送缓冲区更改为
setsockopt
32 KB:Csetsockopt( sock, SOL_SOCKET, 32768, (char *) &val, sizeof( int ));
稍后,当程序发送数据时,它会发出发送呼叫或 WSASend 呼叫,并在每个发送过程中发送 64 KB 的数据:
Csend(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 Platform SDK 文档。
方法 2:使套接字发送缓冲区大小大于程序发送缓冲区大小
若要修改套接字发送缓冲区,请使用 Windows Sockets 函数确定当前套接字发送缓冲区大小 (SO_SNDBUF) ,然后使用该函数设置 getsockopt
setsockopt
套接字发送缓冲区大小。 完成后,SO_SNDBUF值必须至少比程序发送缓冲区大小大 1 个字节。
修改发送调用或 WSASend 调用以指定缓冲区大小至少比值小 1 SO_SNDBUF字节。 在本文"原因"一节的前面示例中,你可以将 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 的服务器上,执行以下步骤:
- 启动 注册表编辑器 (Regedit.exe) 。
-
找到并单击以下注册表子项:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\<Interface GUID>
-
在 "编辑 "菜单上,单击 "添加值",然后创建以下注册表值:
值名称:TcpDelAckTicks
数据类型:REG_DWORD
值数据: 0 - 退出 注册表编辑器。
- 重新启动 Windows,使此更改生效。
在运行 Windows XP 或 Windows Server 2003 的服务器上,执行以下步骤:
- 启动 注册表编辑器。
-
找到并单击以下注册表子项:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\<Interface GUID>
- 在“编辑”菜单上,指向“新建”,然后单击“DWORD 值”。
- 命名新值 TcpAckFrequency, 并为其分配值 1。
- 退出 注册表编辑器。
- 重新启动 Windows,使此更改生效。
方法 4:修改非阻止afd.sys中的缓冲行为
重要
此部分(或称方法或任务)介绍了修改注册表的步骤。 但是,注册表修改不当可能会出现严重问题。 因此,请务必严格按照这些步骤操作。 为了加强保护,应先备份注册表,再进行修改。 如果出现问题,可以还原注册表。 若要详细了解如何备份和还原注册表,请单击以下文章编号以查看 Microsoft 知识库中的文章 :322756 如何在 Windows 中备份和还原注册表
备注
此注册表项仅适用于 Windows Server 2003 Service Pack 1 和后续 Service Pack。
- 单击 "开始*",regedit.exe,* 然后单击"确定"。
-
找到并单击下面的注册表子项:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters
- 在“编辑”菜单上,指向“新建”,然后单击“DWORD 值”。
- 命名新值 NonBlockingSendSpecialBuffering, 并为其分配值 1。
- 退出 注册表编辑器。
- 重新启动 Windows,使此更改生效。
如果您有其他问题,可以联系汉中创云互联阿里云代理商,为您提供一对一专业全面的技术服务,同时新老阿里云会员,均可享受我公司代理商价格,欢迎咨询!
我有话说: