案例:
在做Server压力测试时发现,客户端给服务器不断发请求,并接受服务器端的响应。发现接收服务器响应的过程中,会出现recv服务器端响应,阻塞40ms的情况,但是查看server端日志,Server都在2ms内将请求处理完成,并给客户端响应。
产生问题的原因:
TCP的延迟确认(Delayed Ack)机制导致的。
服务器端是调用send给客户端响应的,send只是把数据存放到TCP的发送缓冲区,TCP协议栈会不会发送这个数据包,还要看Nagle算法(下面介绍)。
解决办法:
在TCP中,recv到数据后,调用一次setsockopt函数,设置TCP_QUICKACK。
例如: setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (int*){1}, sizeof(int));
产生问题原因的详细分析:
1、延迟确认机制及作用
在《TCP/IP详解卷一:协议》第19章对其进行原理进行了详细描述:TCP在处理交互数据流(即Interactive Data Flow,区别于Bulk Data Flow,即成块数据流,典型的交互数据流如telnet、rlogin等)时,采用了Delayed Ack机制以及Nagle算法来减少小分组数目。
2、TCP的延迟确认机制怎么会导致recv延时呢?
仅仅有TCP的延迟确认机制,并不会导致请求延时的(因为并不是必须等待ACK包发出去,recv系统调用才能返回)。
一般来说,只有当该机制与Nagle算法或拥塞控制(慢启动或拥塞避免)混合作用时,才可能会导致时耗增长。
3、延迟确认机制与Nagle算法:
Nagle算法的规则(可参考tcp_output.c文件里tcp_nagle_check函数注释):
1)如果包长度达到MSS(MSS是最大分段大小Maxitum Segment Size ,MTC是最大传输单元Maxitum Transmission Unit ),则允许发送;
2)如果该包含有FIN,则允许发送;
3)设置了TCP_NODELAY选项,则允许发送;
4)未设置TCP_CORK选项时,若所有发出去的包均被确认,或所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送。
对于规则4),就是说一个TCP连接上最多只能有一个未被确认的小数据包,在该分组的确认到达之前,不能发送其他的小数据包。如果某个小分组的确认被延迟了(案例中的40ms),那么后续小分组的发送就会相应的延迟。
也就是说延迟确认影响的并不只是被延迟确认的那个数据包,而是后续所有的应答包。
4、关于TCP_NODELAY和TCP_CORK选项:
TCP_CORK选项与TCP_NODELAY一样,是控制Nagle化的。
1、打开TCP_NODELAY选项,则意味着无论数据包是多么的小,都立即发送(不考虑拥塞窗口)。
2、如果将TCP连接比喻为一个管道,那TCP_CORK选项的作用就像一个塞子。
设置TCP_CORK选项,就是用塞子塞住管道,而取消TCP_CORK选项,就是将塞子拔掉。
当TCP_CORK选项被设置时,TCP链接不会发送任何的小包,即只有当数据量达到MSS时,才会被发送。
一般当数据传输完成时,通常需要取消该选项,以防被塞住,这样才可以让不够MSS大小的包能及时发出去。
5、为什么TCP_QUICKACK需要在每次recv后重新设置?
因为TCP_QUICKACK不是永久的,所以在每次recv数据后,应该重新设置。
联系客服