TCP 报文段格式

上一小节,我们简单介绍了 TCP 协议的基本机制,但很多细节还来不及展开。此时此刻,我们甚至对 TCP 的传输单元长啥样都一无所知。不过没关系,本节我们再接再厉,争取将它一举拿下!

报文结构

由于 TCP 协议位于传输层,它的传输单元一般叫做 TCP段segment ),也可译为 TCP分组 。当然了,也有不少文献将它笼统地称为 TCP报文

那么,一个 TCP 报文段的格式到底是怎样的呢?它跟 UDP 数据报又有哪些异同呢?

与 UDP 数据报一样,TCP 报文也分为头部和数据两个部分。所不同的是,TCP 报文头部要比 UDP 复杂很多:

请看上图, TCP 头部中的字段非常多。这一点都不意外,毕竟 TCP 协议工作机制远比 UDP 复杂。

那么,TCP 头部各个字段分别都有哪些作用呢?接下来,我们逐个展开介绍。

源端口

第一个字段是 源端口source port ),它的长度为 16 位,表示报文 发送方 的端口号。

目的端口

第二个字段是 目的端口destination port ),它的长度为 16 位,表示报文 接收方 的端口号。

序号

序号sequence number )字段,长度为 32 位,表示数据首字节的序号。在三次握手阶段,SYN 指令也是通过该字段,将本端选定的 起始序号 告诉接收方。

确认号

确认号acknowledgement number )字段,长度为 32 位。它表示已确认收到的数据序号,它的值为:已收到数据最后一个字节的序号加一,即接收方期望进一步接收的数据序号。

头部长度

头部长度header length )字段,长度为 4 位,表示 TCP 报文头部的长度,也可称为 数据偏移data offset )。跟 IP 协议一样,TCP 头部长度字段也不是以字节为单位,而是以 32 位字(4字节)为单位。

word )是计算机领域中的一个概念,表示由一系列比特组成的数据单位。字的长度可长可短,常见的有 8 位字、16 位字以及 32 位字等等。

这个字段长度为 4 位,最大值为 $2^4-1=15$​ 。因此,TCP 头部最大长度只能达到 $4\times15=60$​​ 字节。

保留

保留reversed )字段,长度为 3 位,保留未用。

标志位

标志位flags ),长度为 9 位,用于保存一些标志位。前面提到的 SYN ACK FIN 等指令,就是以标志位的形式保存在该字段中。这样的标志位,总共有 9 个:

  • NS ,ECN显式拥塞通知,属于 TCP 扩展,略;
  • CWR ,同样属于 TCP 扩展,略;
  • ECE ,同样属于 TCP 扩展,略;
  • URG ,紧急数据指令,表示紧急指针有效,报文段包含高优先级数据;
  • ACK ,确认指令,表示确认号有效,对已接收数据进行确认;
  • PSH ,立即推送指令,指示接收方立即将数据提交给应用层,不用等缓冲区装满;
  • RST ,重置指令,表示出现严重错误,常用于拒绝非法报文段以及拒绝连接请求;
  • SYN ,序号同步指令,在 TCP 三次握手建立连接时,将本端选定序号告诉对端;
  • FIN ,连接关闭指令,用于告诉对端,本端数据已发送完毕,连接关闭;

窗口大小

窗口大小window size )字段长度为 16 位,表示当前报文发送者接收窗口的大小,单位一般是 字节 。接收窗口表示接收方还能接收的数据大小,用于实现 TCP 流量控制 机制,后续章节再展开介绍。

校验和

校验和checksum )字段长度为 16 位,保存报文段的校验和,用于纠错。跟 UDP 协议一样,TCP 整个报文段都会参与校验和计算。除此之外,TCP 还会在报文段前面拼接一个 IP 伪头部,同时参与校验和计算:

至于校验和的纠错原理,请参考 以太网帧结构 一节;而构造 IP 伪头部参与校验和计算的目的,则可参考 UDP数据报格式 一节,这里均不再赘述。

紧急指针

紧急指针urgent pointer )字段长度为 16 位,仅在 URG 标志位开启时有效,它的值为紧急数据末字节,相对于当前报文段数据序号的偏移量。这意味着,该偏移量与序号字段相加即可得到紧急数据最后一个字节的序号。

URG 标志位和紧急指针一起为 TCP 提供了 紧急模式urgent mode ),以便在正常数据流中传输紧急数据。在套接字编程中,紧急数据经常被称为 带外数据out-of-band data )。

因篇幅关系,紧急模式和带外数据本文不再展开介绍。对这个话题感兴趣的话,可以留意后续章节。

选项

选项options )字段包含一些可选记录,总长度最多可达 40 字节。各个选项记录依次排列,每个选项最开始是 1 字节长的 类型kind )字段,说明选项的类型。

不同类型的选项记录,长度略有差异,记录在 长度 字段中。常用的选项类型,简单列举如下:

类型字段值 类型 说明
0 选项表结束 表示后面没有更多选项记录了(1字节长,无长度和数据部分)
1 无操作 用于选项记录间的字节边界对齐(1字节长,无长度和数据部分)
2 最大报文长度 指明本端所能接收的最大报文段长度(4字节长)
3 窗口扩大因子 用来将窗口大小字段左移,使其增倍,以适应大于65535字节的接收缓冲区。
4 SACK OK 表示支持SACK选项
5 SACK 实际工作的SACK选项

数据长度

您可能会有这样的疑问:TCP 报文段由头部和数据两部分组成,但头部并没有记录数据的长度。那么,TCP 协议是如何确定数据长度的呢?

答案其实非常简单,——可以根据 IP 包的长度来计算!

我们知道,传输层报文段需要借助网络层的传输能力,作为数据搭载在网络层包中,发往目标主机。如下图,为了将 TCP 报文段发往目标主机,协议栈将它作为数据封装成 IP 包,并发送出去:

换句话说,IP 包的数据部分就是一个 TCP 报文段;只要 IP 包的数据长度确定,TCP 报文的长度也就确定了。

回忆 IP 协议相关内容,我们知道 IP 包头部中有 头部长度总长度 两个字段,总长度减去头部长度即是IP包数据部分的长度。因此,TCP 报文段数据部分的长度可以这样计算:

$IP包总长度-IP头部长度-TCP头部长度$

实际上,UDP 协议中的数据长度字段只是个冗余字段,理论上并不需要。可能因为 UDP 协议开发较早,当时 IP 协议可能还未占主导,才需要自行记录数据长度吧。谁知道呢?

扩展阅读

小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: