跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
编程语言算法

Linux TCP 可靠性与性能优化详解:从确认应答到拥塞控制

TCP 可靠性与性能优化涉及确认应答、超时重传、滑动窗口、流量控制及拥塞控制等核心机制。确认应答确保数据接收,超时重传处理丢包,滑动窗口提升吞吐。流量控制防止接收方过载,拥塞控制避免网络拥堵。此外还包括延迟应答、捎带应答优化,以及面向字节流带来的粘包问题解决方案和异常情况处理如保活定时器。理解这些机制有助于构建高效稳定的网络通信。

月光旅人发布于 2026/3/16更新于 2026/6/227 浏览
Linux TCP 可靠性与性能优化详解:从确认应答到拥塞控制

TCP 可靠性与性能优化详解:从确认应答到拥塞控制

一、确认应答(ACK)机制

1.1 什么是确认应答

确认应答(ACK):接收方告诉发送方"我已经收到了你的数据"。

类比:

唐僧讲经:唐僧说一句,悟空答一句"师父,我听懂了" 唐僧继续说下一句

TCP 的确认应答:

发送方发送数据 接收方收到数据后,发送 ACK 发送方收到 ACK 后,继续发送下一批数据
1.2 序列号和确认号的作用

序列号(Sequence Number):

  • 标识发送的数据的第一个字节的序号
  • 用于排序和去重

确认号(Acknowledgment Number):

  • 表示"下一个我要接收的序列号"
  • 告诉发送方"我已经收到了序列号 xxx 之前的所有数据"

例子:

发送方发送:序列号 1000,数据 100 字节(字节序号 1000-1099)
接收方收到后,发送 ACK:确认号 1100
含义:"我已收到 1000-1099,下一个要接收的是 1100"
1.3 确认应答的图示
发送方 接收方 ||| 发送:seq=1000, data=100 字节 ||----------------------------->|||
收到 1000-1099 ||| 收到 ACK | 发送:ack=1100|<-----------------------------||||
发送:seq=1100, data=100 字节 ||----------------------------->|||
收到 1100-1199 ||| 收到 ACK | 发送:ack=1200|<-----------------------------|||
1.4 确认应答的问题

问题:一发一收的方式性能太低。

分析:

发送数据 → 等待 ACK → 收到 ACK → 发送下一批数据
如果网络延迟高(比如 100ms),每次都要等待 100ms
吞吐量 = 数据量 / (数据传输时间 + 等待 ACK 时间)

例子:

数据量:1000 字节
数据传输时间:1ms
等待 ACK 时间:100ms
吞吐量 = 1000 / (1 + 100) = 9.9 字节/ms ≈ 10KB/s
太慢了!

解决方法:滑动窗口(后面详细讲)。


二、超时重传机制

2.1 为什么需要超时重传

问题:数据或 ACK 可能在网络中丢失。

场景 1:数据丢失

发送方发送数据 数据在网络中丢失了 接收方收不到数据,不会发送 ACK 发送方一直等待 ACK

场景 2:ACK 丢失

发送方发送数据 接收方收到数据,发送 ACK ACK 在网络中丢失了 发送方收不到 ACK,以为数据丢失了

解决方法:超时重传。

2.2 超时重传的原理

核心思想:如果一段时间内没收到 ACK,就重发数据。

流程:

1. 发送数据
2. 启动定时器
3. 等待 ACK
   * 如果在超时时间内收到 ACK:停止定时器,发送下一批数据
   * 如果超时:重发数据,重新启动定时器
2.3 超时重传的图示

场景 1:数据丢失,超时重传

发送方 接收方 ||| 发送:seq=1000||------------X 数据丢失 |||| 等待 ACK... || 超时! |||| 重传:seq=1000||----------------------------->|||
收到 1000 ||| 收到 ACK | 发送:ack=1100|<-----------------------------|||

场景 2:ACK 丢失,接收方去重

发送方 接收方 ||| 发送:seq=1000||----------------------------->|||
收到 1000 ||| 等待 ACK... | 发送:ack=1100| X<-----| ACK 丢失 | 超时! |||| 重传:seq=1000||----------------------------->|||
收到重复的 1000(去重) ||| 收到 ACK | 发送:ack=1100|<-----------------------------|||
2.4 超时时间如何确定

问题:超时时间设多长合适?

太长的问题:

超时时间=10 秒
数据丢失了,要等 10 秒才能重传
吞吐量严重下降

太短的问题:

超时时间=10ms
网络延迟=100ms
每次都会超时重传,导致大量重复数据
浪费带宽

理想的超时时间:

超时时间 = RTT(往返时间) + 一些余量
2.5 动态计算超时时间

RTT(Round-Trip Time):数据发送到接收并收到 ACK 的时间。

TCP 的超时时间计算:

RTO (Retransmission Timeout) = SRTT + 4 × RTTVAR
SRTT (Smoothed RTT):平滑的 RTT
RTTVAR (RTT Variance):RTT 的方差

动态调整:

每次收到 ACK 时,测量 RTT
更新 SRTT 和 RTTVAR
重新计算 RTO

Linux 的实现:

超时时间以 500ms 为单位
如果重传一次后仍未收到 ACK,等待 2×500ms 后再重传
如果再重传仍未收到 ACK,等待 4×500ms 后再重传
依次类推:500ms → 1000ms → 2000ms → 4000ms → ...
指数退避(Exponential Backoff)
2.6 重传次数限制

问题:一直重传下去吗?

答案:不是,有次数限制。

Linux 的限制:

累计重传次数达到一定限制后,认为网络或对端异常
强制关闭连接

查看重传次数限制:

cat /proc/sys/net/ipv4/tcp_retries2 # 通常是 15 次

三、滑动窗口机制

3.1 滑动窗口的核心思想

问题回顾:一发一收的性能太低。

解决方法:一次发送多个数据段,不用等待 ACK。

滑动窗口:

允许发送方在收到 ACK 之前,连续发送多个数据段
窗口大小:无需等待 ACK 就能发送的最大数据量

类比:

一发一收 = 单车道(一次只能过一辆车)
滑动窗口 = 多车道(可以同时过多辆车)
3.2 滑动窗口的工作原理

窗口大小:假设窗口大小为 4000 字节(4 个段,每个段 1000 字节)。

发送过程:

1. 一次性发送 4 个段:seq=1000, 2000, 3000, 4000
2. 收到第一个 ACK(ack=2000)
3. 窗口向右滑动,可以发送 seq=5000
4. 收到第二个 ACK(ack=3000)
5. 窗口继续滑动,可以发送 seq=6000...

图示:

发送缓冲区: ┌────┬────┬────┬────┬────┬────┬────┬────┐
│1000│2000│3000│4000│5000│6000│7000│8000│
└────┴────┴────┴────┴────┴────┴────┴────┘
└─────────────┘ 已发送未确认(窗口)
收到 ack=2000 后,窗口滑动:
┌────┬────┬────┬────┬────┬────┬────┬────┐
│1000│2000│3000│4000│5000│6000│7000│8000│
└────┴────┴────┴────┴────┴────┴────┴────┘
└─────────────┘ 已发送未确认(窗口)
3.3 滑动窗口的详细图示
发送方 接收方 ||| 发送:seq=1000||----------------------------->||
发送:seq=2000(不等 ACK) ||----------------------------->||
发送:seq=3000||----------------------------->||
发送:seq=4000||----------------------------->|||
收到 1000 || 发送:ack=2000| 收到 ack=2000|<----||
窗口滑动,发送 seq=5000||----------------------------->|||
收到 2000 || 发送:ack=3000| 收到 ack=3000|<----||
窗口滑动,发送 seq=6000||----------------------------->|||
3.4 滑动窗口的优势

性能提升:

无滑动窗口:
每次发送 1000 字节,等待 100ms
吞吐量 = 1000 / 100ms = 10KB/s
有滑动窗口(窗口大小 4000 字节):
一次发送 4000 字节,等待 100ms
吞吐量 = 4000 / 100ms = 40KB/s
性能提升 4 倍!

核心公式:

吞吐量 ≈ 窗口大小 / RTT

结论:

窗口越大,网络吞吐量越高
3.5 发送缓冲区的作用

问题:如果 ACK 丢失了,如何重传?

答案:使用发送缓冲区。

发送缓冲区的作用:

1. 保存已发送但未收到 ACK 的数据
2. 如果超时,可以从缓冲区重传
3. 收到 ACK 后,删除对应的数据

发送缓冲区的状态:

┌──────────────┬──────────────┬──────────────┐
│ 已确认 │ 已发送未确认 │ 未发送 │
│ (可删除) │ (等待 ACK) │ (待发送) │
└──────────────┴──────────────┴──────────────┘
└──────────────┘ 滑动窗口

四、丢包的处理:重传机制详解

4.1 情况 1:数据包到达,ACK 丢失

场景:

发送:seq=1000, 2000, 3000, 4000
接收方收到所有数据
ACK:ack=2000 丢失
ACK:ack=3000 到达
ACK:ack=4000 到达
ACK:ack=5000 到达

处理:

收到 ack=3000,说明 1000 和 2000 都已收到
收到 ack=5000,说明 1000-4000 都已收到
部分 ACK 丢失不要紧,后续 ACK 可以确认

图示:

发送方 接收方 |||seq=1000||----------------------------->| 收到 |seq=2000|ack=2000|----------------------------->|<----X 丢失 |seq=3000| 收到 |----------------------------->|ack=3000||<----|| 收到 ack=3000|| (说明 1000 和 2000 都已收到) |||
4.2 情况 2:数据包丢失

场景:

发送:seq=1000, 2000, 3000, 4000
seq=2000 丢失
接收方收到:1000, 3000, 4000

接收方的行为:

收到 1000:发送 ack=2000(期望下一个是 2000)
收到 3000:发送 ack=2000(还是期望 2000)
收到 4000:发送 ack=2000(仍然期望 2000)

发送方的行为:

连续收到 3 个 ack=2000
判断:seq=2000 肯定丢失了
立即重传 seq=2000(不等超时)

这就是"快速重传"(Fast Retransmit)。

4.3 快速重传的图示
发送方 接收方 |||seq=1000||----------------------------->| 收到 1000 ||ack=2000|seq=2000|<----||-------X 丢失 ||seq=3000||----------------------------->| 收到 3000(乱序) ||ack=2000(重复 ACK) |seq=4000|<----||----------------------------->| 收到 4000(乱序) ||ack=2000(重复 ACK) |seq=5000|<----||----------------------------->| 收到 5000(乱序) ||ack=2000(重复 ACK) | 收到 3 个 ack=2000|<----|| 快速重传 seq=2000||----------------------------->| 收到 2000ack=6000|<----|| (说明 2000-5000 都已收到) |||
4.4 快速重传的优势

对比超时重传:

超时重传:等待 RTO(可能是几秒)
快速重传:连续 3 个重复 ACK 立即重传(几十毫秒)

性能提升:

减少等待时间
提高吞吐量
4.5 接收缓冲区的作用

问题:接收方收到乱序的数据怎么办?

答案:使用接收缓冲区。

接收缓冲区的作用:

1. 保存已接收但乱序的数据
2. 等待缺失的数据到达
3. 按顺序交付给应用层

例子:

接收顺序:1000, 3000, 4000, 2000
缓冲区:先存 3000 和 4000,等待 2000
收到 2000 后,按顺序交付:1000, 2000, 3000, 4000

五、流量控制

5.1 流量控制的问题

问题:接收方的处理速度有限。

场景:

发送方:每秒发送 100MB 数据
接收方:每秒只能处理 10MB 数据
接收缓冲区:只有 1MB
结果:接收缓冲区很快被打满,后续数据被丢弃

后果:

大量丢包
大量重传
性能严重下降
5.2 流量控制的原理

核心思想:接收方告诉发送方"我的缓冲区还有多少空间"。

实现方式:

接收方在 ACK 中告诉发送方:窗口大小(剩余缓冲区大小)
发送方根据窗口大小调整发送速度

TCP 首部的窗口字段:

16 位窗口大小字段
最大值:65535 字节
5.3 流量控制的例子

场景:

接收缓冲区大小:10000 字节
已接收但尚未被应用层处理的数据:6000 字节
剩余可用缓冲区:4000 字节
接收方发送 ACK:
ACK 标志 =1
确认号 =...
窗口大小 =4000 (告诉发送方:我还能再接收 4000 字节)

发送方的行为:

收到窗口大小=4000
最多再发送 4000 字节
等待接收方处理数据,窗口变大
5.4 流量控制的图示
发送方 接收方 ||| 发送 4000 字节 ||----------------------------->| 接收缓冲区:4000/10000 || ack, window=6000|<-----------------------------||||
发送 6000 字节 ||----------------------------->| 接收缓冲区:10000/10000(满了) || ack, window=0|<-----------------------------||||
收到 window=0,停止发送 |||
应用层读取数据... || 接收缓冲区:5000/10000 || ack, window=5000|<-----------------------------||||
收到 window=5000,继续发送 |||
5.5 窗口探测

问题:如果接收方缓冲区满了(window=0),发送方停止发送。但接收方的窗口更新 ACK 丢失了怎么办?

场景:

接收方:window=0
发送方:停止发送
接收方处理了数据:window=5000,发送 ACK
ACK 丢失
发送方:不知道窗口变大了,一直等待
死锁!

解决方法:窗口探测(Window Probe)。

窗口探测:

发送方在收到 window=0 后,定期发送 1 字节的探测数据
接收方收到探测数据,回复当前的窗口大小
发送方获得最新的窗口大小
5.6 窗口扩大因子

问题:窗口大小字段只有 16 位

高带宽:1Gbps = 125MB/s
高延迟:RTT=100ms
理想窗口大小 = 125MB/s × 0.1s = 12.5MB
远超 65535 字节!

解决方法:窗口扩大因子(Window Scale)。

窗口扩大因子:

在 TCP 选项中协商一个扩大因子(0-14)
实际窗口大小 = 窗口字段值 << 扩大因子
例如:窗口字段=65535,扩大因子=7
实际窗口 = 65535<<7=8388480 字节 ≈ 8MB

六、拥塞控制

6.1 拥塞控制的问题

问题:网络拥堵时,盲目发很拥堵(路由器缓冲区快满了) 发送方继续高速发送数据 路由器缓冲区满了,丢弃数据包 发送方重传,继续发送大量数据 网络更加拥堵 恶性循环!

后果:

网络拥塞崩溃(Congestion Collapse)
所有连接的吞吐量都下降
6.2 拥塞控制的目标

目标:

1. 尽可能快地发送数据(提高吞吐量)
2. 避免造成网络拥堵(维持稳定性)

平衡:

发送太慢:浪费带宽
发送太快:造成拥堵
6.3 拥塞窗口(cwnd)

拥塞窗口(Congestion Window, cwnd):

发送方估算的网络能承受的数据量
根据网络状况动态调整

实际发送窗口:

实际窗口 = min(拥塞窗口,接收方窗口)
拥塞窗口:避免网络拥堵
接收方窗口:避免接收方过载
取两者的最小值
6.4 慢启动(Slow Start)

核心思想:从小窗口开始,逐渐增大。

初始值:

拥塞窗口 cwnd = 1 个 MSS(Maximum Segment Size,最大报文段大小)
通常 MSS = 1460 字节

增长规则:

每收到一个 ACK,cwnd += 1 个 MSS

例子:

初始:cwnd = 1
发送 1 个段,收到 1 个 ACK:cwnd = 2
发送 2 个段,收到 2 个 ACK:cwnd = 4
发送 4 个段,收到 4 个 ACK:cwnd = 8
发送 8 个段,收到 8 个 ACK:cwnd = 16...

增长速度:

指数增长:1 → 2 → 4 → 8 → 16 → 32 → 64 → ...
6.5 慢启动阈值(ssthresh)

问题:一直指数增长会导致网络拥堵。

解决方法:引入慢启动阈值(Slow Start Threshold, ssthresh)。

规则:

cwnd < ssthresh:慢启动,指数增长
cwnd >= ssthresh:拥塞避免,线性增长

初始值:

ssthresh = 接收方窗口大小(或一个很大的值)
6.6 拥塞避免(Congestion Avoidance)

核心思想:到达阈值后,缓慢增长。

增长规则:

每收到一个完整窗口的 ACK,cwnd += 1 个 MSS

例子:

cwnd = 8(8 个段)
发送 8 个段,收到 8 个 ACK:cwnd = 9
发送 9 个段,收到 9 个 ACK:cwnd = 10...

增长速度:

线性增长:每个 RTT 增加 1 个 MSS
6.7 拥塞发生后的处理
情况 1:超时重传(严重拥塞)

判断:

超时未收到 ACK
说明网络严重拥堵(可能大量丢包)

处理:

1. ssthresh = cwnd / 2(阈值减半)
2. cwnd = 1(重新慢启动)
3. 重传数据
情况 2:快速重传(轻微拥塞)

判断:

收到 3 个重复 ACK
说明网络轻微拥堵(部分丢包)

处理(快速恢复):

1. ssthresh = cwnd / 2(阈值减半)
2. cwnd = ssthresh(不是 1,而是阈值)
3. 重传数据
4. 进入拥塞避免阶段(线性增长)
6.8 拥塞控制的完整过程

由上面知识我们可以知道,图中所显示的网络拥塞一定是发生了超时重传,cwnd=1

在这里插入图片描述

6.9 拥塞控制的类比

类比:热恋的感觉

初期(慢启动):
感情快速升温,每天见面次数翻倍
1 次 → 2 次 → 4 次 → 8 次 → ...
稳定期(拥塞避免):
到达一定程度后,缓慢增加
每周增加 1 次见面
吵架(拥塞发生):
小吵架(快速重传):见面次数减半,但不回到初期
大吵架(超时重传):回到初期,重新开始

七、延迟应答与捎带应答

7.1 延迟应答(Delayed ACK)

问题:立即应答可能窗口太小。

场景:

接收缓冲区:1MB
收到 500KB 数据
立即回复 ACK:window=500KB
但实际上:
应用层很快处理了这 500KB 数据(10ms)
如果延迟 200ms 再回复 ACK:window=1MB

延迟应答的好处:

窗口更大 → 吞吐量更高

延迟应答的规则:

1. 数量限制:每收到 N 个包,就应答一次(通常 N=2)
2. 时间限制:超过最大延迟时间,立即应答(通常 200ms)

例子:

收到第 1 个包:不立即应答
收到第 2 个包:应答(确认 1 和 2)
收到第 3 个包:不立即应答
收到第 4 个包:应答(确认 3 和 4)
7.2 捎带应答(Piggybacking)

场景:很多应用层协议是"一发一收"的。

例子:

客户端发送:"How are you?"
服务器收到后:
1. 需要发送 ACK(确认收到)
2. 需要发送响应:"Fine, thank you!"

捎带应答:

把 ACK 和响应数据合并发送
一个 TCP 段同时包含:
- ACK 标志=1,确认号=...
- 数据="Fine, thank you!"

图示:

客户端 服务器 ||----------------------------->|||
收到 |||
收到捎带应答 | 发送:ACK + "Fine, thank you!"|<-----------------------------|||
省略了一个单独的 ACK 报文 

优势:

减少报文数量
提高效率

八、面向字节流与粘包问题

8.1 面向字节流的含义

TCP 是面向字节流的:

应用层看到的是连续的字节流
没有明确的消息边界

对比 UDP:

UDP 是面向数据报的
应用层看到的是一个个独立的数据报
有明确的消息边界
8.2 TCP 缓冲区机制

发送缓冲区:

应用层调用 send(),数据先放入发送缓冲区
TCP 根据情况决定何时发送:
- 缓冲区满了:立即发送
- 收到 PSH 标志:立即发送
- 达到一定时机:发送

接收缓冲区:

TCP 收到数据,放入接收缓冲区
应用层调用 recv(),从缓冲区读取数据

全双工:

一个 TCP 连接同时有发送缓冲区和接收缓冲区
既可以读,也可以写
8.3 读写不匹配

TCP 的读写可以不匹配:

例子 1:多次写,一次读

// 发送端
send("hello"); // 5 字节
send("world"); // 5 字节
send("!");     // 1 字节
// 接收端
recv(buf, 100); // 一次读取 11 字节:"helloworld!"

例子 2:一次写,多次读

// 发送端
send("hello world!", 12); // 12 字节
// 接收端
recv(buf, 5); // 读取 5 字节:"hello"
recv(buf, 7); // 读取 7 字节:" world!"

结论:

TCP 的读写次数和数量可以不匹配
站在应用层的角度,看到的是连续的字节流
8.4 粘包问题

粘包问题:

应用层无法区分两个数据包的边界

例子:

发送端发送两个数据包:
包 1:"hello"
包 2:"world"
接收端可能收到:
情况 1:"hello" 和 "world"(正常)
情况 2:"helloworld"(粘包)
情况 3:"hel" 和 "loworld"(拆包)
情况 4:"he""llo""wo""rld"(拆包)

为什么会粘包:

1. TCP 的发送缓冲区可能合并数据
2. TCP 的接收缓冲区可能合并数据
3. 网络层可能分片或合并数据
8.5 解决粘包问题

核心:明确两个包之间的边界。

方法 1:定长包
// 每个包固定 100 字节
struct Packet {
    char data[100];
};
// 发送
Packet pkt;
strcpy(pkt.data, "hello");
send(&pkt, sizeof(pkt)); // 固定 100 字节
// 接收
Packet pkt;
recv(&pkt, sizeof(pkt)); // 固定接收 100 字节

优点:简单 缺点:浪费空间(数据不够 100 字节也要占用 100 字节)

方法 2:包头长度字段
// 包头包含长度字段
struct PacketHeader {
    uint32_t length; // 数据长度
};
struct Packet {
    PacketHeader header;
    char data[]; // 可变长度
};
// 发送
std::string msg = "hello world";
PacketHeader header;
header.length = msg.size();
send(&header, sizeof(header));
send(msg.c_str(), msg.size());
// 接收
PacketHeader header;
recv(&header, sizeof(header)); // 先接收包头
char* buf = new char[header.length];
recv(buf, header.length); // 根据长度接收数据

优点:不浪费空间 缺点:需要两次 recv(先接收包头,再接收数据)

方法 3:分隔符
// 使用特殊分隔符(如\n 或\r\n)
// HTTP 协议使用\r\n
// 发送
send("hello\n");
send("world\n");
// 接收
while(true) {
    char c;
    std::string line;
    while(recv(&c, 1) > 0) {
        if(c == '\n') { break; // 遇到分隔符,一个包结束 }
        line += c;
    }
    // line 是一个完整的包
}

优点:简单直观 缺点:

  • 数据中不能包含分隔符(需要转义)
  • 效率较低(逐字节读取)
方法 4:应用层协议
// 例如 HTTP 协议
// 包头用\r\n\r\n分隔
// 包头中包含 Content-Length 字段
// 例子:GET / HTTP/1.1\r\n Host: www.example.com\r\n Content-Length:11\r\n \r\n hello world
// 接收流程:
// 1. 读取包头(直到\r\n\r\n)
// 2. 解析 Content-Length 字段
// 3. 根据 Content-Length 读取数据
8.6 UDP 有粘包问题吗

答案:UDP 没有粘包问题。

原因:

UDP 是面向数据报的
每个数据报都是独立的
有明确的边界

例子:

// 发送端
sendto("hello", 5); // 数据报 1
sendto("world", 5); // 数据报 2
// 接收端
recvfrom(buf, 100); // 要么收到完整的"hello",要么收不到
recvfrom(buf, 100); // 要么收到完整的"world",要么收不到
// 不会收到"helloworld"或"hel"

结论:

UDP:
- 要么收到完整的数据报
- 要么收不到(丢失)
- 不会出现"半个"或"粘在一起"的情况

九、TCP 异常情况处理

9.1 进程终止

场景:进程意外终止(如 Ctrl+C、crash)。

处理:

1. 进程终止
2. 操作系统自动关闭所有文件描述符(包括 socket)
3. 发送 FIN(正常关闭流程)

结论:

和正常关闭没有什么区别
四次挥手正常进行
9.2 机器重启

场景:机器重启(reboot)。

处理:

1. 操作系统关闭所有进程
2. 发送 FIN 或 RST(取决于操作系统)
3. 对端收到 FIN 或 RST,关闭连接

结论:

和进程终止类似
连接会被正常关闭
9.3 机器掉电或网线断开

场景:机器突然掉电,或网线被拔掉。

问题:无法发送 FIN。

对端的处理:

情况 1:对端有数据要发送

1. 对端发送数据
2. 等待 ACK,超时
3. 重传数据,再次超时
4. 多次重传失败后,判断连接已断开
5. 返回错误给应用层

情况 2:对端没有数据要发送

1. 对端一直等待
2. 连接看起来仍然存在
3. 但实际上对方已经不在了

解决方法:保活定时器(Keep-Alive)

9.4 保活定时器(Keep-Alive)

保活定时器:定期检测对方是否还在。

工作原理:

1. 如果一段时间内没有数据传输
2. 定期发送保活探测包(1 字节数据)
3. 如果收到 ACK,说明对方还在
4. 如果多次探测都没有响应,判断连接已断开

Linux 的保活参数:

# 保活时间(多久没数据后开始探测)
cat /proc/sys/net/ipv4/tcp_keepalive_time # 默认 7200 秒(2 小时)
# 保活间隔(探测包的间隔)
cat /proc/sys/net/ipv4/tcp_keepalive_intvl # 默认 75 秒
# 保活探测次数(多少次探测失败后判断断开)
cat /proc/sys/net/ipv4/tcp_keepalive_probes # 默认 9 次

总时间:

2 小时 + 75 秒 × 9 = 2 小时 11 分 15 秒
才能判断连接已断开

启用保活:

int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));

应用层的心跳:

TCP 的保活时间太长(2 小时)
很多应用层协议自己实现心跳机制
例如:HTTP 长连接、QQ、游戏
通常间隔:30 秒到 5 分钟

十、TCP 小结

10.1 TCP 为什么可靠

可靠性机制:

  1. 校验和:检测数据是否损坏
  2. 序列号:排序和去重
  3. 确认应答:确认收到数据
  4. 超时重传:数据丢失后重传
  5. 连接管理:三次握手和四次挥手
  6. 流量控制:避免接收方过载
  7. 拥塞控制:避免网络拥堵
10.2 TCP 如何提高性能

性能优化:

  1. 滑动窗口:一次发送多个段,不等 ACK
  2. 快速重传:连续 3 个重复 ACK 立即重传
  3. 延迟应答:等待缓冲区有更多空间再应答
  4. 捎带应答:把 ACK 和数据合并发送
  5. 拥塞控制:根据网络状况动态调整发送速度
10.3 TCP 的设计哲学

核心矛盾:

可靠性 vs 性能

TCP 的选择:

在保证可靠性的前提下,尽可能提高性能

具体体现:

确认应答:保证可靠性
滑动窗口:提高性能
超时重传:保证可靠性
快速重传:提高性能
流量控制:保证可靠性
拥塞控制:提高性能并维持网络稳定
10.4 容易混淆的点
  1. 序列号 vs 确认号:
    • 序列号:我发送的数据的第一个字节的序号
    • 确认号:下一个我要接收的序列号
  2. 流量控制 vs 拥塞控制:
    • 流量控制:避免接收方过载(接收方窗口)
    • 拥塞控制:避免网络拥堵(拥塞窗口)
  3. 慢启动 vs 拥塞避免:
    • 慢启动:指数增长(1→2→4→8→16…)
    • 拥塞避免:线性增长(每个 RTT 增加 1 个 MSS)
  4. 超时重传 vs 快速重传:
    • 超时重传:等待 RTO 超时后重传
    • 快速重传:收到 3 个重复 ACK 立即重传
  5. 粘包问题:
    • TCP 有粘包问题(面向字节流)
    • UDP 没有粘包问题(面向数据报)

目录

  1. TCP 可靠性与性能优化详解:从确认应答到拥塞控制
  2. 一、确认应答(ACK)机制
  3. 1.1 什么是确认应答
  4. 1.2 序列号和确认号的作用
  5. 1.3 确认应答的图示
  6. 1.4 确认应答的问题
  7. 二、超时重传机制
  8. 2.1 为什么需要超时重传
  9. 2.2 超时重传的原理
  10. 2.3 超时重传的图示
  11. 2.4 超时时间如何确定
  12. 2.5 动态计算超时时间
  13. 2.6 重传次数限制
  14. 三、滑动窗口机制
  15. 3.1 滑动窗口的核心思想
  16. 3.2 滑动窗口的工作原理
  17. 3.3 滑动窗口的详细图示
  18. 3.4 滑动窗口的优势
  19. 3.5 发送缓冲区的作用
  20. 四、丢包的处理:重传机制详解
  21. 4.1 情况 1:数据包到达,ACK 丢失
  22. 4.2 情况 2:数据包丢失
  23. 4.3 快速重传的图示
  24. 4.4 快速重传的优势
  25. 4.5 接收缓冲区的作用
  26. 五、流量控制
  27. 5.1 流量控制的问题
  28. 5.2 流量控制的原理
  29. 5.3 流量控制的例子
  30. 5.4 流量控制的图示
  31. 5.5 窗口探测
  32. 5.6 窗口扩大因子
  33. 六、拥塞控制
  34. 6.1 拥塞控制的问题
  35. 6.2 拥塞控制的目标
  36. 6.3 拥塞窗口(cwnd)
  37. 6.4 慢启动(Slow Start)
  38. 6.5 慢启动阈值(ssthresh)
  39. 6.6 拥塞避免(Congestion Avoidance)
  40. 6.7 拥塞发生后的处理
  41. 情况 1:超时重传(严重拥塞)
  42. 情况 2:快速重传(轻微拥塞)
  43. 6.8 拥塞控制的完整过程
  44. 6.9 拥塞控制的类比
  45. 七、延迟应答与捎带应答
  46. 7.1 延迟应答(Delayed ACK)
  47. 7.2 捎带应答(Piggybacking)
  48. 八、面向字节流与粘包问题
  49. 8.1 面向字节流的含义
  50. 8.2 TCP 缓冲区机制
  51. 8.3 读写不匹配
  52. 8.4 粘包问题
  53. 8.5 解决粘包问题
  54. 方法 1:定长包
  55. 方法 2:包头长度字段
  56. 方法 3:分隔符
  57. 方法 4:应用层协议
  58. 8.6 UDP 有粘包问题吗
  59. 九、TCP 异常情况处理
  60. 9.1 进程终止
  61. 9.2 机器重启
  62. 9.3 机器掉电或网线断开
  63. 9.4 保活定时器(Keep-Alive)
  64. 保活时间(多久没数据后开始探测)
  65. 保活间隔(探测包的间隔)
  66. 保活探测次数(多少次探测失败后判断断开)
  67. 十、TCP 小结
  68. 10.1 TCP 为什么可靠
  69. 10.2 TCP 如何提高性能
  70. 10.3 TCP 的设计哲学
  71. 10.4 容易混淆的点
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Linux 多线程初探:从进程到轻量级执行流
  • Flutter 底部导航与顶部选项卡实战:状态保持与鸿蒙适配
  • Java 外卖跑腿代驾小程序开发方案与技术栈解析
  • AI 大模型与传统算法的核心差异与类比解析
  • Agent Skills 设计详解:大模型开发中的技能封装与复用
  • 企业落地大模型的十大挑战与行动指南
  • pywebview:使用 Python 和 Web 技术构建轻量级桌面应用
  • 前端开发三年职业成长回顾:从传统软件到互联网实战
  • C++ vector 容器使用与模拟实现
  • 网络安全行业前景深度解析与入行路径建议
  • jmx_exporter 与 OpenTelemetry 集成:Java 应用可观测性最佳实践
  • C++进阶:unordered_set 和 unordered_map 详解及哈希表模拟实现
  • MCP 协议详解:与 Function Call 的区别及使用方式
  • Open-AutoGLM 平台访问指南与核心功能解析
  • 从零开始用 Python 复现 LLaMA 4 MoE 架构
  • MCP 协议详解:与 Function Call 的区别及使用方法
  • 大模型全解:定义、原理、应用与优劣势分析
  • 检索增强生成(RAG)技术原理与核心范式详解
  • 前后端分离架构 vs 传统架构:核心差异与选型指南
  • PyTorch 深度学习框架核心特性与入门

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online