简介
序列号(Sequence Number) 是 TCP 协议中非常重要的一个概念,以至于不得不专门来学习一下。这篇文章我们就来解开他的面纱.
在 TCP 的设计中,通过TCP协议发送的每个字节都对应于一个序列号. 由于每个字节都有自己的序列号,那么每个字节都可以被对方确认接收.
但是由于 TCP 使用累计确认机制,因此不需要对每个接收到的字节都发送对应的ACK,而是采用确认最后接收到的自己的序列号的方式来进行确认接收的. 举个例子,对于序列号 X, 它意味着 X 之前的所有字节已经被对方接收到(不包含 X 本身)。
序列号这个机制使得 TCP 可以检测到重复的数据包.
本篇文章中将会使用都很多我们在基本部分引入的专业术语,请参考 协议簇:TCP 解析: 基础
系列文章
协议簇:TCP 解析:基础
协议簇:TCP 解析:建立连接
协议簇:TCP 解析:连接断开
协议簇:TCP 解析:Sequence Number
协议簇:TCP 解析:数据传输
Sequence Number
正如我们所知道的,Sequence Number 字段在 TCP 头部占用 32bit 的长度。这也意味着 Sequence Number 是有限的,它的合法值是 0 - 2**32 -1. 因此对于通信双方,在发送 SEQ 时,总是需要将该值与 2 ** 32 取模.
TCP 任何一方在接收到对方发送的数据包之后,都需要对 Sequence Number 进行检查。 最通常的检查包含以下几条:
- 接收到的 Sequence Number 对应于那些我们已经发送还未收到 ACK 的 Sequence Number
- 一个TCP数据段所包含的所有字节都被当前 Sequence Number 覆盖(比如,我们可以将该段从重传队列中移除)
- 接收到的 Sequence Number 是我们正在等待接收的(比如,确保该 Sequence Number 于我们的接收窗口有重叠部分).
概念引入
-
SND.UNA - 最早发送且没有收到ACK的 Sequence Number
[oldest unacknowledged sequence number] -
SND.NXT - 下一个被发送的数据的 Seuqnce Number
[next sequence number to be sent] -
SEG.ACK - 接收到的 TCP 段的 ACK,其中包含了对方期待下一个段的 Sequence Number
[acknowledgment from the receiving TCP (next sequence
number expected by the receiving TCP)] -
SEG.SEQ - 一个 TCP 段的第一个字节的 Sequence Number
[first sequence number of a segment] -
SEG.LEN - 一个 TCP 段中包含的字节的数量
[the number of octets occupied by the data in the segment
(counting SYN and FIN)] -
SEG.SEQ+SEG.LEN-1 - 一个 TCP 段的最后一个字节的 Sequence Number
[last sequence number of a segment] -
RCV.NXT - 下一个期待接收到的段的第一个字节的 Sequence Number. 同时,这个值也代表了接收方的接收窗口的下限.
[next sequence number expected on an incoming segments, and is the left or lower edge of the receive window] -
RCV.NXT+RCV.WND-1 - 下一个期待接收到的段的最后一个字节的 Sequence Number. 同时,这个值也代表了接收方的接收窗口的上限
[last sequence number expected on an incoming segment, and is the right or upper edge of the receive window]
对于数据发送方
在发送了 TCP 数据之后,正常情况下,我们都会收到相应的 ACK.
接收到的 ACK 需要满足条件: SND.UNA < SEG.ACK =< SND.NEXT
当一个处于重传队列的段的SEQ + 它的长度的和小于 ACK 的值,那么这个段已经被对方成功接收,可以从重传队列中移除。
对于数据接收方
接收到的段中包含的 SEQ 需要满足以下条件:
RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND (确保接收到的数据的第一个字节的 SEQ 处于接收窗口中)
OR
RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND (确保接收到的数据的最后一个字节的 SEQ 处于接收窗口中)
满足上述条件中任何一个条件,便可以判断当前接受到的数据部分处于接收窗口.
实际情况中,我们还需要考虑接收窗口为 0 和 空 TCP 段的情况. 那么就需要考虑以下四种情况:
Seg Len | Rcv Wnd | Test |
---|---|---|
0 | 0 | SEG.SEQ = RCV.NXT |
0 | >0 | RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND |
>0 | 0 | not acceptable |
>0 | >0 | RCV.NXT =< SEG.SEQ < RCV.NXT + RCV.WND OR RCV.NXT =< SEG.SEQ+SEG.LEN-1<RCV.NXT+RCV.WND |
注意,当接收窗口为 0 之后,除了ACK之外,无法接收任何其他 TCP 段. (对于上表第三条)
Initial Sequence Number Selection
TCP 协议并不限制在 TCP 的实现中重复使用同一个连接. 一个连接仅仅由一对 socket 标识. (新的连接被成为前一个连接的“化身”[incarnation]) 但是存在一个问题: 如何判断一个TCP段来自前一个连接还是前一个连接的化身? 当一个连接被快速的打开关闭或者一个连接由于内存不足而断掉,恢复后又重新建立的情况下,会尤其明显.
在上述问题中,很有可能两个连接使用相同的序列号发送数据。这种情况下,我们无法区分这个数据包的来源. 因此,对于 TCP 而言,选择一个合适的 Sequence Number 非常重要.
当我们尝试建立一个连接时,我们需要在SYN中附上我们选择的 Sequence Number,也就是 Initial Sequence Number,简称 ISN. ISN 的生成方法如下:
ISN 生成器以当前机器时钟为基础,粗略地每4微妙增加ISN的最低有效位.
这个生成器生成的序列会以 4.55 小时为周期重复,但是设计者认为没有TCP段可以在网络上存活超过这个时间,因此他们认为这样选择 ISN 是非常合理的.
对于每个TCP连接,都包含两个 Sequence Number,分别由 TCP 双方独立生成. 在一个 TCP 连接建立之前,双方应该回想通告自己的 Sequence Number (协议中称之为“synchronize”). 互相通告自己的 Sequence Number 需要每方均发送自己的 ISN 并收到对方发送的 ACK, 以确保对方收到了己方的 ISN.
大体的流程如下:
又由于 TCP 连接是全双工的,在接收的同时可以发送数据,那么2,3步可以合并为一步。最后, 就有了现在的 “三次握手" 了.
END!!!