持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
TCP 报文中用来描述当前数据首字节的索引(序列号 seqno)是32位类型的,这意味着在处理上增加了一些需要考虑的东西:
-
由于 32位类型最大能表达的值是 4GB,存在上溢的可能。因此当 32位的 seqno 上溢后,下一个字节的 seqno 就重新从 0 开始。
-
处于安全性考虑,以及避免与之前的 TCP 报文混淆,TCP 需要让每个 seqno 都不可被猜测到,并且降低重复的可能性。因此 TCP seqno 不会从 0 开始,而是从一个 32 位随机数起步(称为初始序列号 ISN)。
而 ISN 是表示 SYN 包(用以表示TCP 流的开始)的序列号。
-
TCP 流的逻辑开始数据包和逻辑结束数据包各占用一个 seqno。除了确保接收到所有字节的数据以外,TCP 还需要确保接收到流的开头和结尾。 因此,在 TCP 中,SYN(流开始)和 FIN(流结束)控制标志将会被分别分配一个序列号(SYN标志占用的序列号就是ISN)。
流中的每个数据字节也占用一个序列号。
但需要注意的是,SYN 和 FIN 不是流本身的一部分,也不是传输的字节数据。它们只是代表字节流本身的开始和结束。
字节索引类型一多就容易乱。当前总共有三种索引:
- 序列号 seqno。从 ISN 起步,包含 SYN 和 FIN,32 位循环计数
- 绝对序列号 absolute seqno。从 0 起步,包含 SYN 和 FIN,64 位非循环计数
- 流索引 stream index。从 0 起步,排除 SYN 和 FIN,64 位非循环计数。
序列号和绝对序列号之间相互转换稍微有点麻烦,因为序列号是循环计数的。在该实验中,CS144 使用自定义类型 WrappingInt32 表示序列号,并编写了它与绝对序列号之间的转换。
WrappingInt32
class WrappingInt32 {
private:
uint32_t _raw_value; //!< The raw 32-bit stored integer
public:
//! Construct from a raw 32-bit unsigned integer
explicit WrappingInt32(uint32_t raw_value) : _raw_value(raw_value) {}
uint32_t raw_value() const { return _raw_value; } //!< Access raw stored value
};
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn);//将64位转为WrappingInt32
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint);//将WrappingInt32转为64位