协议簇: TCP 解析 - Sequence Number

Oct 11, 2021

目录


简介


序列号(Sequence Number) 是 TCP 协议中非常重要的一个概念,以至于不得不专门来学习一下。这篇文章我们就来解开他的面纱.

在 TCP 的设计中,通过TCP协议发送的每个字节都对应于一个序列号. 由于每个字节都有自己的序列号,那么每个字节都可以被对方确认接收.

但是由于 TCP 使用累计确认机制,因此不需要对每个接收到的字节都发送对应的ACK,而是采用确认最后接收到的自己的序列号的方式来进行确认接收的. 举个例子,对于序列号 X, 它意味着 X 之前的所有字节已经被对方接收到(不包含 X 本身)。

序列号这个机制使得 TCP 可以检测到重复的数据包.

本篇文章中将会使用都很多我们在基本部分引入的专业术语,请参考 协议簇:TCP 解析 - 基础


系列文章



Sequence Number


正如我们所知道的,Sequence Number 字段在 TCP 头部占用 32bit 的长度。这也意味着 Sequence Number 是有限的,它的合法值是 0 - 2^32 -1. 因此对于通信双方,在发送 SEQ 时,总是需要将该值与 2^32 取模.

TCP 任何一方在接收到对方发送的数据包之后,都需要对 Sequence Number 进行检查。 最通常的检查包含以下几条:

  1. 接收到的 Sequence Number 对应于那些我们已经发送还未收到 ACK 的 Sequence Number
  2. 一个TCP数据段所包含的所有字节都被当前 Sequence Number 覆盖(比如,我们可以将该段从重传队列中移除)
  3. 接收到的 Sequence Number 是我们正在等待接收的(比如,确保该 Sequence Number 于我们的接收窗口有重叠部分).

概念引入


  1. SND.UNA - 最早发送且没有收到ACK的 Sequence Number

    [oldest unacknowledged sequence number]

  2. SND.NXT - 下一个被发送的数据的 Seuqnce Number

    [next sequence number to be sent]

  3. SEG.ACK - 接收到的 TCP 段的 ACK,其中包含了对方期待下一个段的 Sequence Number

    [acknowledgment from the receiving TCP (next sequence number expected by the receiving TCP)]

  4. SEG.SEQ - 一个 TCP 段的第一个字节的 Sequence Number

    [first sequence number of a segment]

  5. SEG.LEN - 一个 TCP 段中包含的字节的数量

    [the number of octets occupied by the data in the segment(counting SYN and FIN)]

  6. SEG.SEQ+SEG.LEN-1 - 一个 TCP 段的最后一个字节的 Sequence Number

    [last sequence number of a segment]

  7. RCV.NXT - 下一个期待接收到的段的第一个字节的 Sequence Number. 同时,这个值也代表了接收方的接收窗口的下限.

    [next sequence number expected on an incoming segments, and is the left or lower edge of the receive window]

  8. 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

注意,当接收窗口为 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!!!


Tags