欢迎光临
我们一直在努力

《Linux 网络编程》深入理解 TCP 协议(一):报头解析与可靠传输基础

🔥小叶-duck:个人主页

 ❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》 《Linux系统从入门到实践》《Linux网络从入门到实践》 《Qt 方寸极境》 《MySQL》

未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一、TCP 基础回顾

  1.1 TCP 在网络分层模型中的位置

  1.2 数据传输的真实流程

  1.3 系统调用的本质

二、TCP 协议报文段格式

  2.1 TCP 报头整体结构

  2.2 核心字段简单介绍(后续一一详解)

    2.2.1 源 / 目的端口号(16 位)

    2.2.2 32 位序号与确认序号

    2.2.3 4 位首部长度(本篇重点)

    2.2.4 6 位标志位

  2.3 内核 TCP 报头结构体实现

  2.4 TCP 与 UDP 报头的关键区别

三、4 位首部长度:TCP 首部长度计算规则

  3.1 引出两大核心问题

    3.1.1 有效载荷如何进行分用?

    3.1.2 报头与有效载荷如何进行分离?

  3.2 核心规则

  3.3 取值范围与计算

  3.4 4 字节对齐要求

  3.5 选项长度计算

  3.6 计算流程

四、TCP 可靠性机制:确认应答与超时重传

  4.1 网络传输不可靠的根源

  4.2 TCP 可靠性的准确含义

  4.3 确认应答(ACK)机制

  4.4 超时重传机制:可靠性兜底方案

​编辑

结束语


前言

      在 Linux 网络编程的学习路上,TCP 协议是绕不开的核心。很多同学熟练写出 socket 通信代码,但对 TCP 报文的内部构造、可靠传输底层原理一知半解,遇到网络异常、丢包等问题时很难定位根因。       本篇将从 TCP 在网络分层模型中的位置开始,梳理数据发送的本质;深度拆解 TCP 报头完整结构,本篇文章主要讲解TCP报头中4位首部长度字段的讲解,结合 Linux 内核源码视角认识 TCP 头部实现,对比 TCP 与 UDP 报头的差别。最后讲解 TCP 可靠性最基础的两大机制:确认应答与超时重传,帮你搭建 TCP 底层知识框架,为后续TCP报头中其他字段的内容讲解做好铺垫。

一、TCP 基础回顾

  1.1 TCP 在网络分层模型中的位置

      我们先来回顾经典网络分层模型,明确 TCP 协议所在的层级:

OSI 参考模型TCP/IP 分层模型典型协议
应用层 应用层 HTTP、HTTPS、SSH、FTP
表示层
会话层
传输层 传输层 TCP、UDP、SCTP
网络层 互联网层 IP、ICMP、ARP
数据链路层 网卡层 以太网协议
物理层 (硬件)

      TCP 工作在传输层,它的核心职责是在两台主机上的应用进程之间,搭建一套面向连接、具备可靠性保障的字节流通信通道。

  1.2 数据传输的真实流程

      应用程序向网络发送数据,本质只是通过系统调用把数据拷贝到操作系统内核缓冲区,数据如何发送、发多少、出错如何处理、如何保证可靠,全部由操作系统 内部的 TCP 模块控制。

  • 发送端:应用层数据序列化 → 调用 write/send 拷贝到内核发送缓冲区 → TCP 模块封装并发送。
  • 接收端:TCP 模块接收数据到内核接收缓冲区 → 调用 read/recv 拷贝到应用层 → 应用层反序列化解析。

  1.3 系统调用的本质

      不少刚接触网络编程的同学都会产生一个误解:调用write或者send系统调用,数据就会立刻被发送到网络中。但真实的流程并非如此。 应用程序执行发送操作,本质仅仅是把用户态的数据拷贝到操作系统内核维护的 TCP 发送缓冲区里。

应用程序 write() → 拷贝数据至TCP发送缓冲区 → TCP协议栈自主决定发送时机与数量 → 网络

      数据进入内核缓冲区之后,什么时候发、一次发送多少字节、以多快速度发送,全部由内核里的 TCP 协议栈自主调度,上层应用无法直接干预。这也是 TCP 被叫做传输控制协议的根本原因。

      除发送缓冲区之外,TCP 还会维护一块接收缓冲区。对端发来的数据会先存入接收缓冲区,等待应用程序主动调用 read 函数读取。       发送缓冲区与接收缓冲区共同造就了 TCP字节流的核心特点:TCP 只把数据当成连续无边界的字节序列进行传输,传输层本身不会感知、维护应用层的报文边界,这也是 TCP 粘包现象产生的根源。

二、TCP 协议报文段格式

      TCP 报文由TCP 报头和有效载荷两部分组成,有效载荷为应用层数据,如 HTTP 请求、HTTP 响应、字符串数据等。TCP 报头由固定 20 字节的标准部分和最多 40 字节的可选部分组成,总长度范围为 20~60 字节。

  2.1 TCP 报头整体结构

字段长度字段名称核心作用
16 位 源端口号 标识发送端应用程序(进程)
16 位 目的端口号 标识接收端应用程序(进程)
32 位 序号 本报文段第一个数据字节的编号,用于数据排序、去重与可靠传输
32 位 确认序号 期望收到对方下一个字节的编号,用于对接收到的数据进行确认应答
4 位 首部长度 以 4 字节为单位的 TCP 首部总长度,标识整个 TCP 报头的总长度
6 位 保留位 预留为将来扩展,暂未使用,必须置 0
6 位 标志位 FIN、SYN、RST、PSH、ACK、URG 等控制标志,控制 TCP 连接状态和数据传输
16 位 窗口大小 接收方的接收能力,用于流量控制,告知对方可接收数据大小
16 位 检验和 校验 TCP 首部和数据的完整性,检测报文传输过程中是否出错
16 位 紧急指针 指向紧急数据的位置,标识紧急数据的末尾位置
0~40 字节 选项 扩展 TCP 功能(如 MSS、窗口扩大因子),长度可变,必须 4 字节对齐
可变长度 数据 应用层有效载荷

  2.2 核心字段简单介绍(后续一一详解)

    2.2.1 源 / 目的端口号(16 位)

      这两个字段负责完成数据的分发工作,用来确定收到的数据该交付给主机上哪一个进程。

端口的取值区间是 0~65535,按照用途划分为三类:

  • 0~1023:公认的知名端口,专门分配给标准网络服务,例如 HTTP 占用 80 端口、HTTPS 为 443、SSH 使用 22 端口。
  • 1024~49151:注册端口,留给开发者的业务程序自行选用。
  • 49152~65535:动态临时端口,由操作系统在程序发起连接时自动分配。

    2.2.2 32 位序号与确认序号

      这两个字段构成 TCP 可靠传输的基础。TCP 会为传输的每一个字节数据分配独立编号,序号代表当前报文段里第一个数据字节的编号。

      确认序号的含义:我已经成功收到确认序号之前的全部字节,请对方从该序号位置继续发送后续数据(非常重要,后续会进行详细讲解)。       举个例子:A 发送序号 1~1000 的数据,B 接收完成后,会回复确认序号为 1001 的 ACK 报文。

    2.2.3 4 位首部长度(本篇重点)

      该字段用来标记 TCP 头部整体长度,单位是 4 字节。TCP 头部长度范围为 20~60 字节。

  • 最小值为 5,代表基础 20 字节无选项的 TCP 报头;最大值 15,对应 60 字节,选项部分占满。
  • 接收方依靠这个字段,区分 TCP 头部和后面的应用数据。

    2.2.4 6 位标志位

      6 个标志位用于管控 TCP 连接状态以及数据传输行为:

  • URG:紧急指针生效
  • ACK:确认序号有效,数据传输阶段报文一般都会置 1
  • PSH:通知接收端尽快把缓冲区数据上交应用层
  • RST:强制断开、重置连接
  • SYN:请求建立连接
  • FIN:请求关闭连接

  2.3 内核 TCP 报头结构体实现

      TCP 报头在 Linux 内核源码中以struct tcphdr结构体定义(位于include/linux/tcp.h),利用 C 语言位段精准控制每一个比特位,同时通过条件编译适配不同 CPU 的大小端,和网络报文二进制布局保持一致。

// linux kernel include/linux/tcp.h
struct tcphdr {
__be16 source; // 16位源端口号,网络字节序(大端)
__be16 dest; // 16位目的端口号,网络字节序
__be32 seq; // 32位序号
__be32 ack_seq; // 32位确认序号
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4, // 保留位4位
doff:4, // 4位首部长度(数据偏移)
fin:1, // FIN标志:关闭连接
syn:1, // SYN标志:建立连接
rst:1, // RST标志:重置连接
psh:1, // PSH标志:推送数据
ack:1, // ACK标志:确认号有效
urg:1, // URG标志:紧急指针有效
ece:1, // ECE标志:显式拥塞通知回显
cwr:1; // CWR标志:拥塞窗口减小
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4, // 大端模式下,位段顺序相反
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window; // 16位窗口大小
__sum16 check; // 16位检验和
__be16 urg_ptr; // 16位紧急指针
};

源码解读:

  • 位段的使用:TCP 报头中有很多单个位的标志,使用 C 语言的位段特性可以节省内存,同时方便按位操作。
  • 大小端适配:由于不同 CPU 架构的字节序不同,内核通过条件编译来适配小端和大端模式下的位段顺序。
  • 网络字节序:所有多字节字段(如source、dest、seq等)都使用__be16或__be32类型,表示网络字节序(大端)。

  2.4 TCP 与 UDP 报头的关键区别

      不少学习者会有这样的疑问:UDP 报头里专门设计了 16 位的长度字段,TCP 报头却没有,这是为什么?

答案在于两者提供的服务模型截然不同:

  • UDP 是面向数据报的:每一个 UDP 报文都是相互独立、自带边界的数据单元。报头中的长度字段用于告知接收方这条报文的总长度,接收端据此精准地分割出报头和有效载荷,确定数据的起止位置。
  • TCP 是面向字节流的:TCP 把数据视作一段连续、没有边界的字节流,传输层并不关心应用层报文的划分边界。TCP 的核心职责是保证字节流可靠、有序地送达对端,而报文边界需要交由应用层自行定义与处理。

      一句话总结:UDP 需要长度字段来界定每条独立报文的边界,所以头里带长度;TCP 把边界问题留给了应用层,传输层只管搬运字节流,因此不需要长度字段,只需要通过计算将报头和有效载荷进行分离即可。

三、4 位首部长度:TCP 首部长度计算规则

  3.1 引出两大核心问题

    3.1.1 有效载荷如何进行分用?

      接收方根据16 位目的端口号将数据交付给对应的应用程序,实现多应用同时网络通信的隔离与分发。

    3.1.2 报头与有效载荷如何进行分离?

      答案:借助4位首部长度。       TCP 标准报头前 20 字节固定可提取,结合 4 位首部长度计算出总报头长度,即可完成报头与有效载荷的分离;TCP 报文无自身总长度字段,需结合 IP 层长度计算有效载荷长度。

  3.2 核心规则

      TCP 头部的 4 位首部长度字段,计量单位不是字节,而是 4 字节。用读取到的字段值 N 乘以 4,才能得到 TCP 头部真实的字节长度。

  3.3 取值范围与计算

  • 最小取值:5 → 5×4=20 字节,代表不带任何选项的基础标准报头。
  • 最大取值:15 →15×4=60 字节,代表选项区域填满,达到报头上限。
  • 发送写入:将 TCP 实际头部总长度除以 4,结果存入这个 4 位字段。
  • 接收读取:取出 4 位字段的值,乘以 4,换算为真实报头总字节长度。

  3.4 4 字节对齐要求

      TCP 报头强制要求4 字节对齐,也就是报头总长度一定能被 4 整除,对应二进制形式下最后两个 bit 永远为 0。 借助这个特性,仅用 4bit 的存储空间,就可以表达最大 60 字节的头部长度,节省报文空间。

  3.5 选项长度计算

      选项部分字节长度计算公式:       选项长度 = 首部长度字段值 × 4 − 20       选项区域最大容量为 40 字节。

  3.6 计算流程

      TCP 基础固定部分占 20 字节,选项区域在固定头部之后,可根据需求增加,也可以为空。 接收端解析报文的完整流程:

  • 先读取报文前 20 字节基础头部;
  • 提取 4 位首部长度字段,乘以 4 算出 TCP 头部整体长度;
  • 用总头部长度减去固定 20 字节,得到选项部分的长度,读取选项内容;
  • 头部结束之后剩余的数据,就是 TCP 承载的应用有效载荷。
  • 小结:4 位的取值范围本身仅能表达 0~15,但是 TCP 报头最小就需要 20 字节,因此该字段实际可用范围是 5~15,对应报头 20~60 字节。

    四、TCP 可靠性机制:确认应答与超时重传

      4.1 网络传输不可靠的根源

          底层网络传输本身不具备可靠保障。数据跨越多个路由设备、经过多段物理链路转发,通信两端距离越远,越容易遇到数据包丢失、传输延迟、报文乱序、应答报文丢失等各类异常问题。

      4.2 TCP 可靠性的准确含义

          首先要明确一个核心结论:不存在能够做到绝对 100% 可靠的网络传输协议。 这就是经典的 “蓝军红军问题”:永远存在刚发出、还没有收到应答的报文,发送方无法确认对方是否成功收到。

          TCP 所说的可靠,并不是保证所有报文一定不会丢、不会出错,它的定义是:凡是已经收到对方 ACK 应答确认的历史数据,可以保证已经完整送达;尚未收到应答的报文,传输状态未知,TCP 不做可靠性承诺。

          可以用对话场景简单理解:A 向 B 提问,B 回复消息代表 A 的消息已经被对方收到,本次通信确认可靠。但是 B 的这条回复能不能送达 A,B 本身无法确认,需要 A 再做应答确认。这种一问一答的确认循环,就意味着永远存在未确认的最新报文,网络无法实现全局绝对可靠。TCP 的核心价值,就是依靠一系列机制,保证所有已经应答完成的历史数据可靠交付。

      4.3 确认应答(ACK)机制

          确认应答是 TCP 实现可靠传输最基础、最核心的底层机制。 TCP 会把传输的每一个字节数据分配独立序号。当主机 A 向主机 B 发送数据段之后,B 收到数据,必须向 A 返回一条 ACK 确认报文。

    工作流程:

    • A 发送序号 1~1000 的数据段给 B
    • B 接收完成,回复确认序号为 1001 的 ACK 报文
    • A 收到 ACK,就确定 1~1000 字节已经被 B 完整接收
    • 之后 A 继续发送 1001~2000 字节的数据

    两个关键细节:

  • ACK 报文由接收端操作系统内核 TCP 模块自动生成并发送,不需要应用程序参与。就算上层应用繁忙,也不会影响 TCP 底层的应答逻辑。
  • ACK 报文本身不会再要求对方应答,否则会形成无限循环。ACK 丢失的场景,交由超时重传机制处理。
  •       TCP 属于全双工协议,两端可以同时互相发送数据。双向传输直接复用这套确认应答逻辑,A 发数据 B 应答、B 发数据 A 应答,保障双向的数据传输可靠性。

      4.4 超时重传机制:可靠性兜底方案

          确认应答保障正常场景下的可靠传输,超时重传则用来处理各类网络异常,是 TCP 可靠性体系的兜底机制。

          当主机 A 发送数据之后,启动专属超时计时器。如果在超时时间内,A 没有收到 B 返回的 ACK,此时 A 无法区分下面两种情况:

    • 数据丢包:原始数据报文在传输途中丢失,B 根本没有收到数据,自然不会回复 ACK
    • 应答丢包:B 已经成功收到数据并发出 ACK,但是这条 ACK 应答报文在链路传输时丢失

          TCP 对此采用统一处理策略:只要超时未收到 ACK,就判定本次传输异常,自动重传对应的数据段。

    TCP 的去重机制

          重传可能会带来报文重复到达的问题(上面应答丢包的情况)。接收端依靠 TCP 序号识别重复报文,自动丢弃重复的数据,保证上层应用只会收到一份数据,避免重复处理。

    动态超时时间

          TCP 的超时等待时间不是固定不变,会根据网络情况动态调整。

    • Linux 以 500ms 作为基础时间单位
    • 首次超时等待 500ms
    • 重传之后依旧收不到 ACK,等待时间翻倍(1000ms)
    • 以此类推,超时时间指数递增
    • 当重传次数达到阈值,TCP 判定网络或者对端异常,直接关闭连接

          总结:确认应答用来确认成功接收;超时重传用来修复丢包异常;序号同时支撑报文排序和重复报文去重,三者一起构成 TCP 可靠传输的基础。

    结束语

          本篇我们重点围绕 TCP 报头里的4 位首部长度字段展开解析,同时讲解了 TCP 可靠传输的两大基础机制:确认应答与超时重传。TCP 报头包含的字段繁多,本文不会一次性全部讲完,剩余的端口、序号、标志位等其他报头字段,会放在后续系列文章中逐一进行详细拆解。

            我们首先理清了 TCP 在网络分层中的位置,理解 TCP 数据发送的本质;然后聚焦首部长度这个重点字段,理解它如何界定 TCP 头部边界、区分头部与应用数据。确认应答保障数据有序接收,超时重传用来应对网络丢包,二者共同构筑起 TCP 可靠传输最底层的保障。掌握这部分基础,才能更好地理解 TCP 后续的滑动窗口、拥塞控制等复杂逻辑。本篇作为系列第一篇,只做基础打底,让我们期待下一篇继续深挖 TCP 协议。

    赞(0)
    未经允许不得转载:171主机测评 » 《Linux 网络编程》深入理解 TCP 协议(一):报头解析与可靠传输基础
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址