❶ TCP之Nagle、Cork、Delay ACK(延迟确认)
[TOC]
TCP协议中的Nagle算法
TCP中的Nagle算法
linux下TCP延迟确认(Delayed Ack)机制导致的时延问题分析
TCP-IP详解:Delay ACK
Nagle算法为了避免网络中存在太多的小数据包,尽可能发送大的数据包。定义为在任意时刻,最多只有一个未被确认的小段。小段为小于MSS尺寸的数据块,未被确认是指数据发出去后未收到对端的ack。
Nagle算法是在网速较慢的时代的产物,目前的网络环境已经不太需要该机制,该算法在linux系统中默认关闭。
1)如果包长度达到MSS,则允许发送;
2)如果该包含有FIN,则允许发送;
3)设置了TCP_NODELAY选项,则允许发送;
4)未设置TCP_CORK选项时,若所有发出去的包均被确认,或所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送。
对于规则4),就是说要求一个TCP连接上最多只能有一个未被确认的小数据包,在该分组的确认到达之前,不能发送其他的小数据包。如果某个小分组的确认被延迟了(案例中的40ms),那么后续小分组的发送就会相应的延迟。也就是说延迟确认影响的并不是被延迟确认的那个数据包,而是后续的应答包。
tcp默认使用nagle算法,最大限度的进行缓存。
优点 :避免网络中充斥着许多小数据块,降低网络负载,减少网络拥塞,提高网络吞吐
缺点 :客户端的延迟会增加,实时性降低,不适合延时要求尽量小的场景;且对于大文件传输这种场景,会降低传输速度。
用TCP_NODELAY选项可以禁止Negale 算法。此时,应用程序向内核递交的每个数据包都会立即发送出去。需要注意的是,虽然禁止了Negale 算法,但网络的传输仍然受到TCP确认延迟机制的影响。
TCP在接收到对端的报文后,并不会立即发送ack,而是等待一段时间发送ack,以便将ack和要发送的数据一块发送。当然ack不能无限延长,否则对端会认为包超时而造成报文重传。linux采用动态调节算法来确定延时的时间。
TCP在何时发送ACK的时候有如下规定:
优点 :减少了数据段的个数,提高了发送效率
缺点 :过多的delay会拉长RTT(往返时延)
可以通过TCP_QUICKACK这个选项来启动快速ACK:
所谓的CORK就是塞子的意思,形象地理解就是用CORK将连接塞住,使得数据先不发出去,等到拔去塞子后再发出去。Cork算法与Nagle算法类似,也有人把Cork算法称呼为super-Nagle。Nagle算法提出的背景是网络因为大量小包小包而导致利用率低下产生网络拥塞,网络发生拥塞的时候性能还会进一步下降,因此Nagle算法通过ACK确认包来触发新数据包的发送(ACK确认包意味着对端已经接收到了一个数据包,即有一个数据包已经离开中间网络,此时可以在向中间网络注入一个数据包块,这称呼为self-clocking)。Cork算法则更为激进,一旦打开Cork算法,TCP不关注是否有收到ACK报文,只要当前缓存中累积的数据量不足以组成一个full-sized数据包就不会将数据包发出,直到一个RTO超时后才会把不满足一个full-sized的数据包发出去(实际上是通过一个persist timer来设置的这个RTO定时时间,persist timer超时的时候就会强制发送)。
linux中可以通过TCP_CORK选项来设置socket打开Cork算法。TCP_NODELAY选项和TCP_CORK选项在linux早期版本是互斥的,但目前最新的linux版本已经可以同时打开这两个选项了,但是TCP_CORK选项的优先级要比TCP_NODELAY选项的优先级要高。
Nagle算法和CORK算法非常类似,但是它们的着眼点不一样,Nagle算法主要避免网络因为太多的小包(协议头的比例非常之大)而拥塞,而CORK算法则是为了提高网络的利用率,使得总体上协议头占用的比例尽可能的小.如此看来这二者在避免发送小包上是一致的,在用户控制的层面上,Nagle算法完全不受用户socket的控制,你只能简单的设置TCP_NODELAY而禁用它,CORK算法同样也是通过设置或者清除TCP_CORK使能或者禁用之,然而Nagle算法关心的是网络拥塞问题,只要所有的ACK回来则发包,而CORK算法却只关心内容,在前后数据包发送间隔很短的前提下(很重要,否则内核会帮你将分散的包发出),即使你是分散发送多个小数据包,你也可以通过使能CORK算法将这些内容拼接在一个包内,如果此时用Nagle算法的话,则可能做不到这一点.
优点 :提高网络的利用率
缺点 :对实时性有影响
使用TCP_CORK参数进行配置
❷ liunx怎么解决tcp连接timeout过长
不明白你说的老化时间,关于established的时间,摘录个别人的心得吧 TCP协议有个超时重传机制,想必大家都比较熟悉。TCP协议是一种传输可靠的协议,因此这个机制是必不可少的。那么今天要探讨的是在发送队列还有数据的情况下,网络连接异常断开后,协议栈是到底是怎样来处理这些数据的,资源又是怎样被回收的呢? 我这里先给出几个测试的结果: 1、修改linux系统下的tcp_retries2为1,当socket发送队列有一定数据时,突然切断网线,造成异常断链的场景,此时,大约过了1秒,用netstat观察established的连接消失; 2、继续把该参数修改为15,重复上面的实验,发现大约过了15分钟后,established的连接才断开; 3、把参数再次修改为5,大约过了7秒,连接消失 /proc/sys/net/ipv4/tcp_retries2 思考:TCP的超时后,重传的次数和重传的时间间隔是影响连接断开的主要参数。但是,从上面的实验数据来看,似乎没有什么规律。查阅linux帮助文档,发现这个重传的时间间隔与RTO有关,而这个参数又是协议栈通过检测网络状况而实时改变的。
❸ TCP协议总结
Transmission Control Protocol,传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP协议的目的是: 在不可靠传输的IP层之上建立一套可靠传输的机制。 TCP的可靠只是对于它自身来说的, 甚至是对于socket接口层, 两个系统就不是可靠的了, 因为发送出去的数据, 没有确保对方真正的读到(所以要在业务层做重传和确认机制)。
可靠传输的第一要素是 确认 , 第二要素是 重传 , 第三要素是 顺序 。 任何一个可靠传输的系统, 都必须包含这三个要素。 数据校验 也是必要的。
传输是一个广义的概念, 不局限于狭义的网络传输, 应该理解为通信和交互. 任何涉及到通信和交互的东西, 都可以借鉴TCP的思想。无论是在UDP上实现可靠传输或者创建自己的通信系统,无论这个系统是以API方式还是服务方式,只要是一个通信系统,就要考虑这三个要素。
SeqNum的增加是和传输的字节数相关的。 上图中,三次握手后,来了两个Len:1440的包,而第二个包的SeqNum就成了1441。然后第一个ACK回的是1441(下一个待接收的字节号),表示第一个1440收到了。
网络上的传输是没有连接的,包括TCP也是一样的 。而TCP所谓的“连接”,其实只不过是在通讯的双方维护一个“连接状态”,让它看上去好像有连接一样。所以,TCP的状态变换是非常重要的。
查看各种状态的数量
ss -ant | awk '{++s[$1]} END {for(k in s) print k,s[k]}'
通过三次握手完成连接的建立
三次握手的目的是交换通信双方的初始化序号,以保证应用层接收到的数据不会乱序,所以叫SYN(Synchronize Sequence Numbers)。
ISN是不能hard code的,不然会出问题的。比如:如果连接建好后始终用1来做ISN,如果client发了30个segment过去,但是网络断了,于是client重连,又用了1做ISN,但是之前连接的那些包到了,于是就被当成了新连接的包,此时,client的Sequence Number可能是3,而Server端认为client端的这个号是30了。全乱了。RFC793中说,ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作,直到超过232,又从0开始。这样,一个ISN的周期大约是4.55个小时。因为,我们假设我们的TCP Segment在网络上的存活时间不会超过Maximum Segment Lifetime(MSL),所以,只要MSL的值小于4.55小时,那么,我们就不会重用到ISN。
如果Server端接到了Clien发的SYN后回了SYN-ACK,之后Client掉线了,Server端没有收到Client返回的ACK,那么,这个连接就处于一个中间状态,即没成功,也没失败。于是,Server端如果在一定时间内没有收到的ACK会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻番,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 26 -1 = 63s,TCP才会断开这个连接。
客户端给服务器发了一个SYN后,就下线了,于是服务器需要默认等63s才会断开连接,这样,攻击者就可以把服务器的SYN连接的队列耗尽,让正常的连接请求不能处理。
于是,Linux下给了一个叫tcp_syncookies的参数来应对这个事:当SYN队列满了后,TCP会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence Number发回去(又叫cookie),此时服务器并没有保留客户端的SYN包。如果是攻击者则不会有响应,如果是正常连接,则会把这个SYN Cookie发回来,然后服务端可以通过cookie建连接(即使你不在SYN队列中)。
千万别用tcp_syncookies来处理正常的大负载的连接的情况。因为sync cookies是妥协版的TCP协议,并不严谨。应该调整三个TCP参数:tcp_synack_retries减少重试次数,tcp_max_syn_backlog增大SYN连接数,tcp_abort_on_overflow处理不过来干脆就直接拒绝连接
因为TCP是全双工的,因此断开连接需要4次挥手,发送方和接收方都需要发送Fin和Ack。如果两边同时断连接,那就会就进入到CLOSING状态,然后到达TIME_WAIT状态。
指的是报文段的最大生存时间,如果报文段在网络中活动了MSL时间,还没有被接收,那么会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是两分钟,不过实际上不同的操作系统可能有不同的设置,以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒
主动关闭的一方会进入TIME_WAIT状态,并且在此状态停留两倍的MSL时长。由于TIME_WAIT的存在,大量短连接会占有大量的端口,造成无法新建连接。
主动关闭的一方发出 FIN包,被动关闭的一方响应ACK包,此时,被动关闭的一方就进入了CLOSE_WAIT状态。如果一切正常,稍后被动关闭的一方也会发出FIN包,然后迁移到LAST_ACK状态。
CLOSE_WAIT状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT状态,那么就意味着被动关闭的一方没有及时发出FIN包。
TCP要保证所有的数据包都可以到达,所以,必需要有重传机制。
接收端给发送端的Ack确认只会确认最后一个连续的包 ,比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack 3,然后收到了4(注意此时3没收到),此时的TCP会怎么办?我们要知道,因为正如前面所说的,SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了
但总体来说都不好。因为都在等timeout,timeout可能会很长
不以时间驱动,而以数据驱动重传
如果包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传
Selective Acknowledgment, 需要在TCP头里加一个SACK的东西,ACK还是Fast Retransmit的ACK,SACK则是汇报收到的数据碎版,在发送端就可以根据回传的SACK来知道哪些数据到了,哪些没有收到
重复收到数据的问题,使用了SACK来告诉发送方有哪些数据被重复接收了
经典算法:Karn/Partridge算法,Jacobson/Karels算法
TCP必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包
Advertised-Window :接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来
接收端LastByteRead指向了TCP缓冲区中读到的位置,NextByteExpected指向的地方是收到的连续包的最后一个位置,LastByteRcved指向的是收到的包的最后一个位置,我们可以看到中间有些数据还没有到达,所以有数据空白区。
发送端的LastByteAcked指向了被接收端Ack过的位置(表示成功发送确认),LastByteSent表示发出去了,但还没有收到成功确认的Ack,LastByteWritten指向的是上层应用正在写的地方。
接收端在给发送端回ACK中会汇报自己的AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1;
收到36的ack,并发出了46-51的字节
如果Window变成0了,发送端就不发数据了
如果发送端不发数据了,接收方一会儿Window size 可用了,怎么通知发送端呢:TCP使用了Zero Window Probe技术,缩写为ZWP,也就是说,发送端在窗口变成0后,会发ZWP的包给接收方,让接收方来ack他的Window尺寸,一般这个值会设置成3次,每次大约30-60秒。如果3次过后还是0的话,有的TCP实现就会发RST把链接断了。
如果你的网络包可以塞满MTU,那么你可以用满整个带宽,如果不能,那么你就会浪费带宽。避免对小的window size做出响应,直到有足够大的window size再响应。
如果这个问题是由Receiver端引起的,那么就会使用David D Clark’s 方案。在receiver端,如果收到的数据导致window size小于某个值,可以直接ack(0)回sender,这样就把window给关闭了,也阻止了sender再发数据过来,等到receiver端处理了一些数据后windows size大于等于了MSS,或者receiver buffer有一半为空,就可以把window打开让send 发送数据过来。
如果这个问题是由Sender端引起的,那么就会使用著名的 Nagle’s algorithm。这个算法的思路也是延时处理,他有两个主要的条件:1)要等到 Window Size >= MSS 或是 Data Size >= MSS,2)等待时间或是超时200ms,这两个条件有一个满足,他才会发数据,否则就是在攒数据。
TCP_CORK是禁止小包发送,而Nagle算法没有禁止小包发送,只是禁止了大量的小包发送
TCP不是一个自私的协议,当拥塞发生的时候,要做自我牺牲
拥塞控制的论文请参看 《Congestion Avoidance and Control》
主要算法有:慢启动,拥塞避免,拥塞发生,快速恢复,TCP New Reno,FACK算法,TCP Vegas拥塞控制算法
TCP网络协议及其思想的应用
TCP 的那些事儿(上)
TCP 的那些事儿(下)
tcp为什么是三次握手,为什么不是两次或四次?
记一次TIME_WAIT网络故障
再叙TIME_WAIT
tcp_tw_recycle和tcp_timestamps导致connect失败问题
tcp短连接TIME_WAIT问题解决方法大全(1)- 高屋建瓴
tcp短连接TIME_WAIT问题解决方法大全(2)- SO_LINGER
tcp短连接TIME_WAIT问题解决方法大全(3)- tcp_tw_recycle
tcp短连接TIME_WAIT问题解决方法大全(4)- tcp_tw_reuse
tcp短连接TIME_WAIT问题解决方法大全(5)- tcp_max_tw_buckets
TCP的TIME_WAIT快速回收与重用
浅谈CLOSE_WAIT
又见CLOSE_WAIT
PHP升级导致系统负载过高问题分析
Coping with the TCP TIME-WAIT state on busy Linux servers
❹ Linux上TCP的几个内核参数调优
Linux作为强大操作系统提供了丰富的内核参数进行调优,TCP内核参数调优尤其重要。本文聚焦于内网环境下的TCP参数调优,分享一些实践经验。
调优清单包括:tcp_max_syn_backlog、somaxconn、tcp_abort_on_overflow。这三个参数关联内核TCP连接缓冲队列,设定过小可能导致连接被无端丢弃,引起诡异现象,建议增大Backlog队列大小以避免问题。
设置tcp_abort_on_overflow可避免连接被意外丢弃。同时,确保Java应用正确配置Backlog参数,避免默认值过小导致连接问题。
考虑tcp_tw_recycle参数可能带来的负面影响,如NAT环境下连接成功率降低。高版本内核已优化此参数,如遇TIME_WAIT问题,调整相关参数可缓解。
优化tcp_syn_retries参数,设置合适的重传次数以改善连接建立时的超时时间。Java应用通常可通过API设置超时时间,降低依赖于内核参数的重要性。
调整tcp_retries2参数,用于计算传输过程中的重传次数,对特定场景如长ReadTimeout设置有显著影响。优化此参数可减少宕机时的响应延迟,但资源隔离策略也是关键。
在物理机宕机与进程宕机情形下,内核行为存在差异。物理机宕机会导致内核发送reset,减轻线程阻塞问题。
考虑tcp_slow_start_after_idle参数,Linux默认开启。关闭此参数可提高某些请求的传输速度,适用于网络条件变化频繁的场景。在内网系统间调用中,通常无需启用此功能。
初始CWND大小对请求速率有显著影响。Linux 2.6.32及以前版本初始CWND为(2-4)个mss,现代版本调整为RFC 6928推荐的10段,即14K左右,更适合内网环境。
总之,Linux内核提供丰富参数供调优,选择最适合当前环境的组合至关重要。潜心研究,找到最佳实践,可显著提升系统性能。