接收方窗口大小通知
在《TCP滑动窗口与缓冲区》的博文中说过接收方会通知发送方滑动窗口的大小,但是如果应用程序每调用read函数读出一些数据,就通知发送方,那么网络中将会有大量的只通知对方窗口大小的报文,而每个TCP报文都会添加几十字节的头部信息,也就是说这个报文的有效负载非常低,网络带宽将浪费在这些小报文上。实际上RFC有优化的,不会缓冲区一变大,就通知对方接收窗口大小的,很多情况下是同数据报文或者ACK报文一块通知的。
ACK应答优化
TCP是可靠连接的,每个数据报文都得收到对方的ACK应答,才能从发送缓冲区删除,要是定时器等待ACK应答超时,那么会重发报文。但是如果接收方要是每收到一个数据报文,就回复一个没有数据的ACK报文,同第一种情况一样,将会浪费大量的网络资源。实际上TCP会通过接收方要发送的数据报文捎带过去的。如果接受到数据报文的时候,接收方的发送缓冲区没有数据要发送,那么会延迟一小段时间,要是稍后有数据要发送了,就连同数据报文一块发送出去。这就是延迟ACK。当然延迟的时间也不能太长,要不会触发发送端的超时,从而导致发送速度变慢,得不偿失。
发送方小数据包优化
当应用程序多次调用write函数,每次都只发送几个字节的小数据包,如果每次都调用write函数后,就立即发送报文。与前面2点类似,会导致网络带宽利用率不高,因为一个报文里的有效负载占比太小。TCP使用nagle算法限制大批量的小数据包的,其思想是如果飞行中的报文有一个数据报文不足一个MSS,那么需要等到获取到那个小报文的ACK应答报文后,才能发送下一个不足一个MSS的报文。到此你不知道是否有发现nagle算法与ACK延迟发送在互相等待对方?如下图所示假设延迟ACK的时延是20ms:
这个会导致发送速度变慢。为此linux提供了参数TCP_NODELAY参数用于控制是否打开nagle算法:
int on = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
对于实时性要求高的场景可以考虑关闭,不过关闭后一定要测试一下系统的吞吐量等指标,因为对于nagle算法与延迟ACK,TCP已经优化的非常好了。其实对于这种小包,还是应用程序先组装成一个较大的报文,批量发送,这是一种更好的选择。