重传次数
**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**来设置。
带TSOPT(Time Stamp)选项的RTO计算
**rto(Retransmission TimeOut):**超时重传时间。
**rtt(Round-Trip Time):**发送端发送包到接收端,接收端再返回ack到发送端的往返时间。
**srtt(Smoothed Round-Trip Time):**平滑的[rtt](https://so.youkuaiyun.com/so/search?q=rtt&spm=1001.2101.3001.7020)。
**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的超时重传
发送端每发一个包都有相应的**计时器**来计时。当发送端在**RTO**时间里,接收到从接收端返回的一个**ack包**,而且这个**ack包**使得的发送端的**发送窗口前移**,那么就计算**新的RTO**。如果一直没有接收到符合要求的**ack包**,当计时器超过**RTO**时间后,**RTO**就等于**y\*RTO**,而且重传丢失超时的包。**y**是2,4,8等指数增长的值,但**y\*RTO**不能超过**tcp\_rto\_max**。
失序报文(out-of-order segment)
报文的**seq值**大于接收端记录的**last\_ack**的报文,也就是前面有空缺报文,称为**失序报文**。
重复报文(duplicate segment)
接收端接收到已经接收过的报文。
重复ACK(Duplicate Acknowledgement)
不能使得发送窗口前移的ACK是**重复ACK**。
选择重传SACK(Selective Acknowledgement)
SACK-Permitted选项:
只用于**SYN包**的选项,在**建立连接**的时候,告诉[SYN](https://so.youkuaiyun.com/so/search?q=SYN&spm=1001.2101.3001.7020)包的接收端,发送方支持SACK。
SACK块:
一对4字节序列号,这表示一个连续的数据段,这个数据段已经被接收端接收了。
SACK选项:
**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**。
伪超时(spurious timeout)
过早判定超时,就是**伪超时**。实际**RTT**显著增长,超过了当前**RTO**的时候,就可能会出现**伪超时**。
伪重传(spurious retransmission)
没有出现数据丢失,却引发了重传,这样的重传成为**伪重传**。伪重传造成的原因可能是**伪超时**、**失序报文、重复报文**或ACK丢失。
重复SACK(DSACK/Duplicate Selective Acknowledgement)
基本的**SACK**机制对于接收端接收到**重复报文**怎么处理没有作出规定。**DSACK**为这个状况提供了方案。在接收到**重复报文**的时候,接收端向发送端发送一个包含**SACK选项**的**重复ACK**。第一个**SACK块**称为**DSACK块**,用于向发送端报告**重复报文**。当**ack包**的**ack值**在**重复报文**的左边界的左边,那么紧接着**DSACK块**的**SACK块**必须要包含**DSACK块**,之后的**SACK块**如同基本的**SACK**机制一样。当**ack包**的**ack值**在**重复报文**中或者在**重复报文**右边界的右边,那么**DSACK块**只是包含**重复报文**的重复部分,之后的**SACK块**如同基本的**SACK**机制一样。发送端接收到这个**重复ACK**,就可以知道导致这个**重复ACK**的是不是**伪重传**。
Eifel检测算法
**Eifel检测算法**利用TSOPT选项来实现机制。当发送端发送一个重传,就记录当时重传的TSOPT选项的TSV,当发送端接收到ACK(发送窗口前进),那么判断这个ACK的TSOPT选项的TSER是否小于之前记录的TSV。如果小于那么这个ACK是原始分组的ACK而不是重传的ACK,所以是一个**伪重传**。
前移RTO恢复(F-RTO)
当超时重传之后,收到的第一个ACK时,发送端再发送新数据,之后再响应一个ACK。判断两个ACK是不是**重复ACK**,如果都不是,那么这个超时重传是个**伪重传**,否则就是一个**伪重传**。这种判断方法只能检测**伪超时**导致的**伪重传**。这是检测伪重传的标准算法。
Eifel响应算法
**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))