tcp可靠性机制

tcp 可靠性机制

可靠性指的是网络层能通信的前提下,保证数据包正确且按序到达对端。 比如发送端发送了“12345678”,那么接收端一定能收到“12345678”,不会乱序“12456783”,也不会少或多数据。

实现 TCP 的可靠传输有以下机制:

1.校验和机制(检测和重传受到损伤的报文段)

2.确认应答机制(保存失序到达的报文段直至缺失的报文到期,以及检测和丢弃重复的报文段)

3.超时重传机制(重传丢失的报文段)

1.校验和

每个 tcp 段都包含了一个检验和字段,用来检查报文段是否收到损伤。如果某个报文段因检验和无效而被检查出受到损伤,就由终点 TCP 将其丢弃,并被认为是丢失了。TCP 规定每个报文段都必须使用 16 位的检验和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 校验和的计算
func Checksum(buf []byte, initial uint16) uint16 {
v := uint32(initial)

l := len(buf)
if l&1 != 0 {
l--
v += uint32(buf[l]) << 8
}

for i := 0; i < l; i += 2 {
v += (uint32(buf[i]) << 8) + uint32(buf[i+1])
}

return ChecksumCombine(uint16(v), uint16(v>>16))
}

2.确认机制

控制报文段不携带数据,但需要消耗一个序号,它也需要被确认,而 ACK 报文段永远不需要确认,ACK 报文段不消耗序号,也不需要被确认。在以前,TCP 只使用一种类型的确认,叫积累确认,目前 TCP 实现还实现了选择确认。

  • 累积确认(ACK)

接收方通告它期望接收的下一个字节的序号,并忽略所有失序到达并被保存的报文段。有时这被称为肯定累积确认。在 TCP 首部的 32 位 ACK 字段用于积累确认,而它的值仅在 ACK 标志为 1 时才有效。举个例子来说,这里先不考虑 tcp 的序列号,如果发送方发了数据包 p1,p2,p3,p4;接受方成功收到 p1,p2,p4。那么接收方需要发回一个确认包,序号为 3(3 表示期望下一个收到的包的序号),那么发送方就知道 p1 到 p2 都发送接收成功,必要时重发 p3。一个确认包确认了累积到某一序号的所有包,而不是对每个序号都发确认包。实际的 tcp 确认的都是序列号,而不是包的序号,但原理是一样的。

累积确认是快速重传的基础,这个后面讲拥塞控制的时候会详细说明。

累积确认是快速重传的基础,这个后面讲拥塞控制的时候会详细说明。

  • 选择确认(SACK)

选择确认 SACK 要报告失序的数据块以及重复的报文段块,是为了更准确的告诉发送方需要重传哪些数据块。SACK 并没有取代 ACK,而是向发送方报告了更多的信息。SACK 是作为 TCP 首部末尾的选项来实现的。
首先是否要启动 sack,应该在握手的时候告诉对方自己是否开启了 sack,这个是通过 kind=4 是选择性确认(Selective Acknowledgment,SACK)选项来实现的。
实际传送 sack 信息的是 kind=5 的选项,其格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
         +--------+--------+
| Kind=5 | Length |
+--------+--------+--------+---------+
| Start of 1st Block |
+--------+--------+--------+---------+
| End of 1st Block |
+--------+--------+--------+---------+
| |
/ . . . . . . /
| |
+--------+--------+--------+---------+
| Start of nth Block |
+--------+--------+--------+---------+
| End of nth Block |
+--------+--------+--------+---------+

sack 的每个块是由两个参数构成的{ Start, End } Start 不连续块的第一个数据的序列号。End 不连续块的最后一个数据的序列号之后的序列号。 该选项参数告诉对方已经接收到并缓存的不连续的数据块,注意都是已经接收的,发送方可根据此信息检查究竟是哪个块丢失,从而发送相应的数据块。 比如下图:
image
如图所示,tcp 接收方在接收到不连续的 tcp 段,可以看出,序号 1~1000,1501~3000,3501~4500 接收到了,但却少了序号 1001~1500,3001~3500 。 前面说了,sack 报告的是已接收的不连续的块,在这个例子中,sack 块的内容为{Start:1501, End:3001},{Start:3501, End:4501}, 注意:这里的 End 不是接收到数据段最后的序列号,而是最后的序列号加 1。

产生确认的情况 确认机制

  1. 当接收方收到了按序到达(序号是所期望的)的报文段,那么接收方就累积发送确认报文段。
  2. 当具有所期望的序号的报文段到达,而前一个按序到达的报文段还没有被确认,那么接收方就要立即发送 ACK 报文段。
  3. 当序号比期望的序号还大的失序报文段到达时,接收方立即发送 ACK 报文段,并宣布下一个期望的报文段序号。这将导致对丢失报文段的快重传。
  4. 当一个丢失的报文段到达时,接收方要发送 ACK 报文段,并宣布下一个所期望的序号。
  5. 如果到达一个重复的报文段,接收方丢弃该报文段,但是应当立即发送确认,指出下一个期望的报文段。
  6. 收到 fin 报文的时候,立即回复确认。

3.重传机制

关于重传的基本概念
RTO 即超时重传时间
RTT 数据包往返时间
平均偏差是指单项测定值与平均值的偏差(取绝对值)之和,除以测定次数。
image
可靠性的核心就是报文段的重传。在一个报文段发送时,它会被保存到一个队列中,直至被确认为止。当重传计时器超时,或者发送方收到该队列中第一个报文段的三个重复的 ACK 时,该报文段被重传。

超时重传的概念很简单,就是一定时间内未收到确认,进行再次发送,但是如何计算重传的时间确实 tcp 最复杂的问题之一,毕竟要适应各种网络情况。TCP 一个连接期间只有一个 RTO 计时器,目前大部分实现都是采用Jacobaon/Karels 算法,详细可以看RFC6298,其计算公式如下,

rto 的计算公式:

1
2
3
4
5
6
7
8
9
10
11
第一次rtt计算: 
SRTT = R
RTTVAR = R/2
RTO = SRTT + max (G, K*RTTVAR)
K = 4

之后:
RTTVAR = (1 - beta) * RTTVAR + beta * |SRTT - R'|
SRTT = (1 - alpha) * SRTT + alpha * R'
RTO = SRTT + max (G, K*RTTVAR)
K = 4

SRTT(smoothed round-trip time)平滑 RTT 时间
RTTVAR(round-trip time variation)RTT 变量,其实就是 rtt 平均偏差
G 表示系统时钟的粒度,一般很小,us 级别。 beta = 1/4, alpha = 1/8

发送方 TCP 的计时器时间到,TCP 发送队列中最前面的报文段(即序列号最小的报文段),并重启计时器。