tcp通过设置R1值来确定:愿意尝试重传多少次之后,向IP层传递重新评估当前的IP路径。
tcp通过设置R2值来确定:愿意尝试重传多少次之后,放弃当前连接。
Linux通过分别设置net.ipv4.tcp_retries1和net.ipv4.tcp_retries2来设置R1和R2值。
对于建立连接的SYN与SYN_ACK重传次数,Linux通过分别设置net.ipv4.tcp_syn_retries和net.ipv4.tcp_synack_retries来设置。
rto(Retransmission TimeOut):超时重传时间。
rtt(Round-Trip Time):发送端发送包到接收端,接收端再返回ack到发送端的往返时间。
srtt(Smoothed Round-Trip Time):平滑的rtt。
mdev(Mean Deviation):瞬时偏差。
mdev_max:瞬时偏差最大值。
m:RTT样本。
last_ack:接收端的记录,接收端最后一次发送ack的ack序号。
tsv:发送端发给接收端的包中TSOPT选项中的数据。
ts_recent:接收端的记录,接收端最前一个未回复ack的包所带的tsv的副本。
tser:接收端回复发送端的包中TSOPT选项中的数据。
tcp_rto_min:最少rto
tcp_rto_max:最大rto
刚开始tcp建立连接的时候:
1、发送端给接收端发送syn包,包含了seq值和发送端的时间戳ts1(存储在tsv)。
2、接收端返回发送端ack包,这时last_ack记录了ack包的ack值,ack包包含了ts1(存储在tser)。
3、发送端收到接收端返回的ack包后,发送端初始化m、srtt、mdev、mdev_max和rto。这时候发送端的时间戳是ts2。
m = ts2 - ts1
srtt = m
mdev = srtt/2
mdev_max = max(mdevc, tcp_rto_min)
rto = srtt + 4*(mdev_max)
tcp建立连接后的通讯:
1、当接收端接收到发送端发送的一个包含了seq值和发送端的时间戳ts1(存储在tsv)的包后,如果接收端的last_ack与seq值相等,那么把ts1存储到ts_recent中。
2、接收端返回一个包含了ts1(存储在tser)的ack包,而且接收端把ack包的ack值存储到last_ack。
3、发送端收到接收端返回的ack包后,发送端修改m、srtt、mdev、mdev_max和rto。这时候发送端的时间戳是ts2。
m = ts2 - ts1
当m大于等于srtt - mdev的时候:mdev = mdev * (3/4) + |m - srtt| * (1/4)
当m小于srtt - mdev的时候:mdev = mdev * (31/32) + |m - srtt| * (1/32)
以上两条是为解决当m远远小于srtt的时候会导致mdev上升的问题。
mdev_max = max(mdev_max, mdev)
srtt = srtt * (7/8) + m * (1/8)
rto = srtt + 4 * mdev_max 接收端的last_ack和ts_recent的设计目的是为了在ts_recent中存储最早的一个未经确认的包所携带的tsv。这样子只要接收端返回一个ack包,那么ack包中的tser存储的就是最早的一个未经确认的包所携带的tsv。这样子发送端收到ack包之后的处理就可以解决发送端发的包与返回的ack包并不一一对应的问题。
发送端每发一个包都有相应的计时器来计时。当发送端在RTO时间里,接收到从接收端返回的一个ack包,而且这个ack包使得的发送端的发送窗口前移,那么就计算新的RTO。如果一直没有接收到符合要求的ack包,当计时器超过RTO时间后,RTO就等于y*RTO,而且重传丢失超时的包。y是2,4,8等指数增长的值,但y*RTO不能超过tcp_rto_max。
报文的seq值大于接收端记录的last_ack的报文,也就是前面有空缺报文,称为失序报文。
接收端接收到已经接收过的报文。
不能使得发送窗口前移的ACK是重复ACK。
只用于SYN包的选项,在建立连接的时候,告诉SYN包的接收端,发送方支持SACK。
一对4字节序列号,这表示一个连续的数据段,这个数据段已经被接收端接收了。
SACK选项包含了n个SACK块的信息,长度是8*n + 2字节。每个SACK块之间都是互相独立的而且不重复包含的。第一个SACK块(紧接着选项长度后的SACK块)的数据段必须包含最近一次接收到的报文的序列号范围,之后的SACK块从之前的重复ACK中的SACK块中从时间上由近到远排列。通过包含SACK选项的重复ACK,可以知道接收端的接收数据的空缺。
注意,tcp的头部长度最长是60字节(由tcp头部的以32位字为单位的4位头部长度限制),tcp的基本tcp头部为20字节,其中n最大为4,而且由于SACK选项都是与TSOPT选项一起用的,所以n最大是3。
过早判定超时,就是伪超时。实际RTT显著增长,超过了当前RTO的时候,就可能会出现伪超时。
没有出现数据丢失,却引发了重传,这样的重传成为伪重传。伪重传造成的原因可能是伪超时、失序报文、重复报文或ACK丢失。
基本的SACK机制对于接收端接收到重复报文怎么处理没有作出规定。DSACK为这个状况提供了方案。在接收到重复报文的时候,接收端向发送端发送一个包含SACK选项的重复ACK。第一个SACK块称为DSACK块,用于向发送端报告重复报文。当ack包的ack值在重复报文的左边界的左边,那么紧接着DSACK块的SACK块必须要包含DSACK块,之后的SACK块如同基本的SACK机制一样。当ack包的ack值在重复报文中或者在重复报文右边界的右边,那么DSACK块只是包含重复报文的重复部分,之后的SACK块如同基本的SACK机制一样。发送端接收到这个重复ACK,就可以知道导致这个重复ACK的是不是伪重传。
Eifel检测算法利用TSOPT选项来实现机制。当发送端发送一个重传,就记录当时重传的TSOPT选项的TSV,当发送端接收到ACK(发送窗口前进),那么判断这个ACK的TSOPT选项的TSER是否小于之前记录的TSV。如果小于那么这个ACK是原始分组的ACK而不是重传的ACK,所以是一个伪重传。
当超时重传之后,收到的第一个ACK时,发送端再发送新数据,之后再响应一个ACK。判断两个ACK是不是重复ACK,如果都不是,那么这个超时重传是个伪重传,否则就是一个伪重传。这种判断方法只能检测伪超时导致的伪重传。这是检测伪重传的标准算法。
Eifel检测算法与F-RTO通过检查ACK或原始传输来判定伪重传,称为伪超时。DSACK通过检测伪超时引发的重传所返回的ACK来判定伪重传,称为迟伪超时。
重传计时器超时后,会记录两个变量:
srtt_prev = srtt + 2(G) G代表TCP的始终粒度。
rttvar_prev = rttvar
通过上述的Eifel检测算法与F-RTO检测出伪超时,DSACK检测出迟伪超时。
响应操作。如果是伪超时,那么把snd_nxt修改为snd_max。如果是迟伪超时,那么snd_nxt不修改。这些情况都要重新设置拥塞控制状态,并且一旦接收到重传计时器超市后发送的报文的ACK,那么如下设置:
srtt = max(srtt_prev, m) m是时间戳样本值。
rttvar = max(rttvar_pre, m/2)
rto = srtt + max(G,4(rttvar))
本文章主要参考《TCP/IP详解 卷一:协议》,本人初学,如果有不同意见欢迎留言。