深入理解TCP协议(一):TCP报文格式详解
本文详解TCP报文格式:从TCP协议四大特点(面向连接、可靠性、面向字节流、全双工)出发,逐字段剖析TCP首部——源/目的端口、序号与确认序号、4位首部长度、16位窗口大小、六大标志位(SYN/ACK/FIN/RST/PSH/URG)、紧急指针、校验和,以及MSS、窗口扩大、时间戳、SACK等选项字段,是理解TCP可靠传输与流量控制机制的基础。
一、TCP 协议是什么
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接、具有可靠性、面向字节流的传输层通信协议,负责在网络中两个应用程序之间可靠地传输数据。
TCP 位于 OSI 七层模型(或 TCP/IP 四层模型)的传输层,它的核心职责是:完成进程与进程之间的数据传输、保证数据可靠到达、保证数据到达的顺序正确。它不负责主机定位与寻址——那是网络层 IP 协议的工作。
TCP 协议的四大核心特点
| 特点 | 含义 | 关键影响 |
|---|---|---|
| 面向连接 | 通信之前必须先建立连接 | 连接建立后双方维护通信状态,再进行数据通信(即三次握手,后续章节详解) |
| 可靠性 | 无论底层 IP 网络如何丢包、乱序,TCP 都尽可能保证应用层收到的数据完整、有序 | 依靠序号、确认应答、超时重传、滑动窗口、拥塞控制等机制实现 |
| 面向字节流 | 不关心应用层一次发送多少数据,数据何时发、发多少、丢失怎么办全由操作系统基于 TCP 决定 | TCP 没有消息边界,应用层需自行处理粘包/拆包问题 |
| 全双工通信 | 连接建立后,双方可以同时发送数据 | 收发方向独立,各自拥有发送缓冲区和接收缓冲区,这也是关闭连接需要四次挥手的原因 |
二、TCP 报文格式总览
TCP 报文由**报头(首部)和有效载荷(数据)**两部分组成。TCP 首部最小为 20 字节,当携带选项字段时最大可达 60 字节。各字段的作用如下表所示:
| 字段 | 位宽 | 作用 |
|---|---|---|
| 源端口号 | 16 位 | 标识发送端主机上绑定该连接的网络进程 |
| 目的端口号 | 16 位 | 标识接收端主机上接收数据的网络进程,用于分用 |
| 序号 | 32 位 | 标识本报文携带数据的起始字节位置,解决乱序问题 |
| 确认序号 | 32 位 | 表示接收方已收到指定序号之前的所有数据,下一次希望接收的数据序号 |
| 首部长度(数据偏移) | 4 位 | 指明 TCP 首部的长度,用于报头与有效载荷的分离 |
| 保留位 | 6 位 | 保留未用,置 0 |
| 标志位 | 6 位 | 标识 TCP 报文的类型与连接状态(SYN/ACK/FIN/RST/PSH/URG) |
| 窗口大小 | 16 位 | 接收方通告自身接收缓冲区剩余空间,用于流量控制 |
| 校验和 | 16 位 | 检测报文在传输过程中是否发生数据损坏 |
| 紧急指针 | 16 位 | 定位紧急数据在字节流中的偏移位置(仅 URG=1 时有效) |
| 选项字段 | 可变长度 | 扩展 TCP 功能,如 MSS、窗口扩大因子、时间戳、SACK 等 |
三、端口号:源端口与目的端口
16 位源端口号用于表明发送端主机网络进程绑定的端口号。当对端处理完请求并给予响应时,会根据源端口号将数据交付给发起请求的那个网络进程。
16 位目的端口号则用于"分用"(Demultiplexing):操作系统收到 TCP 报文后,根据目的端口号找到本地绑定该端口的目标进程,将应用层数据交付给它。
这里需要理解两个关键动作:
- 分用:操作系统如何把数据交给本地主机的哪一个进程?——依据 16 位目的端口号完成。
- 解包:操作系统如何将 TCP 报文分离为报头和有效载荷?——依据 4 位首部长度字段完成。
4 位首部长度的巧妙设计
4 位首部长度字段的取值范围是 [0, 15],而 TCP 首部即使不含选项字段,最小也有 20 字节,看起来 4 位根本不够表示。其实 TCP 协议规定:该字段的值 × 4 = TCP 首部的实际长度。因此它的实际表示范围是 [0, 60] 字节,完全可以正确表示 TCP 首部的大小(20~60 字节),从而完成报头与有效载荷的分离。
四、序号与确认序号
4.1 理解可靠性的本质:确认应答机制
当客户端发送的数据被服务器成功接收后,服务器会通过应答报文(ACK)告知客户端"已经收到哪些数据"。这里有个重要的约定:不对应答再做应答,否则就会出现无限套娃。
那如果应答丢失了怎么办?TCP 依靠超时重传机制解决(下一章节详解)。这里先建立对可靠性的准确理解:
- 客户端收到应答,可以保证服务器一定收到了客户端发送的 TCP 报文;
- 客户端没有收到应答的报文,无法保证服务器一定收到;
- TCP 并不保证每个应答报文都一定到达,但通过序号和确认序号记录传输状态,即使应答丢失,也能通过重传机制保证数据最终可靠到达。
4.2 为什么需要两个序号?
TCP 是全双工通信,双方可以同时发送数据。例如:
- 客户端向服务器发送:序号 = 100,数据 = hello
- 同时服务器向客户端发送:序号 = 500,数据 = world
双方都需要记录自己发送的数据编号,同时也需要知道对方发送的数据是否被成功接收。因此 TCP 首部设计了两个字段:
| 字段 | 含义 |
|---|---|
| 序号 | 表示自己发送的数据编号 |
| 确认序号 | 表示已经成功接收到对方的数据编号 |
只用一个序号是不行的:一个序号无法同时记录"当前发送方发送的数据编号"和"当前发送方已经收到的数据编号",也就无法同时维护两个独立方向的数据传输状态。两个序号存在的根本原因,是 TCP 需要同时维护双方的数据传输状态,从而实现可靠的全双工通信。
补充:序号和确认序号并不是用来区分报文类型的(报文类型由标志位表示),虽然从理论上可以用序号区分应答报文和数据报文,但没有必要。
4.3 确认序号的取值规则
确认序号表示:接收方已经收到指定序号之前的所有数据,下一次希望接收的数据序号。
举例:发送方发送序号 = 100、数据长度 = 500 的报文,接收方收到后返回的确认序号 = 600。
4.4 捎带确认机制(附加优势)
一个 TCP 报文可以同时完成两件事:发送自己的数据 + 确认收到的数据。例如服务器在回答客户端问题时,同时携带自己的数据:
- 序号:服务器发送的数据编号
- 确认序号:对客户端数据的确认编号
这种同时包含有效序号和有效确认序号的报文被称为捎带应答,它减少了额外 ACK 报文的发送,提高了网络通信效率。
五、窗口大小与流量控制
应用层调用 read/recv 获取网络数据,并不是直接从网卡读取,而是由操作系统通过协议栈解包、分用后,将有效载荷拷贝到内核级 TCP 接收缓冲区,应用层再从接收缓冲区取数据。
计算机内存资源有限,TCP 接收缓冲区有大小限制。假设服务器以 100MB/s 的速度发送数据,而客户端应用处理速度只有 10MB/s,大量数据就会不断堆积在客户端接收缓冲区。当缓冲区空间不足时,发送方仍继续发送,接收方只能丢弃,造成大量无效传输和资源浪费——虽然重传机制能保证最终可靠,但会严重降低效率。
因此 TCP 引入流量控制机制,通过报头中的 16 位窗口大小字段解决:
- 16 位窗口大小 = 接收方当前允许发送方发送的数据量 = TCP 接收缓冲区剩余空间的大小;
- 接收方通过报文中的窗口大小字段,持续把自己的剩余接收能力告知发送方;
- 三次握手时双方交换初始窗口大小,数据传输过程中接收方通过应答报文持续动态通告,从而动态调整发送方的发送速度。
六、标志位详解
TCP 首部的标志位本质就是比特位:为 1 时有效,为 0 时无效。序号、确认序号、窗口大小解决的是"数据传了多少、传到哪里"的问题,而连接建立、关闭、确认、异常等特殊状态,则需要标志位来描述。
| 标志位 | 名称 | 含义 |
|---|---|---|
| SYN | Synchronize | 请求建立连接 |
| ACK | Acknowledgment | 确认应答,表明确认序号字段有效 |
| FIN | Finish | 请求关闭连接 |
| RST | Reset | 重置连接(连接异常时直接终止) |
| PSH | Push | 提醒接收方尽快将数据交付给应用层 |
| URG | Urgent | 紧急数据标志,表明报文包含紧急数据 |
其中 SYN、ACK、FIN 是 TCP 中最重要的三个标志位。
6.1 ACK 标志位
ACK 表示当前报文中的确认序号字段是否有效。例如 ACK=1、ack=1000,表示"确认序号有效,已经收到对方序号 1000 之前的数据"。TCP 通信过程中,大部分数据报文都会携带 ACK。
6.2 SYN 标志位
SYN 表示请求建立 TCP 连接。客户端发送 SYN=1 表示"我要和你建立连接",服务器收到后回复 SYN=1、ACK=1,表示"我同意建立连接,并确认收到你的请求"。
6.3 FIN 标志位
FIN 表示请求关闭 TCP 连接。客户端发送 FIN=1 表示"我的数据发送完毕,希望关闭连接",服务器收到后回复 ACK=1 确认收到关闭请求,之后双方分别关闭自己的发送方向。
6.4 RST 标志位
RST 表示重置 TCP 连接。当连接出现异常(连接不存在、端口没有监听、收到无法处理的数据)时,TCP 可以直接发送 RST 报文终止连接。例如客户端 connect 一个不存在的端口,服务器会返回 RST=1。
6.5 PSH 标志位
PSH 表示希望接收方立即将数据交付给应用层。正常情况下数据到达后先存放在接收缓冲区,当 PSH 被设置时,TCP 会提示接收方"这部分数据应该尽快交给应用程序"。不过在现代操作系统中,PSH 的实际影响已不像早期网络那么明显。
6.6 URG 标志位
URG 表示当前 TCP 报文包含紧急数据。当 URG=1 时,紧急指针字段有效,用于告诉接收方有部分数据需要优先处理。不过现代网络编程中实际应用非常少。
七、16 位紧急指针
紧急指针用于表示 TCP 报文中紧急数据的位置,只有当 URG 标志位为 1 时才有效。
为什么需要紧急指针?正常情况下 TCP 按字节流顺序传输数据(A→B→C→D→E→F→G),接收方按顺序读取。但某些特殊场景下,发送方希望某部分数据被优先处理。例如用户远程操作服务器时,普通输入是 ls、cd /home 等命令,而 Ctrl+C 需要被优先处理,不能等待前面的普通数据全部处理完成。
紧急指针如何工作?假设发送的数据是 A B C D E F,其中 D 是紧急数据,则 TCP 报文中设置 URG=1、Urgent Pointer=3,表示"从当前序号开始偏移 3 个字节的位置存在紧急数据",接收方据此定位并优先处理。
在 Linux 等现代操作系统中,TCP 紧急数据通常按 1 字节带外数据(OOB Data)处理,应用层可利用这 1 字节自行定义含义(如约定不同数值表示不同的紧急事件)。为什么现在很少使用?主要有两个原因:
- 应用层通常自己设计优先级机制(消息类型字段、优先级字段、控制消息),更灵活可控;
- TCP 紧急数据机制在不同操作系统上的实现存在差异。
因此实际开发中,更多使用"应用层协议 + 消息优先级设计"来实现类似功能。
八、16 位校验和
校验和用于检测 TCP 报文在传输过程中是否发生数据损坏。发送端根据 TCP 首部、数据以及相关的 IP 信息计算出一个 16 位校验和值;接收端收到报文后按同样的规则重新计算并比较。如果计算结果不一致,说明报文很可能在传输中损坏,接收端直接将该报文丢弃。
九、选项字段
TCP 首部最小长度为 20 字节,但设计时预留了可变长度的选项字段,用于扩展 TCP 的功能。由于选项字段长度不固定,TCP 通过**数据偏移(首部长度)**字段表示首部实际长度:
- 没有选项:TCP 首部 = 20 字节;
- 存在选项:TCP 首部 > 20 字节。
接收方通过数据偏移字段就能知道 TCP 数据从哪里开始。常见选项如下:
| 选项 | 全称 | 作用 |
|---|---|---|
| MSS | Maximum Segment Size | 最大报文段长度,通信双方告知对方自己能接收的最大 TCP 数据长度,避免分段过大 |
| 窗口扩大因子 | Window Scale | 扩展 16 位窗口字段的表示范围,突破 65535 字节限制,适配高带宽网络 |
| 时间戳 | Timestamp | 计算 RTT(往返时间),防止旧数据包影响新连接 |
| SACK | Selective ACK | 选择确认,告诉发送方具体哪些数据已收到、哪些缺失,减少不必要的重传 |
以 MSS 为例:客户端发送 SYN 时携带 MSS=1460,表示"我希望你发送给我的 TCP 数据部分最大为 1460 字节"。以 SACK 为例:默认 TCP 的 ACK 是累计确认,收到 1、2、3、5 缺少 4 时只能告诉发送方 ack=4;而 SACK 可以精确告知"1~3 已收到、5 已收到、4 缺失",从而减少不必要的重传。
十、总结
TCP 报文格式是理解整个 TCP 协议的基础。本文核心要点:
- TCP 是面向连接、可靠、面向字节流、全双工的传输层协议;
- TCP 首部最小 20 字节,由端口号、序号/确认序号、首部长度、标志位、窗口大小、校验和、紧急指针及选项字段组成;
- 序号与确认序号解决乱序与全双工状态维护问题,窗口大小实现流量控制,标志位管理连接状态,选项字段扩展协议能力;
- 4 位首部长度 × 4 的设计,使得 4 位字段能够表示 20~60 字节的首部长度。
掌握了报文格式,下一篇我们将深入 TCP 的可靠传输与高效通信机制:确认应答、超时重传、滑动窗口、流量控制、拥塞控制、延迟应答与捎带应答是如何协同工作,让 TCP 既可靠又高效的。
SEO 关键词
TCP协议、TCP报文格式、TCP首部、TCP标志位、SYN、ACK、FIN、RST、TCP序号、确认序号、窗口大小、流量控制、MSS、SACK、传输层协议、网络协议详解
