本文是《计算机网络学习笔记》系列的一篇。在讨论三次握手、拥塞控制、可靠重传之前,有一个更基础的问题值得先弄清楚:TCP 的每一个报文段,究竟长什么样?每一个字段,为什么存在?本文将逐字段拆解 TCP 报文头,并解释其背后的设计意图。

TCP 报文段的整体结构

一个 TCP 报文段由两部分组成:首部(Header)数据(Payload)

首部在没有选项(Options)字段时,固定为 20 字节;加上选项后最长可达 60 字节

2026-04-16T09:16:21.png

固定首部共 5 行,每行 32 位(4 字节),合计 20 字节


逐字段详解

第一行:端口号(Port)

|    源端口号 (16 bit)    |  目的端口号 (16 bit)   |

源端口和目的端口各占 16 位,范围 0 ~ 65535。

端口的本质:TCP 是面向进程的通信。IP 地址标识一台主机,端口则标识主机上的某个进程。一个 TCP 连接由四元组唯一确定:

(源IP,源端口,目的IP,目的端口)

只要四元组中任意一项不同,就是不同的连接。这意味着同一台服务器(固定目的 IP + 目的端口)可以同时维护成千上万条连接,每条连接的源端口不同。

端口分类:

范围类别说明
0 ~ 1023知名端口(Well-Known)由 IANA 统一分配,如 HTTP:80,HTTPS:443,SSH:22
1024 ~ 49151注册端口(Registered)常见应用使用,如 MySQL:3306,Redis:6379
49152 ~ 65535动态/临时端口(Ephemeral)操作系统动态分配给客户端连接使用

第二行:序列号(Sequence Number)

|                   序号 (32 bit)                   |

序列号占 32 位,范围 0 ~ 2³²-1(约 43 亿),是 TCP 可靠传输的核心机制之一。

序列号的含义:该报文段第一个字节数据在整个数据流中的编号。TCP 把应用层交下来的数据看作一个连续的字节流,每个字节都有唯一的编号。

初始序列号(ISN):连接建立时,双方各自随机选取一个初始序列号(ISN,Initial Sequence Number),而不是从 0 开始。随机化的目的是:

  1. 防止历史数据干扰:避免上一条连接残留在网络中的数据包,被新连接误认为是合法数据;
  2. 安全性:让攻击者难以猜测序列号,防止伪造 TCP 报文。

序列号的消耗规则

  • 数据字节,每个字节消耗 1 个序号;
  • SYN 报文,消耗 1 个序号(即使不携带数据);
  • FIN 报文,消耗 1 个序号;
  • ACK 报文,不消耗序号。

序列号的回绕:32 位最多表示 4GB 的数据。在高速网络(如 10Gbps)下,序号空间可能在数分钟内耗尽,发生回绕(从 0 重新开始)。TCP 的时间戳选项(Timestamps)可以辅助区分回绕前后的包,避免混淆。


第三行:确认号(Acknowledgment Number)

|                 确认号 (32 bit)               |

确认号占 32 位,含义是:期望收到对方下一个字节的编号

例如:接收方已经收到了序号 0 ~ 99 的数据,则确认号填 100,意思是"100 之前的数据我都收到了,下一个请从 100 开始发"。

这种方式叫做累计确认(Cumulative ACK):一个 ACK 确认了该编号之前的所有数据,不需要逐个确认每个字节。

注意:确认号只有在 ACK 标志位为 1 时才有效。

第四行:首部长度、标志位、接收窗口

这一行信息量最密集,分为三部分。

首部长度(Data Offset)—— 4 位

首部长度,以 32 位(4 字节)为单位。

  • 最小值为 5,对应 5 × 4 = 20 字节(无选项时的最小首部长度);
  • 最大值为 15,对应 15 × 4 = 60 字节(选项最长 40 字节)。

接收方通过这个字段,才知道首部在哪里结束,数据从哪里开始。

保留位(Reserved)—— 3 位

历史上曾是 6 位,随着 ECN 功能引入,2 位被拆出给了 CWR 和 ECE,现在剩余 3 位均须置 0,留给未来扩展。

标志位(Flags)—— 9 位

标志位是 TCP 报文的"行为指令",每个标志位只有 0 和 1 两种状态。

标志位全称作用
CWRCongestion Window Reduced拥塞控制:告知对方"我已经降低发送速率了"
ECEECN-Echo拥塞控制:告知对方"我收到了路由器的拥塞信号"
URGUrgent紧急数据标志,配合紧急指针字段使用,告知接收方优先处理
ACKAcknowledgment确认号字段有效,连接建立后的所有报文几乎都会置 1
PSHPush推送标志,要求接收方立即将数据交给上层应用,不在缓冲区积压
RSTReset重置连接,强制断开,通常用于异常情况(如端口不存在、拒绝连接)
SYNSynchronize同步序列号,用于建立连接。握手时前两个报文会置 1
FINFinish发送方没有数据要发了,用于正常关闭连接

几个标志位组合释义:

  • SYN=1, ACK=0:连接请求(第一次握手);
  • SYN=1, ACK=1:连接确认(第二次握手);
  • FIN=1, ACK=1:关闭请求/确认(四次挥手中常见);
  • RST=1:连接异常,立即终止,不需要四次挥手;
  • ACK=1:普通数据传输中的确认包。

关于 RST 的实际场景:

  • 客户端发 SYN 到一个未开放的端口 → 服务端回 RST(端口扫描的原理);
  • 进程崩溃未来得及四次挥手 → 操作系统发 RST;
  • 防火墙代为拒绝连接 → 伪造 RST 包。

窗口大小(Window Size)—— 16 位

窗口大小占 16 位,最大值 65535 字节(约 64 KB),是 TCP 流量控制的核心字段。

含义:发送方通过这个字段,告知对方自己的接收缓冲区还剩多少空间,对方不能发超过这个大小的数据,防止接收缓冲区溢出。

这个字段在每个报文中都会携带,动态更新,接收方每读走一批数据、腾出缓冲区,就会通过下一个 ACK 把新的窗口大小告知发送方。

原始的 16 位最大只有 64KB,在高带宽网络下严重不足。因此引入了窗口缩放选项(Window Scale),可以把实际窗口大小左移最多 14 位,最大扩展到 1GB。

第五行:校验和与紧急指针

校验和(Checksum)—— 16 位

校验和用于检测数据在传输过程中是否发生了错误(比特翻转等)。

计算范围:校验和覆盖一个"伪首部 + TCP 首部 + TCP 数据"的整体。

伪首部(Pseudo Header)是一个虚拟结构,仅用于计算校验和,不会真正发送到网络上:

+------------------+------------------+
|   源 IP 地址(32 位)               |
+------------------+------------------+
|   目的 IP 地址(32 位)             |
+---------+--------+------------------+
|  全 0   | 协议号=6 |  TCP 总长度      |
+---------+--------+------------------+

引入 IP 地址参与计算的意义:连 IP 层都一起校验,能检测出数据包被错误投递到另一台主机的情况(IP 地址对不上,校验和就会失败)。

注意:校验和只能检测错误,不能纠正错误。检测到错误后,TCP 会直接丢弃该报文段,由超时重传机制补发。

紧急指针(Urgent Pointer)—— 16 位

仅在 URG 标志位为 1 时有效,指向紧急数据的末尾位置。

实际现状:URG 和紧急指针在现代网络编程中几乎不使用,且中间的路由设备并不关心这个字段(不会优先转发)。它唯一的作用是告诉接收方,有紧急数据要提前处理,但这件事在应用层协议里完全可以自己实现,不需要依赖 TCP 的这个字段。


选项(Options)—— 0 ~ 40 字节

选项字段是 TCP 扩展能力的"插槽",长度可变但必须是 4 字节的整数倍(不足时用填充位补齐)。常见的选项有:

MSS(Maximum Segment Size,最大报文段长度)

MSS 是 TCP 最重要的选项之一,在三次握手时由双方各自声明,告知对方"你最多可以给我发多大的数据段"。

MSS 的计算来自于链路层的限制:

MTU (以太网)  = 1500 字节
减去 IP 头部  =   20 字节
减去 TCP 头部  =   20 字节
-------------------------------
MSS           = 1460 字节
易混淆点:MSS 是指 TCP 报文段中应用层数据的最大长度,不包含 TCP/IP 首部本身。

如果发送的数据超过了 MSS,TCP 层会自动将数据切分成多个报文段发送,这个过程对应用层完全透明。

Window Scale(窗口缩放因子)

弥补窗口大小字段只有 16 位(最大 64KB)的不足。握手时协商一个缩放因子 k,实际窗口大小 = Window Size 字段的值 × 2^k,最大可达 1GB。

现代高速网络(千兆、万兆)中几乎必然会用到此选项。

SACK(Selective Acknowledgment,选择性确认)

同样在握手时协商是否开启。

背景:TCP 的累计确认有一个局限——假设发送方发出了 1、2、3、4、5 号数据包,其中 2 号丢了,3、4、5 都到了。接收方的 ACK 只能填 2(表示还在等 2),发送方不知道 3、4、5 是否安然到达,可能白白重传它们。

SACK 的作用:接收方在回复 ACK 时,额外携带 SACK 块,描述已经收到但不连续的区间:

ACK  = 2          ← 主确认号:还在等 2
SACK = [3, 5]     ← 但 3、4、5 我已经收到了

发送方看到这个信息,就只精准重传 2 号包,而不需要重传 3、4、5。这在丢包率较高的网络中,可以显著提升传输效率。

Timestamps(时间戳)

携带发送时刻和接收时刻,主要有两个用途:

  1. RTT 精确测量:接收方把发送方的时间戳原样回显,发送方收到后,用当前时间减去时间戳,即可得到精确的往返时延(RTT),用于超时重传计时器的估算。
  2. PAWS(Protection Against Wrapped Sequences,防序列号回绕):在高速网络中,32 位序列号可能几分钟内就回绕一圈,时间戳帮助区分同一序列号在回绕前后的报文,避免混淆。

一个完整的字段速查表

字段大小作用
源端口16 位标识发送方进程
目的端口16 位标识接收方进程
序列号32 位本报文段第一字节在字节流中的编号
确认号32 位期望收到的下一字节编号(ACK=1 时有效)
数据偏移4 位首部长度,单位为 32 位(4 字节)
保留位3 位保留,置 0
CWR1 位拥塞窗口已缩减(ECN)
ECE1 位ECN 回显(路由器有拥塞)
URG1 位紧急数据标志
ACK1 位确认号字段有效
PSH1 位立即推送给应用层
RST1 位重置连接
SYN1 位建立连接,同步序列号
FIN1 位发送方无更多数据
窗口大小16 位接收缓冲区剩余空间(流量控制)
校验和16 位差错检测(覆盖伪首部+首部+数据)
紧急指针16 位紧急数据末尾位置(URG=1 时有效)
选项0~40 字节MSS、Window Scale、SACK、Timestamps 等

从协议格式看 TCP 的设计哲学

TCP 的每一个字段都不是凭空而来的,它们分别承担着不同的职责:

  • 序列号 + 确认号:共同实现了可靠的有序传输;
  • 窗口大小:实现了流量控制,防止发送方淹没接收方;
  • 标志位:用极少的比特位,编码了连接建立、维持、异常、关闭等全部状态机;
  • 校验和:在不可靠的底层网络之上,提供了数据完整性保障;
  • 选项字段:为协议的扩展留下了空间,使 SACK、窗口缩放、时间戳等后续增强得以在不修改核心字段的情况下加入。

理解了协议格式,三次握手、四次挥手、拥塞控制等机制就不再是孤立的"八股文",而是能落到具体的字节上、在 Wireshark 里逐帧验证的真实结构。


参考资料:《计算机网络:自顶向下方法》

标签: none

添加新评论