Appearance
第三篇 逐字段看懂 TCP 报文头
在前两篇文章中,我们已经将一次完整的请求,拆解为应用层消息、Socket 系统调用,以及一条切实可见的 TCP 连接。本篇我们将继续深挖,直接剖析报文段(Segment)内部。我们会从一帧原始的十六进制字节流入手,找出 TCP 报文头(TCP Header),逐个字段拆解端口(Port)、序列号(Seq)、确认号(Ack)、标志位(Flags)、窗口(Window)、校验和(Checksum)以及各类选项(Options)。同时,我们还会通过手工计算,来验证 Wireshark 的解析结果是如何得出的。
读完本篇,你将能够独立回答以下问题:
- 报文中的某一段数据,究竟属于 Ethernet 层、IP 层、TCP 层还是应用层?
- TCP 报文头从第几个字节开始?应用层数据(Payload)又从哪里开始?
Seq和Ack分别在通信的两端是如何推进的?推进量该怎么计算?- 服务端同一个端口,凭什么能同时支撑成千上万条并发连接?
Data Offset、标志位、接收窗口、校验和以及 TCP Options,是如何相互配合来描述当前这个报文段的?- Wireshark 里显示的相对序列号,和抓包文件里实际抓到的原始十六进制字节,到底是怎么对应上的?
预备知识
建议你先阅读并完成第二篇的实操:写一个极简的客户端和服务端代码,完成第一次抓包,并能够熟练认出 TCP 连接的四元组(四元组决定了一条连接)。阅读本篇不需要高深的数学底子,只要懂一点基础的二进制、十六进制转换以及简单的整数加法即可。文章里涉及到的所有换算计算,我都会把中间步骤详细展开。
贯穿全篇的数据帧样本
在接下来的第 7 到 14 章中,我们会死磕下面这条从客户端发往服务端的数据帧(Frame)。为了规范,示例中的 IP 地址使用了专用的文档测试网段,帧里携带的应用层数据是 UTF-8/ASCII 纯文本 HELLO TCP\n。
text
0000 02 42 ac 11 00 02 02 42 ac 11 00 01 08 00 45 00
0010 00 3e 1c 46 40 00 40 06 32 22 c0 00 02 0a c6 33
0020 64 14 c0 00 23 28 6a 1b 2c 3e 10 20 30 41 80 18
0030 fa f0 e7 fd 00 00 01 01 08 0a 01 02 03 04 a0 b0
0040 c0 d0 48 45 4c 4c 4f 20 54 43 50 0a抓到的这条数据包总共有 76 字节,包含:14 字节的以太网帧头(Ethernet Header)、20 字节的 IPv4 报文头、32 字节的 TCP 报文头(含选项),以及 10 字节的应用层数据。它的核心四元组是:
text
192.0.2.10:49152 -> 198.51.100.20:9000在这里,TCP 实际在线上网络传输的序列号(Raw Seq)是 0x6a1b2c3e,确认号(Raw Ack)是 0x10203041。不过,如果你在 Wireshark 里开启了相对序列号(Relative Sequence Numbers)功能,它通常会非常贴心地把这个方向握手发 SYN 时的序列号显示为 0,紧接着把第一段普通数据的序列号显示为 1。请记住,这两种显示方式在底层对应的是同一组真实字节。
学习路线图
- 第7章 网络包的分层结构:理清数据帧(Frame)、IP 数据报(Datagram)、TCP 报文段(Segment)与应用层消息的嵌套层级关系。
- 第8章 TCP 报文头总览:借助偏移量(Offset),对 TCP Header 完成第一次庖丁解牛般的完整拆解。
- 第9章 源端口和目标端口:结合报文字段,弄懂内核如何查找匹配的连接,以及服务器如何处理海量的多客户端并发。
- 第10章 Sequence Number:给字节编号:手把手教你计算普通数据、SYN、FIN 以及发生序列号回绕(Wraparound)场景下的 Seq。
- 第11章 Acknowledgment Number:累计确认:理解“累计确认”机制,从已经收到的连续字节前缀,推导出下一个期望收到的 Seq 位置。
- 第12章 TCP 标志位:读懂 TCP 连接建立、确认、数据传输、连接关闭、异常重置(RST)以及拥塞控制反馈等控制信号。
- 第13章 Data Offset、Window、Checksum 和 Urgent Pointer:吃透剩余固定字段的语义,搞清抓包数据的真实边界。
- 第14章 TCP Options:深入解析 MSS、窗口缩放(Window Scale)、选择性确认(SACK)和时间戳(Timestamp)等核心扩展能力。
本篇的章节编排遵循着“定位网络层次 → 建立报文头布局 → 掌握双向的数据收发进度 → 补齐控制字段与扩展选项”这样一条渐进的路径。在每一章里,我们都会回到前面那条唯一的样本数据帧上,用新学到的知识为它加上一层可以亲自验证的解释。
最佳学习姿势
强烈建议你准备好纸笔或者打开一个电子表格,对照着我们的样本数据帧建立三列:帧内偏移量、原始十六进制字节 和 字段含义。每当你读完一章,就亲手补齐对应的字段,并亲自算一遍该章涉及的长度或数值。记住,Wireshark 只是帮我们省去解析的苦力活,而手工计算才能让你真正明白这些解析结果到底是从哪儿来的。
此外,对于每一个观察到的现象,都应该在心里标明它的“证据层级”。举个例子:抓到的原始字节是我们在网络抓包点看到的“铁证”(线上真实的二进制报文);而 Wireshark 提示的类似 TCP Retransmission(TCP 重传),则是抓包软件结合上下文环境给出的“分析推断”;至于请求最终到底成没成功,那则是“应用层语义”决定的事。养成这样分层思考的习惯,在日后排查复杂网络故障时,将会极大提升你的诊断效率。
本篇终极任务
当学完本篇所有内容后,你可以给自己布置这样一个终极任务:在你的抓包文件里,随便挑一条携带应用层 payload 的 TCP 报文段,从 Wireshark 的 Packet Bytes 窗格里把那堆十六进制原始字节抄下来,然后试着独立列出一份详尽的字段解析表,完成下面几件事:
- 准确标出链路层、IP 层、TCP 层以及应用层数据的起止偏移量。
- 提取出源/目的 IP、源/目的端口,拼出该连接完整的四元组。
- 纯靠分析十六进制字节,计算出线上的真实 Seq、Ack、TCP 报文头长度以及 TCP Payload 长度。
- 结合 Payload 长度、SYN 和 FIN 标志位,推算出该方向下一次该发送的 Seq。
- 列出所有固定字段和常用的 TCP 选项(Options),并说清楚每个值在网络双向传输中代表的含义。
- 弄清楚 Wireshark 界面中显示的到底是相对序列号还是原始序列号,学会区分什么是原始值、什么是显示值,以及什么是分析器的智能推断。
如果你能给出一份合格的解答,那么任何一位懂行的技术人员,应该仅仅凭借你给出的帧号、偏移量和计算过程,就能丝毫不差地复现你的结论。