C++中send()低频率调用时性能下降的优化方案咨询
基于微软MSDN的C++ TCP服务器与客户端示例开展乒乓测试:服务器向客户端发送10字节TCP小数据包,客户端等待指定时长后回复10字节数据包,服务器立即回传,循环执行该流程。
测试环境为本地Windows Server 2019系统,搭载Intel Xeon 2288G处理器(已关闭C状态,完成高性能硬件优化)。测试观察到:客户端等待时长从0增加至数秒时,服务器端send()调用耗时从5微秒升至13微秒。
需求:如何让服务器端的send()调用在客户端任意等待时长下都保持最低耗时?
相关代码
服务器端代码
do { iResult = recv(ClientSocket, recvbuf, recvbuflen, 0); QueryPerformanceCounter(&counter); // 计时 t0 = counter.QuadPart / (double)freq.QuadPart * 1000000; iSendResult = send(ClientSocket, recvbuf, iResult, 0); QueryPerformanceCounter(&counter); T[i] = counter.QuadPart / (double)freq.QuadPart * 1000000 - t0; i++; } while (i < N);
客户端代码
do{ iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 ); iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); i++; // 延迟实现:通过调整Cycles值(0到3*10^9)改变等待时长 int j = 0; double a = 1.000000001; do { a0 = a0 * a; j++; } while(j < Cycles); } while (i < N);
测试趋势参考
send()耗时随客户端等待时长变化的趋势图:send()耗时 vs 客户端等待时长
禁用Nagle算法(TCP_NODELAY)
默认TCP的Nagle算法会延迟小数据包发送以合并报文,当连接空闲后,首次send()可能触发该延迟。调用以下代码禁用Nagle算法,强制小数据包立即发送:int optval = 1; setsockopt(ClientSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&optval, sizeof(optval));启用TCP保活机制
客户端长时间等待会让连接进入空闲状态,内核可能会调整连接资源状态,导致send()时额外开销。通过设置TCP保活,维持连接活跃:tcp_keepalive keepalive = {0}; keepalive.onoff = 1; keepalive.keepalivetime = 1000; // 连接空闲1秒后开始发送保活包 keepalive.keepaliveinterval = 1000; // 保活包发送间隔1秒 DWORD bytesReturned; WSAIoctl(ClientSocket, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &bytesReturned, NULL, NULL);调整TCP发送缓冲区大小
过小的发送缓冲区可能导致send()等待内核处理,可通过以下代码增大缓冲区(建议设置为64KB或更大,根据实际场景调整):int bufsize = 65536; // 64KB setsockopt(ClientSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&bufsize, sizeof(bufsize));改用异步I/O模型
同步send()在连接空闲后可能触发额外的内核态/用户态切换开销。改用重叠I/O或IOCP(完成端口)等异步模型,让send()以非阻塞方式完成,避免状态切换耗时。优化系统网络参数
在Windows Server 2019中调整以下注册表项(修改后需重启系统):HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveTime:设为1000(毫秒),加快保活探测启动HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay:适当缩短TIME_WAIT状态时长(需注意避免端口耗尽风险)HKLM\SYSTEM\CurrentControlSet\Services\AFD\Parameters\FastSendDatagramThreshold:设为小于10字节,让系统优先快速处理小数据包
内容的提问来源于stack exchange,提问作者Vitaly

