Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TCP 协议(1) - 简介 #47

Open
bigwolftime opened this issue Apr 11, 2022 · 0 comments
Open

TCP 协议(1) - 简介 #47

bigwolftime opened this issue Apr 11, 2022 · 0 comments
Labels

Comments

@bigwolftime
Copy link
Owner

https://bigwolftime.github.io/TCP/

一. TCP 报文段的首部格式

TCP 是面向字节流的, 但 TCP 传送的数据单元是报文段. 报文段分为首部和数据部分. 以下是关于 TCP 报文段的首部.

源端口 / 目的端口

分别占用 2byte.

序号

在 TCP 字节流的传输过程中, 每一个字节会按照顺序编号, 要传送的字节流的起始序号要在建立连接时设置, 此字段即表示要
发送 的数据第一个字节的序号. 占用 4byte(范围: [0, 2 ^ 32 - 1]), 达到最大值后会归零重新计数.

ex: 报文段的序号为 301, 携带 100byte 数据, 可知最后一个字节的序号为 400, 下一个报文段的序号值应为 401.

确认号

4byte, 指的是: 期望收到对方下一个报文段的第一个数据字节的序号.

以上一个为例, 计算出的下一个报文段的序号为 401, 那么就可以将确认号标记为 401, 表示: 希望下一个报文段的序号
为 401.

数据偏移

占用 4bit, 指示: 报文段的数据起始处 与 报文段的起始处 的距离. 可以理解为 TCP 报文段的首部长度(因为 TCP 分为:
首部和数据, 即: 数据的起始地址 - 报文段的起始地址 = 首部长度).

TCP 报文段首部的前 20byte 固定长度, 但是选项和填充的数据长度可变的, 所以此处需要读取偏移量才能定位到数据.

有一点需注意: 数据偏移的单位为: 4byte(32bit), 例如: 0011(B) 表示首部长度为 4byte * 3 = 12byte.
4bit 可以表示的的范围: [0, 15], 所以 TCP 报文的首部最大长度为: 4byte * 15 = 60byte, 其中首部的前 20byte
长度固定, 所以选项和填充部分的最大长度为 40byte.

保留

占用 6bit, 保留今后使用.

TCP Flags

SYN: Synchronize Sequence Number. 在 TCP 握手阶段发送. SYN=1, ACK=0 表明这是一个连接请求报文段, 若对方
同意连接, 则会再响应报文中置 SYN=1, ACK=1.;
ACK: 确认号的标记, 1 表示确认号字段有效, 0 表示确认号无效. TCP 规定: 连接建立后所有报文段的 ACK 需置 1;
FIN: 完成数据的传输, 要求释放连接. TCP 挥手过程发送;
RST: reset. 表示在 TCP 连接过程出现错误, 需释放当前连接. 还可以用此字段拒绝非法的报文或者连接;
URG: 报文中含有紧急数据, 需提高优先级传送. ex: 程序在远程主机上运行, 用户发送 Ctrl + C 的中断命令, 此时若
不标记为紧急数据, 那么此指令会被存储在 TCP 缓存的末尾, 等到所有的数据被处理完毕才会交给应用程序, 这样就会浪费
许多时间.
PSH: 两个程序通信时, 有时需要输入一个命令后立即收到对方的响应, 此时可以将 PSH 置为 1, 接收方将 PSH 标记为 1
的报文段尽快地交付给应用进程, 而不是等缓存满了再处理;

关于 URG / PSH: 两字段的作用都是提高报文的优先级, 但是实现的方式不同.

URG(紧急位): 数据报文直接交付给应用进程, 不会进入缓冲区;
PSH(急迫位): 此类型报文会进入到缓冲区后, TCP 立刻将缓冲区的数据交给应用进程.

窗口

窗口的大小为 16bit, 可表示的最大值为: 65535. 窗口指的是: 发送报文段的一方的接收窗口, 告诉发送方目前允许发送的数据
量. 可以理解为: 接收方的处理能力有限, 为了能够控制速率, 需要告诉发送方自己当前的处理能力, 发送方会根据此值控制
发送窗口大小, 以达到流量控制的目的. 其值是动态变化的.

校验和

占用 2byte. 校验范围包括报文段的首部和数据两部分.

紧急指针

占用 2byte, 当 URG = 1 时有意义, 指示报文段中紧急数据的末尾位置.

选项

选项部分的长度可变, 最高可传输 40byte, 没有选项需要传输时, TCP 报文段首部长度为 20byte.

二. TCP 可靠传输的实现

介绍以字节为单位的滑动窗口. 假设 A 为数据的发送方, B 为数据的接收方:

1

假定 A 收到了 B 的确认报文段, 其窗口大小为 20byte, 确认号是 31, 则 A 可以根据这两个字段确定出发送窗口的参数:

图中标记的发送窗口的后沿, 表示此前的数据已发送成功并收到了确认, 这些数据可以丢弃掉无需保留; 发送窗口的前沿, 则表示数据不允许发送.
发送窗口由前沿和后沿共同决定的.

后沿的变化情况有 2 种: 不动(即未收到相应的的确认) 和 前移(收到了确认), 不能向后移动, 因为不能撤销已收到的确认, 而且之前的数据很有
可能已被丢弃.

前沿的变化情况也有 2 种: 不动(没有收到确认, 所以不用变; 或者对方的处理能力降低, 通知窗口变小, 此时仅移动后沿) 和 前移. 前沿也有可能向
后沿方向移动, 不过 TCP 的标准中并不建议这样做, 因为发送方此前可能发送了窗口中的许多数据, 现在要收缩的话可能会导致一些错误.

在没有收到 B 确认的情况下, A 可以将窗口内的数据都发送出去, 但还不能丢弃掉, 因为遇到超时情况需要重传. 发送窗口越大, 发送方就可以在收到
确认之前发送更多的数据, 但需要注意, 这也需要接收方有能力及时处理并发出确认报文.

2

假定 A 已经发送了窗口中的编号 31-41 的数据(灰色部分), 此部分已发送但未收到相应确认; 42-50 代表允许但尚未发送

3

A 接收到了 B 的部分确认报文(32, 33), 此时发送窗口后沿还不能移动, 因为没有收到 31 的确认报文(可能丢失或者网络滞留), 此时 B 的确认号
仍为 31(即期望收到的序号), 不能是 32 或者 33.

4

假定 B 接收到了序号 31 的数据, 将 31-33 的数据交付主机后, 给 A 发出确认报文, 其中确认号变为 34, 表示已经收到了序号 33 及其之前的数据.

5

A 收到了 B 的确认报文后, 将窗口的后沿移动至 34, 前沿移动至 53.

6

当 A 将窗口内的所有数据发送完毕, 但没有收到任何确认, 此时 A 的发送窗口已满, 必须停止发送等待 B 的确认, 如果一段时间后仍然没有收到确认
(超时时间由超时计时器控制), 则 A 会进行数据的重传过程.

当数据发送方的发送速率过快, 接收方处理不及时, 导致数据丢失或者发送方判断数据传输超时, 此时就需要流量控制, 通过上文的滑动窗口机制就可以
实现.

三. 超时重传时间的确认

TCP 的下层是互联网环境, 发送的报文段途经的路由不同, 网络速率也不同, 所以对超时重传的时间计算较为复杂. 重传时间定的太短会导致不必要的
重传, 增大网络开销; 定的太长又使网络的延迟增大.

TCP 中使用了一种自适应算法, 它记录一个报文段的发出时间和收到确认的时间, 其时间差为 RTT(报文段的往返时间). 由多个 RTT 进行加权平均得到
RTTs(平滑往返时间), 每测量到一个 RTT 样本时, 会进行如下计算得到 RTTs:

RTTs = (1 - a) * (RTTs) + a * (RTT), a 的取值范围: [0,1)

若 a 趋于 0, 则表示新旧的 RTTs 相差不大; 若 a 趋于 1 则表示新的 RTTs 值受新的 RTT 影响较大.

RFC 2988 推荐 a 取值 0.125

超时重传时间 RTO(Retransmission Time-Out) 应该大于 RTTs: RTO = RTTs + 4 * RTOd (RFC-2988), RTOd 表示 RTT 的偏差的加权平均值,
与 RTTs 和新的 RTT 样本有关.

RTOd 的计算方法:

第一次测量时: RTTd = 0.5 * RTT;
以后的测量: RTTd = (1 - b) * RTTd + b * | RTTs - RTT |, b < 1, 推荐取值 0.25

思考 1

假如发出一个报文段, 由于没有及时收到确认报文而重传, 一段时间后收到了确认, 问题来了: 如何判定此确认报文是对第一次报文的确认, 还是对
重传报文的确认? 无法区分.

如果收到的是对重传报文的确认, 但却当成了首次发送报文的确认, 则会导致 RTT 偏大, 进而影响 RTTs 的取值使其偏大; 若收到的是对首次报文的
确认, 但却当成了重传报文的确认, 则会导致 RTTs 变小, 进而 RTO 变小.

由此, Karn 提出: 计算 RTTs 时, 对于重传的报文, 其 RTT 样本一律丢弃, 这样得到的结果就相对准确.

思考 2

这又引来新的问题, 假设报文段的传输延时增大, 进行超时重传, 但根据 Karn 的结论, 重传报文的 RTT 会被丢弃, 这就导致 RTTs 和 RTO 得不到
更新. 因此又对此算法进行了修正:

报文每重传一次, 就将 RTO 增大到: 旧的重传时间 * 2; 直到不再发生重传时, 才会使用上面的计算公式.

四. 参考

《计算机网络》谢希仁
RFC Document

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant