
🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:

文章目录
- 前言:
- [一. TCP 核心知识点回顾](#一. TCP 核心知识点回顾)
- [二. TCP 发送数据的两种工作模式](#二. TCP 发送数据的两种工作模式)
-
- [2.1 串行发送模式(停等协议)](#2.1 串行发送模式(停等协议))
- [2.2 并行发送模式(流水线模式)](#2.2 并行发送模式(流水线模式))
- [三. 序号与确认序号的深度解析](#三. 序号与确认序号的深度解析)
-
- [3.1 序号的本质](#3.1 序号的本质)
- [3.2 确认序号的定义](#3.2 确认序号的定义)
- [3.3 序号的三大核心功能](#3.3 序号的三大核心功能)
- [3.4 为什么需要同时有序号和确认序号?](#3.4 为什么需要同时有序号和确认序号?)
- [四. 16 位窗口大小与流量控制](#四. 16 位窗口大小与流量控制)
-
- [4.1 窗口大小的含义](#4.1 窗口大小的含义)
- [4.2 流量控制的工作流程](#4.2 流量控制的工作流程)
- [4.3 窗口探测机制](#4.3 窗口探测机制)
- [五. 标志位字段详解](#五. 标志位字段详解)
-
- [5.1 ACK 标志(确认标志)](#5.1 ACK 标志(确认标志))
- [5.2 SYN 标志(同步标志)](#5.2 SYN 标志(同步标志))
- [5.3 FIN 标志(结束标志)](#5.3 FIN 标志(结束标志))
- [六. TCP 连接管理机制](#六. TCP 连接管理机制)
-
- [6.1 三次握手建立连接](#6.1 三次握手建立连接)
- [6.2 四次挥手断开连接](#6.2 四次挥手断开连接)
- 结尾:
前言:
在上一篇文章中,我们深度解析了 TCP 报头的设计精髓以及可靠性的两大基石 ------ 确认应答与超时重传机制。但 TCP 的复杂之处远不止于此:为了提高传输效率,TCP 支持并行发送多个报文;为了避免发送方过快导致接收方溢出,TCP 引入了流量控制;为了管理连接的生命周期,TCP 设计了经典的三次握手和四次挥手流程。本文将继续沿着 TCP 协议的设计思路,从发送模式讲起,深度拆解序号与确认序号的核心作用,详解流量控制机制,并结合 Linux 内核源码和实战案例,彻底搞懂 TCP 连接管理的底层原理。所有内容均严格基于 TCP 协议规范和 Linux 内核实现,力求做到理论与实践相结合。
一. TCP 核心知识点回顾
在开始新内容之前,我们先回顾几个关键结论,这些是理解后续内容的基础:
- TCP 通信双方交换的是完整的 TCP 报文:即使是单纯的应答,也至少包含一个完整的 TCP 报头,不会只发送单独的标志位。
- 可靠性的本质不是 "必须送达":而是 "无论发送成功还是失败,发送方都能知道结果"。收到应答表示成功,超时未收到应答则判定为失败并重传。
- TCP 是全双工协议:通信双方可以同时发送和接收数据,这一特性深刻影响了 TCP 的很多设计,包括捎带应答、三次握手等。

二. TCP 发送数据的两种工作模式
TCP 提供了两种数据发送模式,分别适用于不同的数据量场景:
2.1 串行发送模式(停等协议)
这是最简单的发送模式:发送方发送一个报文后,必须等待接收方的 ACK 应答,才能发送下一个报文。
html
主机A 主机B
|---- 数据1 ---->|
| |
|<---- ACK1 -----|
| |
|---- 数据2 ---->|
| |
|<---- ACK2 -----|
优点:实现简单,不会出现乱序和重复问题。
缺点:效率极低,尤其是在网络往返时间(RTT)较长的情况下,大部分时间都在等待应答。
这种模式只适用于发送少量数据的场景,在实际网络通信中很少单独使用。
2.2 并行发送模式(流水线模式)
这是 TCP 最主流的发送模式:发送方可以在未收到前一个报文的 ACK 应答时,连续发送多个报文。
html
主机A 主机B
|---- 数据1 ---->|
|---- 数据2 ---->|
|---- 数据3 ---->|
| |
|<---- ACK1 -----|
|<---- ACK2 -----|
|---- 数据4 ---->|
|<---- ACK3 -----|
优点:将多个报文的等待时间重叠,大幅提高了网络吞吐量和传输效率。
问题:并行发送带来了新的挑战 ------ 如何区分哪个报文丢了?如何保证数据按序到达?如何处理重复报文?
为了解决这些问题,TCP 引入了两个核心字段:32 位序号 和32 位确认序号。


三. 序号与确认序号的深度解析
序号和确认序号是 TCP 可靠性和高效性的核心,几乎所有的 TCP 机制都建立在这两个字段之上。
3.1 序号的本质
TCP 将发送缓冲区中的数据看作一个连续的字节数组,序号本质上就是这个字节数组的下标。
每个 TCP 报文的序号字段,填写的是该报文第一个数据字节的下标。例如:
- 发送缓冲区第 1~1000 字节组成的报文,序号为 1
- 第 1001~2000 字节组成的报文,序号为 1001
- 第 2001~3000 字节组成的报文,序号为 2001


3.2 确认序号的定义
确认序号的定义非常关键:确认序号 = 收到的最后一个字节的序号 + 1。
它的含义是:我已经收到了确认序号之前的所有字节,下一次请从确认序号开始发送。
例如:
- 主机 B 收到了序号为 1~1000 的字节,回复确认序号 1001
- 主机 B 收到了序号为 1001~2000 的字节,回复确认序号 2001
重要特性:累积确认
如果主机 B 收到了 1~1000 和 2001~3000 字节,但没有收到 1001~2000 字节,它只会回复确认序号 1001,而不会确认 2001~3000 字节。这保证了 TCP 数据的按序交付。

3.3 序号的三大核心功能
序号机制解决了并行发送带来的所有问题:
- 保证可靠性:通过确认序号,发送方可以准确知道哪些数据已经被接收,哪些需要重传。
- 数据去重:由于超时重传,接收方可能会收到重复的报文。通过序号,接收方可以轻松识别并丢弃重复报文。
- 保证按序到达:网络传输可能导致报文乱序,接收方可以根据序号对报文进行排序,然后按序交付给应用层。
3.4 为什么需要同时有序号和确认序号?
很多初学者会问:只用一个序号字段不行吗?应答时直接把序号 + 1 返回不就可以了?
答案在于 TCP 的全双工特性 和捎带应答机制。
捎带应答:当主机 B 需要给主机 A 发送数据时,它可以把对主机 A 之前数据的 ACK 应答,"搭顺风车" 放在自己的数据报文中一起发送。这样可以减少网络报文的数量,提高效率。
例如:
- 主机 A 给主机 B 发送数据(序号 1~1000)
- 主机 B 正好有数据要发给主机 A,它可以在自己的数据报文中,同时设置确认序号 1001 和自己的序号(比如 5001~6000)
这种情况下,一个报文既是数据报文,又是应答报文,必须同时包含序号(自己发送数据的序号)和确认序号(对对方数据的确认)。

四. 16 位窗口大小与流量控制
即使网络带宽足够大,发送方也不能无限制地发送数据,因为接收方的处理能力是有限的。如果发送方发送太快,导致接收方的接收缓冲区被填满,后续的数据会被直接丢弃,造成不必要的网络资源浪费。
为了解决这个问题,TCP 引入了流量控制机制,而 16 位窗口大小字段就是流量控制的核心。
4.1 窗口大小的含义
窗口大小字段表示接收方当前接收缓冲区的剩余空间大小,单位是字节。
接收方在发送 ACK 应答时,会将自己接收缓冲区的剩余空间大小填入窗口大小字段,告诉发送方自己还能接收多少数据。
发送方根据这个值,调整自己的发送速度:
- 窗口大:接收方处理能力强,发送方可以加快发送速度
- 窗口小:接收方处理能力弱,发送方需要减慢发送速度
- 窗口为 0:接收方缓冲区已满,发送方必须停止发送数据


4.2 流量控制的工作流程
html
主机A(发送方) 主机B(接收方,缓冲区大小8192字节)
| |
|---- 数据1~1000 ---->| 缓冲区剩余:7192字节
| |
|<-- ACK(1001, 7192)-| 告诉A还能接收7192字节
| |
|-- 数据1001~5000 -->| 缓冲区剩余:3192字节
| |
|<-- ACK(5001, 3192)-| 告诉A还能接收3192字节
| |
|-- 数据5001~8192 -->| 缓冲区剩余:0字节
| |
|<--- ACK(8193, 0) --| 告诉A停止发送
| |
| | 应用层读取了4096字节,缓冲区剩余4096字节
| |
|<-- ACK(8193, 4096)-| 窗口更新,告诉A可以继续发送
| |
|- 数据8193~12288 -->|
4.3 窗口探测机制
当接收方的窗口为 0 时,发送方会停止发送数据。但如果后续接收方的窗口更新报文在传输过程中丢失了,发送方会一直等待,导致死锁。
为了避免这种情况,TCP 引入了窗口探测机制:当发送方收到窗口为 0 的 ACK 后,会定期发送一个只有 1 字节数据的窗口探测报文,询问接收方当前的窗口大小。
五. 标志位字段详解
TCP 报头中有 6 个关键的标志位,它们的本质是区分报文类型,告诉接收方应该如何处理这个报文。
我们先来看 Linux 内核中tcphdr结构体中标志位的定义(位于include/linux/tcp.h):
c
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#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;
__sum16 check;
__be16 urg_ptr;
};
可以看到,每个标志位只占 1 个比特位,这是 C 语言位段特性的典型应用。下面我们重点讲解三个最核心的标志位:
5.1 ACK 标志(确认标志)
- 作用:表示确认序号字段是否有效。
- 使用场景:除了最初的 SYN 报文,所有 TCP 报文的 ACK 标志都应该置 1。
- 含义:当 ACK=1 时,表示这个报文包含对之前收到数据的确认。


5.2 SYN 标志(同步标志)
- 作用:请求建立连接。
- 使用场景:在 TCP 三次握手的前两个报文中使用。
- 含义:当 SYN=1 时,表示这是一个连接请求报文。
5.3 FIN 标志(结束标志)
- 作用:通知对方本端要关闭连接。
- 使用场景:在 TCP 四次挥手的过程中使用。
- 含义:当 FIN=1 时,表示本端已经没有数据要发送了,请求关闭连接。
其他三个标志位的作用:
- URG:紧急指针有效,表示报文中有紧急数据
- PSH:提示接收端立即将数据从 TCP 缓冲区推送给应用层
- RST:强制重置连接,用于处理异常情况
六. TCP 连接管理机制
TCP 是面向连接的协议,任何数据传输之前都必须先建立连接,数据传输完成后必须释放连接。TCP 连接的建立和释放分别通过三次握手 和四次挥手来完成。
6.1 三次握手建立连接
三次握手的完整流程
html
客户端(CLOSED) 服务器(LISTEN)
| |
|---- SYN(seq=x) ---------->| 第一次握手:客户端请求建立连接
| |
|<-- SYN+ACK(seq=y, ack=x+1)-| 第二次握手:服务器确认连接请求
| |
|---- ACK(seq=x+1, ack=y+1)->| 第三次握手:客户端确认服务器的确认
| |
|(ESTABLISHED) |(ESTABLISHED)
为什么是三次握手?(面试高频考点)
这是 TCP 连接管理中最经典的面试题,核心原因有两个:
- 以最小次数验证全双工通信
- 第一次握手:服务器知道客户端可以发送数据
- 第二次握手:客户端知道服务器可以发送和接收数据
- 第三次握手:服务器知道客户端可以接收数据
三次握手是验证全双工通信的最小次数。如果只有两次握手,服务器无法确认客户端是否能接收数据。
- 以最小成本确认双方的通信意愿
- 客户端发送 SYN:表示我想和你建立连接
- 服务器发送 SYN+ACK:表示我同意和你建立连接
- 客户端发送 ACK:表示我知道你同意了
三次握手完成了双方意愿的确认。如果只有两次握手,服务器无法确认客户端是否收到了自己的同意报文。
三次握手的状态转换
- 客户端:CLOSED → SYN_SENT → ESTABLISHED
- 服务器:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED



6.2 四次挥手断开连接
四次挥手的完整流程
html
主动关闭方(ESTABLISHED) 被动关闭方(ESTABLISHED)
| |
|---- FIN(seq=m) ----------->| 第一次挥手:主动关闭方请求关闭
| |
|<---- ACK(ack=m+1) --------| 第二次挥手:被动关闭方确认关闭请求
|(FIN_WAIT_2) |(CLOSE_WAIT)
| | 被动关闭方继续发送剩余数据
| |
|<---- FIN(seq=n) ----------| 第三次挥手:被动关闭方数据发送完毕,请求关闭
| |
|---- ACK(ack=n+1) --------->| 第四次挥手:主动关闭方确认
|(TIME_WAIT) |(CLOSED)
| |
| 等待2MSL时间 |
| |
|(CLOSED) |
为什么是四次挥手?
建立连接时,服务器的 SYN 和 ACK 可以合并在一个报文中发送(捎带应答),但断开连接时不能合并,原因是:
- 当主动关闭方发送 FIN 时,只表示主动关闭方没有数据要发送了
- 被动关闭方可能还有数据没有发送完,它需要先发送 ACK 确认,然后继续发送剩余数据
- 等被动关闭方的数据全部发送完毕后,它才会发送 FIN 表示自己也没有数据要发送了
因此,断开连接需要四次挥手。




结尾:
html
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:本文我们深入讲解了 TCP 协议的核心机制:从并行发送模式引出序号和确认序号的作用,详解了流量控制的原理,结合 Linux 内核源码分析了标志位的实现,并彻底搞懂了三次握手和四次挥手的底层逻辑。TCP 协议的设计充满了智慧,它在不可靠的网络之上,通过一系列精巧的机制,实现了可靠、高效的数据传输。当然,TCP 的复杂之处还不止于此,为了进一步提高传输性能,TCP 还引入了滑动窗口、快速重传、拥塞控制等机制,这些内容我们将在后续的文章中继续拆解。如果本文对你有帮助,欢迎点赞、收藏、关注,我会持续分享更多 Linux 系统编程和网络编程的干货内容。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど
