运输层之 TCP 报文段首部格式 与可靠传输的实现

每篇一句:方如棋盘,圆如棋子,动如棋生,静如棋死;  ——《曾国藩》

TCP 报文段首部格式

TCP 传送的数据单元是 报文段。一个 TCP 报文段可分为 首部数据 两部分。首部的前 20 个字节是固定的,后面有 4n 字节是根据需要而增加的选项


首部固定部分各字段意义:

1.源端口和目的端口

各占 2 个字节。分别为 源端口号目的端口号

2.序号

占 4 字节。序号范围是 [0, 2^32 - 1],使用 mod 2^32 运算。在一个 TCP 连接中传送的字节流中的每一个字节都按顺序编号。首部中的序号字段值则指 本报文段所发送的数据的第一个字节的序号。这个字段也叫做 “报文段序号

3.确认号

占 4 字节。是期望收到对方下一个报文段的第一个数据字节的序号

若确认号为 N,则表明序号 N - 1 为止的所有数据都以正确受到

4.数据偏移

占 4 位。它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处的距离,实际上是 TCP 报文的首部长度。数据偏移的最大值是 60 字节,即 TCP 首部的最大长度

5.保留

占 6 位,保留今后使用,但目前置为 0

下面有 6 个控制位,用来说明本报文段的性质

6.紧急 URG

当 URG = 1 时,表明紧急指针字段有效。告诉系统此报文段中有紧急数据,应尽快传送,而不是按照原来的排队顺序来传送

发送方会 把紧急数据插入到本报文段数据的最前面。而紧急数据后面的数据仍是 普通数据。这要与首部中的 紧急指针 字段配合使用

7.确认 ACK

仅当 ACK = 1 时 确认号字段 才有效。TCP 规定,在连接建立后,所有传送的报文段必须把 ACK 置为 1

8.推送 PSH

两个应用进程进行通信时,在一端的应用进程希望在键入一个命令后立即能够收到对方的响应,就可以使用推送操作。( 一般使用较少 )

9.复位 RST

当 RST 置为 1 时,表明 TCP 连接中出现严重差错,必须释放连接,然后再重新建立连接。还用来拒绝一个非法的报文段或拒绝一个打开一个连接

10.同步 SYN

在连接建立时用来同步序号。SYN 置为 1 表明这是一个 连接请求或连接接受响应报文

11.终止 FIN

用来释放一个连接。当 FIN 置为 1 时,表明 此报文段的发送方数据已发送完毕,并要求释放连接

12.窗口

占 2 字节。窗口值是 [0, 2^16 - 1]之间的整数。窗口指的是发送本报文段的一方的 接收窗口。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量( 以字节为单位 )

窗口字段明确指出了 现在允许对方发送的数据量。窗口值经常在动态变化着

13.校验和

占 2 字节。校验和字段检验的范围包括首部和数据两部分。和 UDP 用户数据报一样,在计算时,在 TCP 报文段前加上 12 个字节的伪首部

伪首部和 UDP 用户数据报的伪首部一样。但把第 4 个字段的 17 改为 6。把第 5 字段中的 UDP 长度改为 TCP 长度

14.紧急指针

占 2 个字节。紧急指针仅在 URG 置为 1 时有效,它指出 本报文段中紧急数据的字节数。因此,紧急指针指出了紧急数据的末尾在报文段中的位置

15.选项

长度可变,最长可达 40 字节。

1).最大报文段长度 MSS

MSS 是每一个 TCP 报文段中的 数据字段 的最大长度。数据字段加上 TCP 首部字段才等于整个的 TCP 报文段。

在传输数据时,若选择较小的 MSS 长度,网络利用率就降低。若 TCP 报文段过长,在 IP 层传输时就可能分解成多个段数据报片。如果出错还要重传。这些都使开销增大。

因此,MSS 应该尽可能大些,只要在 IP 层传输时不需要分片就行。一般 MSS 的默认值是 536 字节。

2).窗口扩大选项

窗口扩大选项是为了扩大窗口。占 3 个字节。其中有一个字节表示 移位值 S。新的窗口值等于 TCP 首部中的窗口位数从 16 增大到 ( 16 + S )。移位值允许使用的最大值是 14,相当于窗口最大值增大到 2^(16+14) - 1 = 2^30 - 1。

3).时间戳选项

占 10 个字节。其中最主要的字段是 时间戳值 字段和 时间戳回送 字段( 4 字节 )。有以下两个功能:

  1. 用来计算返回时间 RTT。发送方在发送报文段时把当前时间值放入时间戳字段,接收方在确认该报文段时把时间戳字段复制到时间戳回送回答字段。发送方在收到确认报文后,就可以准确地计算出 RTT 来
  2. 用于处理 TCP 序号超过 2^32的情况,又称 防止序号绕回 PAWS。TCP 报文段序号只有 32 位,在使用高速网络时,序号很有可能重复,可以加上时间戳来区分新旧报文段。

4).选择确认选项( 后面介绍 )

可靠传输的实现

以字节为单位的滑动窗口

TCP 的滑动窗口以字节为单位。

现在假定 A 收到了 B 发来的确认报文段,其中 窗口 值是 20,确认号 是 31 ( 这表明 B 期望收到的下一个序列号是 31,而序号 30 为止 的数据已经收到了 )。根据这两个数据,A 构造出自己的发送窗口,如下图

发送方 A 的发送窗口表示:在没有收到 B 的确认的情况下,A 可以连续把窗口内的数据都发送出去。凡是已经发送出去的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用。

发送窗口里面的序号表示允许发送的序号。窗口越大,在收到对方确认之前可以连续发送更多的数据,传输效率也更高。而且,A 的发送窗口不能超过 B 的接收窗口数值。发送方的发送窗口大小还要收到网络拥塞程度的影响。

发送窗口后沿的后面部分表示已经发送且已收到了确认,这些数据就会不再保留。而发送窗口前沿的前面部分表示不允许发送的,因为接收方都没有为这部分数据保留临时存放的缓存空间。

发送窗口的位置由窗口前沿和后沿的位置确定。发送窗口后沿 的变化情况有两种可能:即 不动 ( 没有收到新的确认 ) 和 前移 ( 收到了新的确认 )。

发送窗口前沿通常是不断向前移动的,但也可能不动或者向后收缩。前沿不动有两种情况:

  1. 没有收到新的确认,对方通知的窗口大小也不变
  2. 收到了新的确认但对方通知的窗口缩小了,使得发送窗口前沿正好不动

而前沿也有可能向后收缩

  1. 对方通知的窗口缩小,但很可能发送方在收到通知以前已经发送了窗口的许多数据,但现在又不让发送了


现在假定 A 发送了序号为 31 ~ 41 的数据。这时,发送窗口位置并为改变,但发送窗口靠后面有 11 个字节表示已发送但未收到确认。而发送窗口靠前面的 9 个字节是允许发送但尚未发送的

从上图可以,可以看出要描述一个发送窗口的状态需要三个指针:P1. P2 和 P3。指针都是指向字节的序号。各部分的意义如下:

  • 小于 P1:已发送并已收到的确认的部分
  • P2 - P1:已发送但未收到确认
  • P3 - P2:允许发送但尚未发送
  • P3 - P1:A 的发送窗口
  • 大于 P3:不允许发送

再看 B 的接收窗口。30 号之前的数据表示已发送过确认,并且已交付主机了,在 B 中将不保留这些数据。而接收窗口内的序号 ( 31 ~ 50 ) 是允许接收的。在接收时未按序到达,B 只会对按序收到的数据中的最高序号给出确认,因此 B 发送的确认报文段中的确认号仍是 31

现在假定 B 收到了序号为 31 的数据,并把序号为 31 ~ 33 的数据交付给主机,然后 B 删除这些数据。接着把接收窗口向前移动 3 个序号( 如图5-17 ),同时给 A 发送确认,其中窗口仍是 20,但确认号是 34。B 还收到了序号为 37. 38 和 40 的数据,但都没有按序到达,只能先暂存在接受窗口中。A 在收到 B 的确认后,可以把发送窗口向前滑动 3 个序号,但指针 P2 不动,继续发送。

A 在继续发送完序号为 42 ~ 53 的数据后,指针 P2 向前移动和 P3 重合。发送窗口内序号已用完,但还没有确认。由于 A 的发送窗口已满,因此必须停止发送。但如果发送窗口内所有数据已发送正确到达 B,B 也早发出确认。但由于其他原因,A 没有收到。这时 A 在经过一段时间后会重传这部分数据,重新设置超时计时器,直到收到 B 的确认为止。

下面是窗口和缓存的关系,如图 5-19,发送方维持的发送缓存和发送窗口,以及接收方维持的接收缓存和接收窗口


发送方

发送缓存用来暂时存放:

  • 发送应用程序传送给发送方 TCP 准备发送的数据
  • TCP 已发送出但尚未收到的确认的数据

发送窗口通常只是发送缓存的一部分。已被确认的数据应当从发送缓存中删除,因此发送缓存和发送窗口的后沿是重合的。发送应用程序最后写入发送缓存的字节 减去 最后被确认的字节,就是 保留在发送缓存中的被写入的字节数

接收方

接收缓存用来暂时存放:

  • 按序到达的. 但尚未被接收应用程序读取的数据
  • 未按序到达的数据

如果收到的分组被检测出有差错,则要丢弃。如果接收应用程序来不及读取收到的数据,接收缓存最终会被填满,使接收窗口减小到零。反之,如果能够及时读取,则接收窗口就可以增大,但最大不能超过接收缓存的大小

注意

  • 虽然 A 的发送窗口是根据 B 的接收窗口设置的, A 的发送窗口并不一定和 B 的接收窗口一样大。因为网络传送窗口值需要经历一定的时间滞后
  • 对于不按序到达的数据,如果接收方一律丢弃,虽然管理比较简单,但对网络资源的利用不利。因此通常对不按序到达的数据先临时缓存在接受窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程
  • TCP 要求接收方必须有累积确认功能,这样可以减少传输开销。接收方可以在合适的时候发送确认。但应注意:接收方不应过分推迟发送确认,否则会导致发送方发送不必要的重传,反而浪费了网络资源

超时重传时间的选择

TCP 采用一种自适应算法,它记录了一个报文段发出的时间,以及收到相应的确认的时间。这两个时间差就是 报文段的往返时间 RTT

TCP 保留了 RTT 的一个 加权平均往返时间 RTTs。每当第一次测量 RTT 样本时,RTTs 值就取为所测量到的 RTT 样本值。以后每测量到一个新的 RTT 样本,就按下式重新计算一次 RTTs:

新的 RTTs = (1 - α) * (旧的 RTTs) + α * (新的 RTT 样本)

上式中,0 < α < 1。推荐的 α 值为 0.125。

RTTD 是 RTT 的 偏差的加权平均值。当第一次测量时,RTTD 值取为测量到的 RTT 样本值的一半。以后测量中,使用下式计算加权平均的 RTTD:

新的 RTTD = (1 - β) * (旧的 RTTD) + β * | RTTs - 新的 RTT 样本 |

这里的 β 是个小于 1 的系数,推荐值是 0.25

超时重传时间 RTO应略大于上面的加权平均往返时间 RTTs。使用下式计算 RTO

RTO = RTTs + 4 * RTTD

现在还有一个问题,发送了一个报文段,设定的重传时间到了,还没有收到确认,于是重传报文段。经过一段时间,收到了确认报文段。但是如何判定 此确认报文段是对先发送的报文段的确认,还是对后来重传的报文段的确认呢

如果判定出现错误,则得出的超时时间重传 RTO 就肯定不准确。
根据这,Karm 提出了一个算法:在计算加权平均 RTTS 时,只要报文段重传了,就不采用其往返时间样本。这样得出的加权平均 RTTS 和 RTO 就较准确

但是这又有新的问题。如果报文段的时延突然增加了很多,原来得出的重传时间内,不会收到确认报文段。于是会重传报文段。但是 Karm 算法不考虑重传报文段的往返时间样本。这样超时重传时间就无法更新

对 Karm 算法进行更新:报文段每更新一次,就把超时重传时间 RTO 增大一些,例如取新的重传时间为原来的 2 倍。因此 Karm 算法能使运输层区分开有效的和无效的往返时间样本,改进了往返时间的估测,使结果更加合理

选择确认的 SACK

若收到的报文段无差错,只是未按序号,中间还缺少一些序号的数据,想要只传送缺少的数据而不重发已经正确的到达接收方的数据,可以使用 选择确认

TCP 的接收方在接收对方发送过来的数据字节流的序号不连续,就形成了一些不连续的字节块。接收方会先接收下这些数据,并通知发送方不要再重复发送。

如果要提供这些字节块的边界信息。可以使用选择确认 SACK,在建立连接时,就要在 TCP 首部的选项中加上允许 “SACK” 的选项,而双方都必须实现商定好。在以后的 TCP 报文段的首部增加 SACK 选项,以报告收到的不连续的字节块的边界。

写在最后

如果有任何问题,欢迎交流学习。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值