这两天一直在研究这个话题,踩了几个坑,把遇到的东西整理成文,供有需要的朋友参考。
🔥草莓熊Lotso:个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:
文章目录
二. TCP 协议格式深度解析 2.3 Linux 内核中 TCP 报头的实现2.4 TCP 与 UDP 报头的关键区别 三. TCP 可靠性核心:确认应答与超时重传 结尾:前言:
在互联网世界中,TCP 协议无疑是最核心的基石之一 —— 我们每天用的 HTTP、HTTPS、SSH、FTP 等几乎所有可靠通信都建立在 TCP 之上。不少人知道 TCP 是 “可靠的传输控制协议”,但很少有人深入搞懂它的可靠性究竟是如何实现的,以及它的报头设计背后隐藏着怎样的精妙考量。这里将从最基础的 TCP 数据发送流程讲起,深度拆解 TCP 报头的每一个字段,结合 Linux 内核源码分析其底层实现,并详细讲解 TCP 可靠性的两大核心机制:确认应答与超时重传。所有内容均严格基于 TCP 协议规范和 Linux 内核实现,力求做到理论与实践相结合。
一. TCP 的简单回顾
1.1 TCP 在网络分层中的位置
我们先回顾一下经典的网络分层模型,明确 TCP 的定位:
OSI 参考模型TCP/IP 分层模型典型协议应用层应用层HTTP、HTTPS、SSH、FTP表示层--会话层--传输层传输层TCP、UDP、SCTP网络层互联网层IP、ICMP、ARP数据链路层网卡层以太网协议物理层(硬件)-
1.2 TCP 数据发送的本质
不少初学者会误以为调用write/send函数就是直接把数据发送到网络上,这是一个常见的误区。实际上,我们向网络发送数据的本质是将数据拷贝到操作系统内核的 TCP 发送缓冲区中。
应用程序 write() → 拷贝数据到TCP发送缓冲区 → TCP协议栈控制发送时机、速率 → 网络
TCP 协议栈完全自主决定:
- 发多少数据
- 什么时候发
- 以什么速率发
正是因为有了发送和接收缓冲区,TCP 才可以实现面向字节流的特性 —— 数据被看作是一连串无结构的字节流,传输层不关心应用层的报文边界。
二. TCP 协议格式深度解析
TCP 报头是 TCP 协议的核心,所有的控制信息都包含在报头中。搞懂 TCP 报头是掌握 TCP 协议的基础。
2.1 TCP 报头整体结构
TCP 报头由固定 20 字节的标准部分和最多 40 字节的可选部分组成,总长度范围为 20~60 字节。
字段长度字段名称核心作用16 位源端口号标识发送方进程16 位目的端口号标识接收方进程32 位序号本报文段第一个数据字节的编号32 位确认序号期望收到对方下一个字节的编号4 位首部长度以 4 字节为单位的 TCP 首部总长度6 位保留位预留为将来扩展,必须置 06 位标志位控制 TCP 连接状态和数据传输16 位窗口大小接收方的接收能力(流量控制)16 位检验和校验 TCP 首部和数据的完整性16 位紧急指针标识紧急数据的末尾位置0~40 字节选项扩展 TCP 功能(如 MSS、窗口扩大因子)可变长度数据应用层有效载荷
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 位二进制范围:0000~1111(十进制 0~15)
- 基本单位:4 字节
- 实际首部长度 = 字段值 × 4 字节
- 最小值:5(5×4=20 字节),表示没有选项的标准报头
- 最大值:15(15×4=60 字节),表示选项部分占满 40 字节
- 为什么要以 4 字节为单位?因为 4 位最多只能表示 15 个值,直接表示字节长度的话最多只能到 15 字节,远远不够。通过规定基本单位为 4 字节,将表达范围从 0~15 字节扩展到了 0~60 字节。
- 更深层次的原因是:TCP 报头长度必须是 4 字节的整数倍,因此报头长度的二进制表示的低两位永远是 0。我们不要存储这两位,只要存储高 4 位即可,读取时再左移两位(×4)补回低两位的 0。
- 标准报头 20 字节:20 ÷ 4 = 5 → 字段值为 5(二进制 0101)
- 带 12 字节选项的报头:20+12=32 字节 → 32 ÷ 4 = 8 → 字段值为 8(二进制 1000)
2.2.4 6 位标志位
这 6 个标志位用于控制 TCP 连接的状态和数据传输方法:
- URG:紧急指针有效
- ACK:确认序号有效(所有数据传输阶段的报文都必须置 1)
- PSH:提示接收端立即将数据从 TCP 缓冲区推送给应用层
- RST:强制重置连接
- SYN:请求建立连接
- FIN:请求关闭连接
2.3 Linux 内核中 TCP 报头的实现
我们来看一下 Linux 内核中tcphdr结构体的定义(位于include/linux/tcp.h),这是 TCP 报头在代码中的直接映射:
// 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 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 报文都是独立的、有边界的。长度字段告诉接收方这个 UDP 报文的总长度,从而可以准确分离报头和有效载荷。
- TCP 是面向字节流的:TCP 将数据看作是连续的字节流,传输层不关心应用层的报文边界。TCP 只需要保证字节流可靠到达,报文的边界由应用层自己处理。
三. TCP 可靠性核心:确认应答与超时重传
TCP 最核心的特性就是可靠性。那么,TCP 是如何在不可靠的网络层之上实现可靠传输的呢?
3.1 可靠性的本质
首先我们要明确一个重要结论:网络世界中没有 100% 可靠的协议。
这是一个经典的 “蓝军红军麻烦”:永远有最新的消息还没有得到应答,无法确认对方是否收到。但 TCP 通过确认应答机制,保证了被应答的历史数据 100% 可靠。
3.2 确认应答(ACK)机制
TCP 可靠性的基础就是确认应答机制:A 主机发送给 B 主机的每一个数据段,B 主机都必须给 A 主机回复一个 ACK 确认报文。
开发流程:
- A 发送数据段(序号 1~1000)给 B
- B 收到数据后,回复 ACK 报文(确认序号 1001)
- A 收到 ACK 后,就知道 1~1000 字节已经被 B 可靠接收
- A 继续发送下一个数据段(序号 1001~2000)
- ACK 是由对方操作系统的 TCP 层自动回复的,不需要应用层参与。这保证了即使应用层很忙,也不会影响 TCP 的可靠性。
- ACK 本身不需要被应答,否则会陷入无限循环。TCP 通过超时重传机制来处理 ACK 丢失的情况。
3.3 超时重传机制
如果 A 在一定时间内没有收到 B 的 ACK,会发生什么?
对于 A 来说,无法区分是以下哪种情况:
- 数据本身在传输过程中丢失了,B 根本没有收到
- B 收到了数据并回复了 ACK,但 ACK 在传输过程中丢失了
TCP 的去重机制:由于可能会重传数据,接收方可能会收到重复的报文段。TCP 通过序号来识别重复的报文段,并将重复的丢弃,保证应用层只会收到一次数据。
动态超时时间计算:TCP 的超时时间不是固定的,而是根据网络状况动态计算的:
- Linux 系统以 500ms 为基本单位
- 第一次超时等待 500ms
- 如果重传后仍然没有收到 ACK,等待时间加倍(1000ms)
- 以此类推,以指数形式递增
- 累计重传一定次数后,TCP 觉得网络或对端异常,强制关闭连接
结尾:
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
** 结语:这里我们从 TCP 的基本开发流程讲起,深度解析了 TCP 报头的每一个字段,特别是 4 位首部长度的设计精髓,并结合 Linux 内核源码分析了其底层实现。同时,我们详细讲解了 TCP 可靠性的两大核心机制:确认应答和超时重传,搞懂了 TCP 如何在不可靠的网络之上实现可靠的数据传输。当然,TCP 的麻烦之处远不止于此。为了提高传输性能,TCP 还引入了滑动窗口、快速重传、流量控制、拥塞控制等机制;为了管理连接,TCP 设计了三次握手和四次挥手的流程。这些内容我们将在后续的文章中逐一拆解。
✨把这些内容吃透超牛的!放松下吧✨
>
ʕ˘ᴥ˘ʔ
>
づきらど
本次分享就到这里。技术这东西越研究越有意思,后续有新的收获我也会继续更新。
评论 (0)
暂无评论