案例: 在做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数据后,应该重新设置。
|