文章目录
- [一、TCP 协议](#一、TCP 协议)
- [二、TCP 补充](#二、TCP 补充)
一、TCP 协议
TCP 全称为 "传输控制协议",人如其名,要对数据的传输进行一个详细的控制。
1、TCP 协议头
TCP 协议头固定 20 字节,加上选项最多 60 字节。

-
源/目的端口号: 标识数据从哪个进程来、到哪个进程去
-
32 位序号/确认号: 保证数据有序、不重复,实现可靠传输
-
4 位首部长度: 单位为 4 字节,最大 15×4=60 字节
-
6 位标志位: 控制连接状态
URG:紧急指针有效ACK:确认号有效PSH:提示接收方立即读取数据RST:复位连接SYN:请求建立连接(同步报文)FIN:请求关闭连接(结束报文)
-
16 位窗口大小: 流量控制,告知对方自己的接收缓冲区大小
-
16 位校验和: 校验 TCP 首部 + 数据的完整性,出错直接丢弃
-
16 位紧急指针: 标识紧急数据的位置
-
选项(最多 40 字节): 扩展功能(如 MSS、时间戳等)
总结: TCP 头里的端口号决定 "给谁",序号 / 确认号保证 "可靠",标志位控制 "连接的建立与断开",窗口大小实现 "不被冲垮"。
2、确认应答(ACK)机制
TCP 给每个字节数据都编了号(序列号) ,接收方用 ACK 确认号告诉发送方:到哪个字节为止我都收到了,下次从这个编号的下一个字节发

序列号怎么用?
TCP 是面向字节流的,发送方会把数据按字节顺序编号:
- 比如一次发送
1~1000字节,这个段的序列号就是1 - 下一段
1001~2000字节,序列号就是1001
ACK 确认号的含义
接收方回复的 ACK 里,确认号 = 下一个要接收的字节编号,也就是:
- 收到
1~1000后,回复ACK=1001
表示1000 之前的字节都收到了,下次从 1001 开始发 - 收到
1001~2000后,回复ACK=2001
表示:2000 之前的字节都收到了,下次从 2001 开始发
总结: 序列号标记 "发了什么",ACK 确认号标记 "收到哪了",两者配合,TCP 才能保证数据不丢、不乱、不重复。
3、超时重传机制
超时重传是 TCP 保证可靠传输的核心机制,一句话概括就是超时没收到 ACK,就把数据重发一遍
两种丢包场景
TCP 不管哪种情况,都会触发重传:
- 数据丢包: 发送方的数据包在路上丢了,收不到 ACK

- ACK 丢包: 数据到了接收方,但确认应答丢了,发送方也收不到 ACK

重复数据的处理
接收方会收到重复数据,怎么办?
- 利用序列号识别重复包,直接丢弃
- 不影响上层应用,只保证传输层的正确性
超时时间怎么定?
TCP 会动态计算超时时间,兼顾效率和稳定性:
- 以 500ms 为基础单位
- 每次重传超时时间指数级递增:
500ms → 1000ms → 2000ms → 4000ms... - 超过最大重传次数后,TCP 认为网络 / 对方异常,强制关闭连接
总结:超时重传 + 序列号去重,让 TCP 能在丢包的网络环境下,依然保证数据可靠送达。
4、连接管理机制
1)三次握手
目标:双方确认彼此收发能力正常
| 阶段 | 客户端 | 服务端 | 报文含义 |
|---|---|---|---|
| 1 | CLOSED → SYN_SENT,发 SYN |
LISTEN → SYN_RCVD,收到 SYN |
客户端发起连接请求 |
| 2 | 收到 SYN+ACK |
发 SYN+ACK |
服务端同意并确认连接 |
| 3 | 发 ACK,进入 ESTABLISHED |
收到 ACK,进入 ESTABLISHED |
客户端最终确认,连接建立 |
总结: SYN → SYN+ACK → ACK,双方同步序列号,确认通信能力。
2)四次挥手
目标:双方数据都收发完毕,安全可靠关闭连接
| 阶段 | 客户端 | 服务端 | 报文含义 |
|---|---|---|---|
| 1 | ESTABLISHED → FIN_WAIT_1,发 FIN |
收到 FIN,回 ACK,进入 CLOSE_WAIT |
客户端主动申请关闭 |
| 2 | 收到 ACK,进入 FIN_WAIT_2 |
CLOSE_WAIT,处理剩余业务数据 |
服务端确认关闭请求,暂不关闭 |
| 3 | 收到 FIN,回 ACK,进入 TIME_WAIT |
数据处理完,发 FIN,进入 LAST_ACK |
服务端数据发完,也申请关闭 |
| 4 | 等待 2MSL 后,进入 CLOSED |
收到 ACK,进入 CLOSED |
客户端最后确认,连接彻底断开 |
关键状态说明
ESTABLISHED:连接建立完成,可读写数据TIME_WAIT:客户端必须等待2MSL,防止最后一个ACK丢失,导致服务端无法正常关闭CLOSE_WAIT:服务端收到客户端的FIN,等待应用层调用close()发FIN

3)TIME_WAIT 状态
现象: 先启动服务端,再启动客户端,服务端退出后立刻重启,会报错。

原因:
- 主动关闭连接的一方会进入
TIME_WAIT状态 ,持续2MSL时间。 - 服务端被
Ctrl+C终止,是主动关闭方,端口在TIME_WAIT期间被占用,无法立刻重新绑定。
为什么是 2MSL?
- 确保两个方向上的迟到报文、重传报文都消失,避免新连接收到旧数据。
- 保证最后一个
ACK丢失时,对方重发的FIN能被处理,让连接彻底关闭。
查看超时配置: cat /proc/sys/net/ipv4/tcp_fin_timeout

默认值通常为 60 秒(不同系统略有差异)。
解决 bind 失败:
在 bind() 之前,给 socket 设置 SO_REUSEADDR 选项,允许端口复用。
cpp
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

4)CLOSE_WAIT 状态
现象: 客户端断开连接后,服务器端连接停留在 CLOSE_WAIT 状态,无法正常释放。

根本原因: 服务器没有调用 close() 关闭 socket,导致四次挥手无法完成。
- 客户端主动断开连接(发
FIN),服务器收到后回复ACK,进入CLOSE_WAIT。 - 服务器的应用层没有调用
close(),就不会发送自己的FIN,连接卡在CLOSE_WAIT,无法继续挥手。

解决: 在客户端断开连接的分支里,加上 Close(),主动关闭 socket,让四次挥手走完。
总结: CLOSE_WAIT 是服务器端的 "僵尸连接",本质是代码漏了 close(),导致连接无法正常关闭。
5、滑动窗口

为什么要用滑动窗口?
为了解决 "一发一收" ACK 应答性能低的问题:
- 窗口大小: 无需等待 ACK 就能连续发送的最大数据量(比如窗口大小 4000 字节,可一次性发 4 段)。
- 工作逻辑: 先发满窗口内的所有数据;收到最早的 ACK 后,窗口向后滑动,继续发新数据。
- 核心优势: 把多个段的 "等待 ACK 时间" 重叠起来,大幅提升吞吐率。

丢包后的两种处理方式
-
情况 1:数据到了,但 ACK 丢了
- 后续的 ACK 会自动覆盖前面丢的 ACK,发送端只要收到后续确认,就知道前面的数据也都收到了,不需要重传 。

- 后续的 ACK 会自动覆盖前面丢的 ACK,发送端只要收到后续确认,就知道前面的数据也都收到了,不需要重传 。
-
情况 2:数据本身丢了(快重传机制)
- 发送端会反复收到同一个 ACK(比如一直收到 "下一个要 1001")。
- 当连续 3 次收到相同的 ACK,就判定对应段丢了,立刻重传该段。
- 接收端收到重传的数据后,会直接返回最新的 ACK(比如直接确认到 7001),因为中间的数据早就收到并缓存好了。

总结:滑动窗口通过批量发送提升效率,再用 "后续 ACK 补全" 和 "3 次重复 ACK 触发快重传" 解决丢包问题,既高效又可靠。
6、流量控制
什么是流量控制?
TCP 为了防止发送方发太快,接收方处理不过来 ,导致缓冲区溢出、丢包重传,会让接收方根据自己的处理能力,控制发送方的发送速度,这个机制就是流量控制。

核心工作逻辑
- 接收方通知窗口: 每次 ACK 应答里,都会带上自己当前的接收缓冲区剩余大小(即窗口大小),告诉发送方 "我还能收多少数据"。
- 发送方按窗口发送: 发送方会根据这个窗口大小,调整自己的发送速度 ------ 窗口越大,发得越快;窗口越小,发得越慢。
- 窗口为 0 时的处理: 如果接收方缓冲区满了,会把窗口设为
0,发送方就会暂停发送。- 为了避免后续接收方恢复后,窗口更新的 ACK 丢了导致双方 "僵住",发送方会定期发送窗口探测包,询问最新的窗口大小。
TCP 窗口大小的扩展
- TCP 首部的窗口字段是 16 位,理论上最大只能表示
65535字节。 - 实际中通过 TCP 选项里的窗口扩大因子 M 解决:
实际窗口大小 = 窗口字段的值 × 2M(即窗口字段值左移 M 位),以此支持更大的窗口,提升传输效率。
总结: 流量控制就是 "接收方说能收多少,发送方才敢发多少",靠 ACK 里的窗口大小实现,窗口为 0 时靠探测包打破僵局,再用窗口扩大因子突破 65535 字节的上限。
7、拥塞控制
什么是拥塞控制?
TCP 为了避免发送方一次性发太多数据,把网络 "挤爆",所以会根据网络的拥堵状态,动态调整发送速度 ,这个机制就是拥塞控制。

核心机制:慢启动 + 拥塞避免
- 慢启动(起步阶段)
- 目标: 先少量发送数据,探测网络状态,避免一开始就 "炸网"。
- 规则:
- 初始拥塞窗口(cwnd)很小(比如 1 个段)。
- 每收到 1 个 ACK,cwnd 就 + 1,窗口呈指数级增长(翻倍)。
- 当 cwnd 超过「慢启动阈值(ssthresh)」时,退出慢启动。
- 拥塞避免(稳定阶段)
- 目标: 防止窗口增长过快,进入拥堵状态。
- 规则:
- cwnd 超过 ssthresh 后,不再指数增长,而是每轮 RTT线性 + 1(加法增大)。
- 窗口缓慢增长,给网络 "喘息的机会"。

网络拥堵了怎么办?(丢包后的处理)
当发生超时重传(网络严重拥堵的信号):
- 把慢启动阈值
ssthresh减半(乘法减小)。 - 拥塞窗口
cwnd重置为 1,重新进入慢启动阶段。
拥塞控制的核心就是:慢启动指数爬坡,拥塞避免线性试探,一旦拥堵立刻 "踩刹车",把窗口减半重走慢启动,在 "快传数据" 和 "不挤爆网络" 之间找平衡。
就像热恋的感觉: 刚开始谈恋爱(慢启动),热情指数级上升;进入稳定期(拥塞避免),慢慢升温;一旦闹矛盾(丢包 / 超时),热情直接跌到谷底,得重新从慢启动开始试探
8、延迟应答
什么是延迟应答?
接收端收到数据后,不立刻回复 ACK,而是稍微等一会儿再应答 ,以此来提高传输效率。

为什么要延迟应答?
- 刚收到数据时,接收端的缓冲区剩余空间还不大(比如收到 500K,返回窗口 500K)。
- 但接收端处理速度很快,等一小会儿数据就被消费掉了,缓冲区又空出来了。
- 延迟应答后,返回的窗口会更大(比如直接返回 1M),窗口越大,网络吞吐量和传输效率就越高。
延迟应答的限制(不能无限等)
- 数量限制: 一般每收到 2 个数据包,就必须应答一次。
- 时间限制: 延迟时间不能超过固定值(通常是 200ms),超时必须应答。
总结:延迟应答就是 "攒一会儿再确认",用时间换更大的窗口,提高传输效率,同时用数量和时间限制避免 ACK 超时。
9、捎带应答
什么是捎带应答?
在延迟应答的基础上,当接收方(比如服务器)本身就要给发送方(比如客户端)回应用数据时,会把ACK 确认应答直接 "搭顺风车",跟服务器的响应数据一起发给客户端,不用单独发一次 ACK 包。
核心优势
- 减少了一次独立 ACK 包的发送,降低了网络开销和传输次数。
- 既实现了确认应答,又不额外占用带宽,效率拉满。

举例
就像图里的交互流程:
- 客户端发命令(如
EHLO),服务器既要回250响应,又要确认收到数据。 - 捎带应答会把 "确认收到数据的 ACK" 和
250响应合并在同一个包里发回去,不用分开两次发。
总结: 捎带应答就是 "把 ACK 和要回的数据打包一起发",省掉单独的 ACK 包,提升传输效率。
二、TCP 补充
1、面向字节流
内核缓冲区
创建 TCP Socket 时,内核会同时开辟两个缓冲区:
- 发送缓冲区: 你调用
write写的数据,先进入这里,再由 TCP 协议决定什么时候发、怎么发。 - 接收缓冲区: 收到的数据先存在这里,你调用
read时再从缓冲区取数据。
发送与接收的特点
- 发送端(write)
- 数据先写入发送缓冲区,不是立刻发出去。
- 太长的数据会被拆分成多个 TCP 段发送;太短的数据会先攒着,凑够合适长度再发。
- 接收端(read)
- 数据先到达接收缓冲区,应用程序再从这里读取。
- 读取方式和发送方式完全解耦,不用一一匹配。
关键特性
- 全双工: 同一个 TCP 连接,既可以读数据,也可以写数据(因为同时存在发送和接收两个缓冲区)。
- 字节流特性: 你怎么写、写几次,和对方怎么读、读几次,没有强制对应关系。比如:
- 你写 100 字节:可以一次写 100 字节,也可以分 100 次每次写 1 字节。
- 对方读 100 字节:可以一次读 100 字节,也可以分 100 次每次读 1 字节。
总结: TCP 面向字节流的本质,就是靠内核的发送 / 接收缓冲区,实现 "读写解耦、按需打包",同时支持全双工通信。
2、粘包问题
什么是粘包?
TCP 是面向字节流,没有消息边界,多个应用层数据包连在一起,应用层分不清从哪里拆分,这就是粘包。
为什么只有 TCP 有?
- TCP:传输层拆包 / 组包,应用层只看到连续字节流,无边界
- UDP:一个报文一个报文交付,自带边界,没有粘包问题
怎么解决?(核心:定边界)
- 定长协议: 所有包长度一样,按固定长度读
- 包头带长度: 包前加长度字段,先读长度再读数据
- 分隔符协议: 用特殊符号分隔包(不与内容冲突)
总结: TCP 字节流无边界导致粘包,UDP 报文有边界不会粘包;解决方法就是给应用层包加明确边界。
3、TCP 异常情况
1. 进程终止 / 机器重启
- 系统会释放文件描述符,正常发送 FIN 报文,按标准流程关闭连接,和正常关闭无本质区别。
2. 机器掉电 / 网线断开
- 对方无法发送 FIN 报文,接收端暂时认为连接仍存在。
- 接收端尝试写入数据时,会收到 RST 报文,直接感知连接断开。
- 即使无写入,TCP 的保活定时器会定期探测对方状态,超时无响应则释放连接。
- 应用层也常做额外检测(如 HTTP 长连接、QQ 的断线重连机制),保障可靠性。
4、TCP 小结
TCP 之所以复杂,就是为了同时实现可靠传输 和高性能传输两大目标。
保障可靠性的核心机制
- 校验和: 检查数据是否在传输中损坏
- 序列号: 保证数据按序到达,解决乱序问题
- 确认应答(ACK): 让发送方知道数据已被接收
- 超时重传: 丢包后自动重试,保证数据不丢失
- 连接管理: 三次握手建立、四次挥手关闭,避免无效连接
- 流量控制: 接收方控制发送方速度,防止缓冲区溢出
- 拥塞控制: 根据网络状态动态调整发送速度,避免网络拥堵
提升传输性能的核心机制
- 滑动窗口: 批量发送数据,减少等待 ACK 的时间
- 快速重传: 收到 3 次重复 ACK 直接重传,不用等超时
- 延迟应答: 等数据处理完再回复 ACK,放大窗口提升吞吐
- 捎带应答: 把 ACK 和应用层响应一起发,减少额外报文开销
其他关键机制
各种定时器:超时重传定时器、保活定时器、TIME_WAIT 定时器等,处理异常和连接状态维护。
5、基于 TCP 的应用层协议
| 协议 | 核心用途 | 默认端口 | 关键特点 |
|---|---|---|---|
| HTTP | 网页浏览、数据传输 | 80 | 明文传输,无加密,是 Web 的基础协议 |
| HTTPS | 加密的网页浏览 | 443 | 在 HTTP 基础上增加了 TLS/SSL 加密,保证数据安全 |
| SSH | 远程登录、安全命令行 | 22 | 加密的远程管理协议,替代不安全的 Telnet |
| Telnet | 早期远程登录 | 23 | 明文传输,无加密,现已基本被 SSH 淘汰 |
| FTP | 文件传输 | 21 (控制)、20 (数据) | 双端口设计,分别处理命令和数据传输 |
| SMTP | 发送邮件 | 25 | 用于邮件客户端向服务器发送邮件,或服务器间转发邮件 |
自己写的 TCP 程序,本质也是自定义的应用层协议,只要双方约定好数据格式、边界规则,就能实现通信。
6、TCP/UDP 对比
不能简单说谁更好,按需选用。
TCP
- 可靠、面向连接、有流量 / 拥塞控制,牺牲速度换可靠有序。
- 适用:文件传输、网页、重要数据交互等必须不丢包的场景。
UDP
- 无连接、不可靠、开销小、速度快、延迟低,不保证可靠但实时性强。
- 适用:直播视频、语音通话、游戏、QQ 这类看重实时性 场景;还支持广播、多播。
总结:TCP 保可靠,UDP 保实时;根据业务需求选择使用即可。
7、UDP 实现可靠传输
UDP 本身无连接、不可靠,要在应用层复刻 TCP 可靠机制:
- 加序列号: 保证数据有序、去重
- 加 ACK 确认应答: 确认对方已收到数据
- 加超时重传: 超时没收到 ACK 就重发
- 还可自行实现:滑动窗口、流量控制、拥塞控制、丢包重传等
总结:UDP 做传输层载体,应用层自己仿写 TCP 全套可靠逻辑