目录
- TCP协议格式
- 确认应答(ACK)机制
- 超时重传机制
-
- [超时重传的计时器 RTO 为什么要动态调整?](#超时重传的计时器 RTO 为什么要动态调整?)
- 连接管理
- 三次握手
- 四次挥手
- 滑动窗口
- 流量控制
- 拥塞控制
- 捎带应答
- 延迟应答
- 面向字节流
- 粘包问题
- TCP如何避免历史连接报文
- [TCP 异常情况](#TCP 异常情况)
- TCP小结
- [TCP/UDP 对比](#TCP/UDP 对比)
-
- [用 UDP 实现可靠传输](#用 UDP 实现可靠传输)
TCP协议格式

- 源端口、目的端口:分别对应发生进程的端口和接收进程的端口
- 序号:标记 TCP 报文段的字节位置(面向字节流),确保数据按序重组,避免重复。
- 确认序号:告知对方 "已成功接收至该序列号的数据",确认号 = 期望收到的下一个序列号。
- 4位首部长度:标识TCP的报头长度包括选项,标识范围[5,16],单位4字节->[20,64]字节
- 6个标志位
- 窗口大小:接收窗口的大小
- 校验和:校验 TCP 报头 + 数据的完整性(检测传输过程中的错误)。
- 紧急指针:仅当 URG=1 时有效,指示紧急数据在报文段中的结束位置(紧急数据优先处理)
6位标志位
- SYN:同步序列号,用于三次握手建立连接(SYN=1 表示发起连接请求)。
- ACK:确认标志,用于响应和确认数据(ACK=1 时确认号有效)。
- FIN:终止标志,用于四次挥手释放连接(FIN=1 表示无数据要发送)。
- RST:复位标志,强制关闭异常连接(如端口未监听时,服务器返回 RST 报文)。
- PSH:推送标志,要求接收方立即将数据交给应用层(不缓存)。
- URG:紧急标志,标识报文段中有紧急数据(需配合 "紧急指针" 字段)。
确认应答(ACK)机制
TCP 确认应答是可靠传输的核心,核心逻辑:接收方收到数据后,向发送方返回 "确认报文(ACK)",告知 "已成功接收的数据范围",发送方据此判断是否重传,确保数据不丢失、不重复、按序到达
应答无需再应答
序号的作用:
确认到达,防丢包
将乱序到达的报文,还原正确顺序
对重复获取的报文进行去重
超时重传机制
TCP 超时重传是可靠传输的关键兜底机制,核心逻辑:发送方发送报文段后,启动超时计时器,若超时未收到对应 ACK,自动重传该报文段,确保数据不丢失。
核心工作流程:
- 发送方发送报文段(含序列号 Seq),同时启动 "重传超时计时器(RTO)";
- 若在 RTO 内收到接收方的 ACK(确认号 = Seq + 数据长度),则关闭计时器,正常发送下一批数据;
- 若 RTO 超时仍未收到 ACK,立即重传该报文段,同时调整 RTO(避免频繁重传占用网络资源);
- 重传后仍未收到 ACK,会触发 "指数退避"(RTO 按 2 倍、4 倍、8 倍... 递增),最多重传指定次数(通常 5 次),仍失败则关闭连接。
超时重传的计时器 RTO 为什么要动态调整?
网络延迟(RTT)会随拥堵情况波动,固定 RTO 可能导致 "误重传"(延迟大时 RTO 过短)或 "传输低效"(延迟小时 RTO 过长),动态调整可适配网络变化。
连接管理
在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接
三次握手
为了保证TCP连接的可靠性在建立连接时需要进行三次握手
三次握手的核心目标
- 双向确认通信双方的 "发送 + 接收" 能力均正常(链路双向可达);
- 交换双方的初始序列号(ISN),为后续可靠数据传输奠定基础;
- 协商 TCP 关键选项(如 MSS、窗口缩放),初始化连接上下文;
- 过滤历史连接请求,避免无效连接建立。
三次握手具体流程
- 客户端向服务器发送SYN同步报文,报文内容生成的ISN随机初始序号和窗口信息,状态由CLOSE变为SYN_SEND,服务端接收后状态由LISTEN变为SYN_RECV
- 服务器向客户端发送ACK+SYN, 报头中SYN=1、ACK=1,报文内容生成的ISN随机初始序号、确认序号客户端ISN+1和窗口信息,客户端接收后从SYN_SENT(已发送 SYN)状态进入ESTABLISHED(已建立连接)状态。
- 客户端发送ACK,确认序号服务器ISN+1,可携带应用层数据,服务器接收后进入ESTABLISHED状态。

半连接、全连接队列
- 半连接队列:存储状态为SYN_RECV的连接(仅完成前两次握手),大小由内核参数tcp_max_syn_backlog控制;
- 全连接队列:存储状态为ESTABLISHED但未被应用层accept()取走的连接,大小由somaxconn(默认 128)和应用层监听时设置的 backlog 共同决定;
在第二次握手时服务器会将暂未完成的连接放到半连接队列中,第三次握手完成后将其放到全连接队列 - accept是从全连接队列中获取已经创建好的连接
- SYN攻击就是用大量的假IP发送SYN,占满服务器半连接队列,使其不能接收正常的SYN请求

为什么两次握手不行
三次握手最重要的目的就是验证双方收发功能的正常,以保证可靠性
- 服务器无法验证客户端接收能力正常(因为客户端未发送第三次握手ACK),无法保证双方收发能力正常
- 无法过滤历史请求,服务器接收到历史SYN会直接建立连接,之后客户端无响应,重传SYN+ACK超限后释放连接浪费资源,遭到SYN攻击时直接建立连接,相比半连接伤害更大
- 无法实现双向序列号同步,服务器无法确认客户端是否收到自己的ISN,导致服务器无法确定后续数据传输的起始序列号,可靠性无法保障。
握手报文丢失问题
- 第一次SYN丢失:服务器无响应,超时后客户端重发第一次SYN(完全相同)
- 第二次SYN+ACK丢失:客户端收不到ACK重发SYN,服务器收不到第三次握手ACK重发SYN+ACK
- 第三次ACK丢失:服务器收不到ACK重发SYN+ACK
重传超时机制:当未收到ACK时等待一定时间后重传,每次等待时间为上一次二倍,重传达到一定次数时关闭连接
客户端重传第一次SYN,服务器收到重复SYN:收到重发的原因就是对方未收到ACK,服务器重发第二次握手SYN+ACK
SYN攻击问题
SYN 攻击是针对 TCP 三次握手的典型 DoS/DDoS 攻击,攻击者伪造大量虚假 IP 的 SYN 报文发送给服务器,耗尽服务器半连接队列资源,导致正常连接请求无法被处理。
攻击者不发送第三次 ACK 报文,服务器会持续重传 SYN+ACK 直至超时,半连接被长期占用;
解决方法:
- (核心绕开半连接)SYN Cookie:半连接队列满时,服务器不存储连接信息,通过算法生成 Cookie 作为 ISN,第三次握手时验证合法性,无需占用队列;
- 增大队列容量:调整tcp_max_syn_backlog(半连接队列)、somaxconn(全连接队列)内核参数;
- 缩短超时重传:调小tcp_synack_retries(SYN+ACK 重传次数),加快半连接释放;
四次挥手
核心是通信双方通过四次报文交互,确保双方都完成数据传输、确认关闭意图,最终安全终止连接(TCP 是全双工通信,关闭时需分别关闭两个方向的数据流)。
本质:TCP连接是全双工有两条流,四次挥手本质是通过两次FIN+ACK关闭TCP全双工的两条流
流程:
- 客户端调用关闭连接函数(close、shutdown),发出FIN报文,由ESTABLISHED状态变为FIN_WAIT_1状态,表示客户端不再向服务端发送数据(关闭客户端→服务端的写通道),但仍可接收服务端数据。
- 服务端接收FIN报文后直接发送一个ACK报文,由ESTABLISHED状态变为CLOSE_WAIT状态,表示确认客户端已停止向自己发送数据,等待连接关闭,客户端接收到ACK后状态由FIN_WAIT_1变为FIN_WAIT_2状态
- 服务端如果有数据需要发送,将数据发送完后调用关闭连接函数,如果没有数据发送就直接调用关闭连接函数,发送FIN报文,表示服务端也不再向客户端发送数据,状态由CLOSE_WAIT变为LAST_ACK
- 客户端接收到FIN后发送ACK应答,状态由FIN_WAIT_2变为TIME_WAIT,等待2个MSL后变为CLOSE状态,服务器接收到ACK后变为CLOSE状态,连接彻底关闭

shutdown
shutdown是协议层上的关闭,关闭读端或写端并刷新缓冲区,不会释放套接字资源,close直接释放套接字资源不刷新缓冲区,会直接丢弃缓冲区未发完的数据。
所以双方都是在合适时机先调用shutdown保证需要读或写的数据读完或写完,再调用close释放资源
如果直接调用close,已经write但缓冲区未发送的数据会被丢弃,直接释放套接字资源,而shutdown会刷新缓冲区后关闭读端或写端,不释放资源
应用层关闭逻辑
客户端需要保证需要读的数据读完:先调用shutdown关闭写端发送FIN,等read()读到EOF返回0时说明服务器的数据已经发完,并且已经读完,此时调用close释放套接字资源
服务端需要保证需要发的数据发完:read()读取到EOF返回0时,说明收到FIN对方关闭读端,然后调用shutdown关闭写端并刷新缓冲区数据发送给对方,再调用close释放套接字资源
半关闭
半关闭定义:TCP 全双工连接中,可单独关闭一条数据流(写流 / 读流),另一条保持开放,称为半关闭(不是独立状态,是四次挥手的中间特征);
半关闭的价值:半关闭是 TCP 可靠传输的重要设计,确保双向数据都传输完成后再释放连接。
核心状态对应:
- 客户端 FIN-WAIT-2 状态:写流已关(不发数据),读流开放(可收服务端数据);
- 服务端 CLOSE-WAIT 状态:读流感知 EOF(客户端写流已关),写流开放(可发数据);

服务端可在半关闭时将未发送的数据全部发送,且客户端因保留读通道(FIN-WAIT-2 状态)可完整接收,最终实现双向数据无丢失。
为什么需要四次挥手
通信双方通过四次报文交互,确保双方都完成数据传输、确认关闭意图,最终安全终止连接(TCP 是全双工通信,关闭时需分别关闭两个方向的数据流)。
TCP 是全双工通信(两条独立的数据流:A→B、B→A),关闭连接时需要分别关闭每条数据流,且每条数据流的关闭都需要 "请求(FIN)+ 确认(ACK)" 的交互,两次独立的 "FIN+ACK" 闭环叠加,最终形成四次交互。
TIME_WAIT状态与MSL
MSL 指 TCP 报文在网络中能够存活的最长时间,即一个报文从发送到被丢弃的最大时长(超过该时间,报文会被网络设备彻底清除)。
TIME_WAIT状态在第四次挥手ACK发送后进入,等待2个MSL时间后彻底关闭进入CLOSE状态,核心作用是确保连接安全、彻底关闭。
TIME_WAIT状态作用
- 确保主动关闭方的最后一个 ACK 被被动关闭方接收(解决 ACK 丢失问题),若丢失对方会超时重传FIN,此时需要重新发送ACK
- 过滤历史报文(正常MSL后死亡,如果重发FIN最多2*MSL被接收),避免直接重新建立相同四元组连接接收到历史报文干扰
TIME_WAIT堆积问题
主动关闭连接方会处于TIME_WAIT状态,TIME_WAIT 堆积是高并发短连接场景下主动关闭方(通常是客户端,也可能是服务端)在大量连接关闭后,会产生大量处于 TIME_WAIT 状态的连接,导致端口耗尽、新连接无法建立,或系统资源(内存、文件描述符)被过度占用。
解决方法:
- 开启端口复用:Linux 下通过设置 SO_REUSEADDR 套接字选项(应用层代码配置),允许复用处于 TIME_WAIT 状态的端口
- 改用长连接,源头避免堆积
滑动窗口
滑动窗口是 TCP 实现可靠传输、流量控制与管道化高效传输的核心机制,它允许发送方在未收到确认(ACK)时连续发送多段数据,窗口随 ACK 动态右移,同时受接收窗口(rwnd)与拥塞窗口(cwnd)共同约束。
核心作用:是在保证数据可靠传输的前提下,动态调整发送速率,以实现高效的、与接收方处理能力相匹配的数据流控制。
滑动窗口大小=min(拥塞窗口,对方接收窗口大小) 受网络拥塞程度和对方接收缓冲区剩余空间约束
发送缓冲区当中的数据分为三部分:
- 已经发送并且已经收到ACK的数据。
- 可发送且没有收到ACK的数据(滑动窗口)。
- 还没有发送的数据。
滑动窗口移动:当收到ACK确认序号>滑动窗口左端时,左端右移到ACK确认序号位置,右端根据滑动窗口大小调整
丢包问题
丢包有两种情况:
- 数据包抵达,但ACK丢了
- 数据包丢了

第一种根据确认序号可以确认数据包递达
第二种收到重复的相同确认序号(只能增长到丢包的序号)
快重传
快重传:当发送方收到3 个相同的重复 ACK(即对同一个未确认字节的 ACK),直接判定对应数据段丢失,立即重传该丢失段,无需等待重传定时器超时。
本质是根据收到相同的确认序号判断有多个后续的包递达,但最左端的包未递达
为什么是三次:2次可能是乱序到达,3次很可能是丢包,4次更可能是丢包,但三次效率更高
流量控制
TCP支持根据接收端的接收数据的能力来决定发送端发送数据的速度,这个机制叫做流量控制(Flow Control)。
流量控制是根据对方报文中窗口大小进行发送数据速度控制,过大就增大速度,过小就降低速度,为0就停止,并定时发送窗口探测包,检测是否有空间产生,接收方也会在缓冲区有剩余空间时发送窗口更新包。
窗口大小:16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么? 实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口 字段的值左移 M 位;
拥塞控制
TCP经过网络传输数据,不仅要考虑根据对方能力进行流量控制,也需要考虑网络环境进行发送数据速度的调整
TCP 拥塞控制的核心作用:
- 适配网络承载能力
- 在避免网络拥塞的前提下最大化传输吞吐量
- 同时减少无效重传和资源浪费
- 从而保障 TCP 传输的稳定性与高效性。

拥塞窗口
- 发送方维护的一个状态变量,表示在不引起网络拥塞的前提下,自己可以发送的数据量。
- 实际发送窗口 = min(对方接收窗口rwnd, 拥塞窗口cwnd)
- 拥塞窗口单位一般是MSS(TCP报文的最大长度)
拥塞控制原理
- TCP默认认为丢包的原因是网络拥塞
- 发生丢包时要降低发送速度,并通过探测反馈恢复速度,提高网络带宽利用率
- 对于网络可以避免恶化阻塞,对于TCP避免因网络拥塞造成多次无效重传
拥塞控制方法
慢启动
TCP在刚建立连接时并不知道网络拥塞情况,所以采用探测+快速提速的慢启动来进行拥塞控制
内容:
- 拥塞窗口大小为1,开始发送报文
- 每收到一个ACK拥塞窗口大小+1,也就是每经过一轮发送接收时间(窗口大小*2)进行指数快速增长
- 当拥塞窗口大小=ssthresh(慢启动阈值)时进入拥塞避免,进行线性增长
拥塞避免
拥塞窗口大小按 "线性增长"(每经过一个 RTT,cwnd 增加 1 MSS),避免因增长过快触发拥塞,而是缓慢调整至网络最佳值
拥塞发生
网络出现拥塞时,也就是触发重传时进入拥塞发生
两种重传方式对应不同的处理方法:
- 超时重传:网络拥塞可能较严重,此时将ssthresh置为当前拥塞窗口的一半,拥塞窗口大小置为1,进入慢启动
- 快重传:可以接收到多个ACK说明只有部分包丢失,网络轻度拥塞,为了提高网络带宽利用率,不能重新进入慢启动,而是将ssthresh置为当前拥塞窗口的一半,拥塞窗口/2,进入快速恢复
快速恢复
内容:
- 将拥塞窗口大小+3(已收到三个ACK)
- 每收到一个ACK拥塞窗口大小+1
- 直到收到的ACK中有新的确认序号(丢包已修复)结束
- 将拥塞窗口大小=ssthresh进入拥塞避免
快速恢复的作用:在检测到网络发生单包丢失这类轻度拥塞时,不中断数据传输,快速完成丢包修复并平稳地将连接状态调整到安全水平,从而最大限度地维持吞吐量,避免性能断崖式下跌。
捎带应答
接收方收到数据后发生的应答不是只有一个ACK,而是将ACK标志位置为1的TCP报头,无报文。
捎带应答:接收方在向发送方发送数据报文时,将对之前接收数据的 ACK(确认信息)"搭载" 在该数据报文上一起发送,无需单独发送纯 ACK 报文,最大化降低网络开销。
延迟应答
接收方收到数据后,不立即返回 ACK,而是延迟一段时间(通常 200ms 内,或等待自身有数据要发送时)再回复,减少 ACK 报文数量,降低网络开销。
作用:
- 利用捎带应答减少纯ACK报文,减少网络开销
- 给上层拿出缓冲区数据的时间,增大发送报文时的窗口大小
注意:在接收缓冲区满时不能延迟应答,需要及时通知发送方停止发送,避免无效发送
面向字节流
面向字节流的特点:内核只负责运输,数据的边界由应用层自己控制处理
- 数据无结构、无边界,以字节为最小单位传输;
- 内核只负责传输,不解析内容,灵活性极高;
- 应用层需自己处理解析、边界、部分读写等问题
粘包问题
是由于TCP是面向字节流的,内核对应用层的数据边界无感知,所以应用层通过TCP接收到的数据可能由多组数据构成(粘包),半包可能由半组数据构成
处理边界的方法:由应用层自己处理
头部长度:在报头添加整个报文的长度属性
特殊字符分割:用特殊字符分割
固定长度:报文长度固定,固定读取
TCP如何避免历史连接报文
避免历史报文可以从两方面分析:
1.消除报文,TCP连接断开后主动关闭连接的一方进入TIME_WAIT状态保存两个MSL时间,使无法建立重复的四元组,等待报文被网络消除,
2.接收验证,随机序号初始化(避免混淆)+序号验证,两种方法互补
TCP 异常情况
进程终止: 进程终止会释放文件描述符, 仍然可以发送 FIN. 和正常关闭没有什么区别.
机器重启: 和进程终止的情况相同. 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已 经不在了, 就会进行 reset. 即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.
另外, 应用层的某些协议, 也有一些这样的检测机制. 例如 HTTP 长连接中, 也会定期检测对方的状态. 例如 QQ, 在 QQ 断线之后, 也会定期尝试重新连接.
TCP小结
TCP在保证可靠性的同时也尽可能地提高性能
可靠性:
• 校验和
• 序列号(按序到达)
• 确认应答
• 超时重发
• 连接管理
• 流量控制
• 拥塞控制
提高性能:
• 滑动窗口
• 快速重传
• 延迟应答
• 捎带应答
TCP/UDP 对比
• TCP 用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
• UDP 用于对高速传输和实时性要求较高的通信领域, 例如, 早期的 QQ, 视频传 输等. 另外 UDP 可以用于广播
归根结底, TCP 和 UDP 都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定.
用 UDP 实现可靠传输
在应用层手动实现 TCP 核心可靠机制
例如:
• 引入序列号, 保证数据顺序;
• 引入确认应答, 确保对端收到了数据;
• 引入超时重传, 如果隔一段时间没有应答, 就重发数据;