当前位置: 首页 > news >正文

万字长文:从三次握手的“为什么”到内核 sk_buff 的流动,彻底说透 UDP、TCP 与 HTTP

前言:你理解的“TCP可靠、UDP不可靠”,可能只触及了冰山一角

“TCP 是可靠的,UDP 是不可靠的”——这句话几乎所有开发者都能脱口而出。但当你线上服务的接口突然超时飙高、下载速度莫名骤降、游戏画面频繁卡顿,你真的能说清楚问题出在握手阶段、滑动窗口、拥塞控制,还是零窗口探测吗?

大多数开发者的网络协议认知停留在这句“八股文”层面。遇到超时、丢包、拥塞时,只能盲目地改改超时时间、调调缓冲区大小,运气好解决了,运气不好就陷入“重启试试”的玄学循环。

真正的底层认知,决定了你排查问题的效率上限。这篇文章,我们从 UDP 的 8 字节首部一路拆到 TCP 的滑动窗口与拥塞控制,再到 HTTP 从 1.1 到 3.0 的演进逻辑,最后深入 Linux 内核看一眼数据包的全生命周期。读完你会理解:为什么 UDP 没有发送缓冲区?TCP 为什么是三次握手而不是两次?TIME_WAIT 为什么要等 2MSL?HTTP/3 为什么要“抛弃”TCP?


一、网络协议栈全景:四层模型中,HTTP、TCP、UDP 各司其职

在深入拆解之前,先建立全局视图。现代网络通信基于TCP/IP 四层模型

层级协议示例核心职责
应用层HTTP、DNS、RTP为用户提供具体服务,定义数据格式与语义
传输层TCP、UDP端到端通信,提供端口寻址与传输控制
网络层IP跨网络的数据包路由与寻址
数据链路层Ethernet物理介质上的帧传输

数据发送时,应用层数据(如 HTTP 报文)逐层向下封装:传输层加上 TCP/UDP 首部、网络层加上 IP 首部、数据链路层加上帧首部。接收时则反向逐层解封装。

HTTP 是应用层协议,它“坐”在 TCP 或 QUIC(基于 UDP)之上;TCP 和 UDP 是传输层协议,它们“坐”在 IP 之上。理解这个层次关系,是后续一切分析的基础。


二、UDP 协议深度拆解:8 字节的极简主义

2.1 核心特性

UDP(User Datagram Protocol)的设计目标是最小化开销。它的三个核心特性决定了它的“性格”:

  • 无连接:发送数据前不需要建立连接,直接发——像寄信,写完就投递,不管对方在不在。

  • 不可靠:不保证数据到达,不保证顺序,丢包了就丢了——没有重传,没有确认。

  • 面向报文:应用层交多少数据,UDP 就原封不动发多少,不会拆分也不会合并。

2.2 8 字节固定首部结构

UDP 的首部极其精简,固定8 字节,无选项字段:

text

0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ | 源端口号 | 目的端口号 | +--------+--------+--------+--------+ | UDP 长度 | UDP 校验和 | +--------+--------+--------+--------+
字段长度说明
源端口号16 bit发送方端口,0 表示不关心回复
目的端口号16 bit接收方端口
UDP 长度16 bit整个报文长度(首部 8 字节 + 数据),最小 8,最大 65535
UDP 校验和16 bit可选,IPv6 下必须计算

Linux 内核中用struct udphdr结构体描述这个首部。

2.3 内核收发路径与“无发送缓冲区”

UDP没有发送缓冲区,但有接收缓冲区。这意味着:

  • 发送方:应用层调用sendto()后,内核直接封装成 UDP 数据报交给 IP 层。如果网卡繁忙,数据可能直接在协议栈中丢弃——没有排队重试机制

  • 接收方:接收缓冲区用于暂存数据,等待应用层读取。但如果缓冲区满了,新来的数据会被直接丢弃——这再次体现了 UDP 的“不可靠”。

2.4 典型应用场景

UDP 的极简设计让它成为实时性优先场景的首选:

  • 实时音视频(RTP):宁可丢一帧画面,也不能等重传造成卡顿

  • DNS 查询:一次查询一个响应,简单高效

  • 在线游戏:位置同步等高频小包,延迟敏感

  • QUIC(HTTP/3 的底层):在 UDP 之上实现了可靠的流式传输


三、TCP 协议深度拆解(重头戏)

如果说 UDP 是“极简主义”,那 TCP 就是“精密仪器”。TCP 的设计目标是在不可靠的 IP 网络之上提供可靠的字节流传输

3.1 20 字节固定首部 + 可变选项

TCP 首部至少20 字节,最多 60 字节(带选项时):

text

0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 源端口号 | 目的端口号 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 序列号 (32 bit) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 确认号 (32 bit) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 首部长度 | 保留 |U|A|P|R|S|F| 窗口大小 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 校验和 | 紧急指针 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 选项 (可选,最多40字节) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

核心字段解读

  • 序列号(32 bit):当前报文第一个字节的序号,用于按序重组和丢包检测

  • 确认号(32 bit):期望收到的下一个字节序号,告诉发送方“这之前的我都收到了”

  • 首部长度(4 bit):单位是 32-bit 字,最小 5(即 20 字节),最大 15(60 字节)

  • 标志位(6 bit):URG、ACK、PSH、RST、SYN、FIN——连接管理的“指令集”

  • 窗口大小(16 bit):接收方通告的可用缓冲区大小,最大 65535,可通过窗口缩放选项扩展

为什么 TCP 需要 20 字节而 UDP 只要 8 字节?序列号、确认号、窗口、标志位——每一项都是 TCP 承诺“不丢数据、不乱顺序、不超对方承受能力”的代价。

3.2 三次握手:为什么不是两次?

TCP 建立连接需要三次握手

  1. 第一次(SYN):客户端 → 服务器,SYN=1,携带初始序列号client_isn

  2. 第二次(SYN+ACK):服务器 → 客户端,SYN=1,ACK=1,携带服务器的初始序列号server_isn,确认号 =client_isn + 1

  3. 第三次(ACK):客户端 → 服务器,ACK=1,确认号 =server_isn + 1

关键问题:为什么不是两次握手?

两次握手无法解决历史连接问题。假设客户端发送的 SYN 报文在网络中滞留,客户端超时重传后建立了新连接并完成了数据传输。此时旧 SYN 报文“迟到”到达服务器,服务器以为这是一个新连接请求,回复 SYN+ACK——如果只有两次握手,服务器会认为连接已建立并开始分配资源,但实际上客户端根本不认这个“幽灵连接”。第三次握手的核心作用就是让客户端有机会拒绝这种历史重复请求。

初始序列号(ISN)不是从 0 开始的,而是一个 32 位计数器,每 4 微秒加 1,约 4.55 小时循环一次——这是为了防止序列号重叠。

3.3 四次挥手与 TIME_WAIT

TCP 断开连接需要四次挥手

  1. 主动关闭方发送 FIN

  2. 被动关闭方回复 ACK

  3. 被动关闭方发送 FIN

  4. 主动关闭方回复 ACK

为什么挥手要四次而握手只要三次?因为 TCP 是全双工的——握手时 SYN 和 ACK 可以合并(SYN+ACK),但挥手时被动关闭方收到 FIN 后,可能还有数据要发送,所以 ACK 和 FIN 必须分开发。

TIME_WAIT 为什么是 2MSL?主动关闭方收到对方的 FIN 并回复 ACK 后,进入 TIME_WAIT 状态,等待2MSL(Maximum Segment Lifetime,最大报文生存时间)后才彻底关闭。两个原因:

  1. 保证最后的 ACK 能被对方收到:如果 ACK 丢失,对方会重传 FIN,TIME_WAIT 确保能收到并重发 ACK

  2. 让旧连接的所有报文在网络中消失:防止旧连接的“迟到报文”被新连接误认为是有效数据

3.4 可靠传输机制

TCP 的可靠性靠一套精密的机制保证。

滑动窗口

没有滑动窗口时,每条数据都要等 ACK 才能发下一条,效率极低。滑动窗口允许发送方在收到 ACK 之前连续发送窗口大小以内的数据:

  • 发送窗口= 已发送未确认的数据范围

  • 接收窗口= 接收方可用的缓冲区大小

  • 收到一个 ACK,窗口就“滑动”一次,继续发送下一条数据

窗口大小由接收方根据自身处理能力动态通告。

RTO 与超时重传

发送方为每个报文启动重传计时器,超时未收到 ACK 则重传。RTO(Retransmission Timeout)的计算至关重要:

  • 太小 → 不必要的重传,浪费带宽

  • 太大 → 增加延迟

RTO 基于加权平均 RTT计算,且每次重传后超时时间会翻倍(指数退避)。

快速重传与 SACK

如果只靠超时重传,丢包检测太慢。快速重传优化了这一点:发送方收到3 个重复的 ACK时,立即重传丢失的报文,不用等超时。

但快速重传有个问题:如果有多个报文丢失,它不知道具体丢的是哪些。SACK(Selective Acknowledgment,选择性确认)解决了这个问题——接收方在 ACK 中明确告知“我收到了哪些乱序数据块”,发送方只重传真正丢失的部分

3.5 流量控制:零窗口探测

流量控制的核心是让发送方不要发得太快,以免接收方来不及处理

接收方通过窗口大小字段通告自己的可用缓冲区。当接收方缓冲区满了,窗口大小变为0,发送方停止发送。

但如果这个“零窗口”通告丢失了呢?发送方等待窗口更新,接收方等待数据——死锁!TCP 用零窗口探测打破僵局:

  • 发送方启动持续计时器

  • 超时后发送零窗口探测报文

  • 如果窗口仍为 0,重置计时器继续等待

  • 如果窗口已恢复,继续发送数据

  • 探测次数一般为 3 次

3.6 拥塞控制:慢启动、拥塞避免、快重传与快恢复

流量控制关注的是端到端(接收方能不能收得过来),拥塞控制关注的是网络(网络能不能承载这么多数据)。

TCP 拥塞控制有四个经典算法:

① 慢启动(Slow Start)

刚建立连接时,发送方不知道网络带宽有多少,从1 个 MSS开始,每收到一个 ACK,拥塞窗口(cwnd)翻倍——指数增长,直到达到慢启动阈值(ssthresh)。

② 拥塞避免(Congestion Avoidance)

达到 ssthresh 后,从指数增长切换到线性增长:每经过一个 RTT,cwnd 加 1——慢下来,避免一下子撑爆网络。

③ 快重传(Fast Retransmit)

收到 3 个重复 ACK 时立即重传(前面已讲),同时将 ssthresh 减半,cwnd 设为 ssthresh + 3,进入快恢复。

④ 快恢复(Fast Recovery)

快重传之后不回到慢启动,而是直接进入拥塞避免——因为收到 3 个重复 ACK 说明网络仍然能传输数据,只是丢了几个包,不需要从头慢启动。

现代 Linux 内核支持多种拥塞控制算法,默认通常是CUBIC(基于丢包的算法),Google 的BBR(基于带宽和延迟的算法)也在广泛使用。


四、HTTP 协议演进:从文本到二进制,从 TCP 到 QUIC

HTTP 是应用层协议,它的每一次演进都对应着底层传输机制的优化需求。

4.1 HTTP/1.1:文本协议与队头阻塞

HTTP/1.1 采用纯文本格式传输,报文结构清晰:

text

GET /index.html HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 (空行) (响应体)

核心痛点

  • 队头阻塞(Head-of-Line Blocking):虽然引入了 Keep-Alive 长连接,但请求必须按顺序响应——前一个请求是大文件,后面的小请求就得排队等

  • 同一域名仅支持 6-8 个并发 TCP 连接,多资源页面需排队加载

  • 头部冗余:每次请求都携带完整头部,重复传输

4.2 HTTP/2:二进制分帧与多路复用

HTTP/2 在应用层做了彻底重构

  • 二进制分帧:将所有数据拆分为(Frame),替代纯文本

  • 多路复用:同一 TCP 连接上,多个请求/响应通过流 ID标识,可并行传输、乱序响应——解决了 HTTP 层的队头阻塞

  • HPACK 头部压缩:用静态表 + 动态表压缩重复头部,压缩率可达 50%-70%

但 HTTP/2 有个“隐形天花板”:它仍然跑在 TCP 上。一旦 TCP 层发生丢包,整个连接的所有流都会被阻塞——这是TCP 层的队头阻塞

4.3 HTTP/3:基于 UDP 的 QUIC 协议

HTTP/3 彻底抛弃了 TCP,采用基于UDPQUIC协议。

QUIC 的核心优势

优势说明
0-RTT 快速建连首次 1-RTT,后续 0-RTT(复用会话票据)
无队头阻塞多路复用每个流独立——单流丢包不影响其他流
连接迁移基于 Connection ID 而非四元组,Wi-Fi 切 4G 不断连
内置 TLS 1.3加密是协议的一部分,不是附加层

HTTP/3 的革新本质上是将可靠传输的控制权从操作系统内核(TCP)上移到用户态(QUIC),带来了更大的灵活性和更快的迭代速度。


五、横向对比与内核视图

5.1 TCP vs UDP 对比

维度TCPUDP
连接性面向连接(三次握手)无连接
可靠性可靠(确认 + 重传)不可靠,丢包不重传
传输方式面向字节流面向报文
首部大小20-60 字节8 字节(固定)
发送缓冲区
流量控制滑动窗口 + 零窗口探测
拥塞控制慢启动 + 拥塞避免等
系统资源高(维护连接状态)
典型场景Web、文件传输、邮件音视频、DNS、游戏

5.2 一次 HTTP 请求在内核中的数据流动

以一次 HTTP GET 请求为例,数据在 Linux 内核中的完整路径:

  1. 应用层:浏览器构造 HTTP 请求报文

  2. Socket 层:通过 Socket 写入内核的TCP 发送缓冲区

  3. 传输层(TCP):TCP 将数据分段(MSS 大小),加上 TCP 首部

  4. 网络层(IP):加上 IP 首部,查询路由表决定从哪个网卡发出

  5. 数据链路层:加上 Ethernet 首部,封装成帧

  6. 网卡驱动:将帧写入ring buffer,通过 DMA 交给网卡硬件发送

接收路径相反:

  1. 网卡:DMA 将帧写入内存,触发硬中断通知 CPU

  2. 软中断ksoftirqd内核线程处理,逐层解封装

  3. IP 层:重组 IP 分片

  4. TCP 层:按序重组、ACK 确认、放入接收缓冲区

  5. Socket 层:应用层通过read()读取数据

整个过程中,Socket 缓冲区是用户态与内核态的数据交换枢纽,sk_buff结构体是内核中描述每个报文的核心数据结构。


六、实战调优与总结

6.1 TCP 常见优化手段

tcp_tw_reuse:复用 TIME_WAIT 端口

TIME_WAIT 占着端口不释放,高并发下可能导致端口耗尽。net.ipv4.tcp_tw_reuse=1允许将 TIME_WAIT 状态的 socket 用于新连接。注意:仅在客户端(发起连接方)有效,且需要tcp_timestamps开启。tcp_tw_recycle已不推荐使用,在 NAT 环境下会引发问题。

TCP_NODELAY:禁用 Nagle 算法

Nagle 算法会延迟小包的发送以合并成更大的报文,但这对低延迟交互(如游戏、实时通信)是灾难。设置TCP_NODELAY=1禁用 Nagle,让数据立即发送。代价是网络中可能出现更多小包。

③ 调整缓冲区大小

高带宽长距离网络(高 BDP)中,默认的缓冲区可能不够大。调整net.core.rmem_maxnet.core.wmem_maxnet.ipv4.tcp_rmemnet.ipv4.tcp_wmem可以提升吞吐量。

④ 调整监听队列

net.core.somaxconn控制 listen 队列最大长度,高并发场景下默认 128 可能不够。

6.2 HTTP 优化思路

  • 启用HTTP/2多路复用减少连接数

  • 在弱网环境考虑HTTP/3(QUIC)降低延迟

  • 使用TLS 会话复用减少握手开销

  • 合理配置Keep-Alive超时,平衡连接复用与资源释放


写在最后

从 UDP 的 8 字节首部到 TCP 的滑动窗口与拥塞控制,再到 HTTP 从 1.1 到 3.0 的演进——每一层协议的设计都是对“如何在不可靠网络上可靠、高效地传输数据”这一问题的回答

理解这些底层机制,不是让你去改写内核,而是让你在面对超时、丢包、延迟抖动时,知道从哪里下手排查——是握手阶段的问题?是滑动窗口太小?是拥塞控制触发了?还是应用层的队头阻塞?

文章篇幅有限,更深入的内核源码剖析和真实案例调优已打包成专栏资料。


🔥 福利领取方式

如果这篇文章帮你打通了网络协议的“任督二脉”,欢迎点赞 + 在看 + 转发,让更多被网络问题困扰的开发者看到。

http://www.jsqmd.com/news/1233231/

相关文章:

  • ADI LT3045/3045-1国产替代方案——士模CM6121/2,超低噪声超高 PSRR LDO
  • Java面试进阶:从八股文到实战场景的深度准备策略
  • 推荐防止客户流失的管理工具 - 中媒介
  • WebP 与 GIF 播放关系分两层说清楚
  • .vimrc
  • OpenRouter API聚合平台:简化大模型接入与管理的终极方案
  • 智能手机市场格局演变:千元机为何逐渐消失?
  • C++ AST解析实战:使用cppast库简化代码分析与度量工具开发
  • 基于DeepLabV3+的市政管道缺陷智能检测技术解析
  • Codex Skill深度解析:8个必装技能让AI编程助手从问答机变执行者
  • 九江桶装水配送哪家及时? - 中媒介
  • C++实现频谱图绘制:从FFT原理到工程实践全解析
  • 74HC595芯片原理与Arduino应用实战
  • Python数据分析入门:NumPy、pandas与可视化实战
  • 聊城市茌平县2026最新黄金回收门店及联系方式指南 黄金回收白银回收铂金回收店铺TOP5排行榜 - 大熊猫898989
  • 工业5G专网下的边缘节点高吞吐与RedCap轻量化接入代码实践
  • Java开发者面试突围:构建三位一体能力体系,应对场景化面试与AI浪潮
  • AI工程化如何破解软件开发不可能三角
  • 原生三件套实现旅游网站:HTML+CSS+JavaScript实战指南
  • 计算机毕业设计之网上购书系统
  • STM32流水灯开发实战:从基础到进阶
  • AI认证体系全解析:从入门到精通的路径指南
  • 大语言模型性能的决定因素与优化策略
  • SCI 3/4区论文写作指南:YOLO与RT-DETR应用研究的创新突破
  • 无服务器数据库连接池“炸裂”:Spring Boot 按需计费下的连接风暴与优雅治理
  • 深入解析TMS320F2802x I2C寄存器:从时钟配置到FIFO优化实战
  • 梅州市五华县2026最新黄金回收门店及联系方式指南 黄金回收白银回收铂金回收店铺TOP5排行榜 - 盛世金银回收
  • 如何使用sider
  • MCP双Token认证体系:AI平台零信任模型服务的工程实践
  • Luma AI单眼视频生成:从原理到电商短视频实战指南