<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Networking on 纪伟的个人博客</title><link>https://www.jiwei.space/categories/networking/</link><description>Recent content in Networking on 纪伟的个人博客</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Wed, 10 Jun 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://www.jiwei.space/categories/networking/index.xml" rel="self" type="application/rss+xml"/><item><title>TCP/IP（十三）：抓包与网络诊断方法论</title><link>https://www.jiwei.space/posts/networking/tcp-ip/13-diagnostics/</link><pubDate>Wed, 10 Jun 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/13-diagnostics/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇把 NAT、DHCP 这些“地址怎么来、怎么转换”的机制讲完了，五层模型从下到上的协议也就齐了。但会背协议和“真的会排障”是两回事——线上一个“网站打不开”，到底是 DNS 挂了、网关不通、端口被占，还是应用自己炸了？&lt;/p&gt;
&lt;p&gt;本篇是系列收官，目标是把前十二篇的原理&lt;strong&gt;落到工具上&lt;/strong&gt;：先给一张网络诊断工具速查表，再重点拆 tcpdump 和 Wireshark，最后给一套&lt;strong&gt;按层排查的连通性方法论&lt;/strong&gt;。结尾会把整个 TCP/IP 系列串一遍，并指向本站相关的实战文章。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一工具速查表"&gt;&lt;a href="#%e4%b8%80%e5%b7%a5%e5%85%b7%e9%80%9f%e6%9f%a5%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;一、工具速查表
&lt;/h2&gt;&lt;p&gt;先一张总表，每层对应的常用工具：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;层&lt;/th&gt;
 &lt;th&gt;工具&lt;/th&gt;
 &lt;th&gt;解决什么问题&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;网络层&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;ping&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;连通性 + RTT（基于 ICMP Echo）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;网络层&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;traceroute / mtr&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;观察探测路径与逐跳响应，辅助定位异常区间（基于 TTL）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;传输层&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;ss / netstat&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;连接状态、监听端口、TIME_WAIT 堆积&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;应用层&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;dig / nslookup / host&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;DNS 解析是否正常&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;全层&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;tcpdump&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;抓包，看链路上真实在跑什么&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;全层&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Wireshark&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;读 pcap、按协议分层解析、交互式过滤&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;下面挑最常用的逐个说清楚。&lt;/p&gt;
&lt;h3 id="ping连通性与-rtt"&gt;&lt;a href="#ping%e8%bf%9e%e9%80%9a%e6%80%a7%e4%b8%8e-rtt" class="header-anchor"&gt;&lt;/a&gt;ping：连通性与 RTT
&lt;/h3&gt;&lt;p&gt;ping 发 ICMP Echo Request，对端回 ICMP Echo Reply，用来判断&lt;strong&gt;可达性&lt;/strong&gt;和&lt;strong&gt;往返延迟（RTT）&lt;/strong&gt; 。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ping -c &lt;span class="m"&gt;4&lt;/span&gt; 8.8.8.8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PING 8.8.8.8 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;64&lt;/span&gt; bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;115&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.3 ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;64&lt;/span&gt; bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;115&lt;/span&gt; &lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11.9 ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;4&lt;/span&gt; packets transmitted, &lt;span class="m"&gt;4&lt;/span&gt; received, 0% packet loss
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;读懂输出：&lt;code&gt;ttl&lt;/code&gt; 是应答到达你时剩余的跳数；只有先假设对端使用了某个常见初始 TTL，才能粗略估计回程跳数，而且回程路由可能与去程不同。&lt;code&gt;time&lt;/code&gt; 是 RTT，丢包率是重要指标。注意 ping 不通&lt;strong&gt;不等于网络断了&lt;/strong&gt;——很多服务器 / 防火墙会禁掉 ICMP，HTTP 服务照样能访问。&lt;/p&gt;
&lt;h3 id="traceroute--mtr逐跳路径"&gt;&lt;a href="#traceroute--mtr%e9%80%90%e8%b7%b3%e8%b7%af%e5%be%84" class="header-anchor"&gt;&lt;/a&gt;traceroute / mtr：逐跳路径
&lt;/h3&gt;&lt;p&gt;ping 只能观察目标是否回应特定 ICMP 探测；traceroute 则通过各跳返回的 ICMP 响应观察一条可能的探测路径，帮助缩小异常范围，但不能单凭某一跳不回应就断定业务包在那里丢失。原理是利用 IP 头里的 &lt;strong&gt;TTL&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先发一个 TTL=1 的包，第一跳路由器收到后 TTL 减到 0，丢弃并回 &lt;strong&gt;ICMP Time Exceeded&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;再发 TTL=2 的包，第二跳路由器回 Time Exceeded；&lt;/li&gt;
&lt;li&gt;如此递增，直到到达目的或超时。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ traceroute -n 8.8.8.8 &lt;span class="c1"&gt;# Linux/macOS，-n 不反解域名（更快）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;1&lt;/span&gt; 192.168.1.1 1.2 ms &lt;span class="c1"&gt;# 家用路由器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;2&lt;/span&gt; 10.0.0.1 5.3 ms &lt;span class="c1"&gt;# 运营商接入&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;3&lt;/span&gt; * * * &lt;span class="c1"&gt;# 这一跳禁了 ICMP，不代表故障&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;9&lt;/span&gt; 8.8.8.8 12.1 ms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;平台差异要注意：Linux 的 &lt;code&gt;traceroute&lt;/code&gt; 默认发 &lt;strong&gt;UDP&lt;/strong&gt; 包到高端口（33434 起），Windows 的 &lt;code&gt;tracert&lt;/code&gt; 默认发 &lt;strong&gt;ICMP Echo&lt;/strong&gt;。有的路由器只对 ICMP 放行、对 UDP 高端口丢包，所以同一目标两边结果可能不同。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mtr&lt;/strong&gt;（my traceroute）结合了持续探测与逐跳统计，实时显示各跳响应率和延迟，是常用的路径诊断工具。中间节点可能限制 ICMP 回包；若某一跳显示丢包而后续节点和最终目标正常，通常不能认定业务流量在该跳丢失：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ mtr -n 8.8.8.8 &lt;span class="c1"&gt;# 持续刷新，Ctrl+C 退出看汇总&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;一个常见误读：中间某跳显示丢包，但后面几跳正常——这往往是&lt;strong&gt;该路由器对 ICMP 限速&lt;/strong&gt;（control-plane policing），不是真丢包。看最终目的的丢包率才靠谱。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="ss--netstat连接状态与监听端口"&gt;&lt;a href="#ss--netstat%e8%bf%9e%e6%8e%a5%e7%8a%b6%e6%80%81%e4%b8%8e%e7%9b%91%e5%90%ac%e7%ab%af%e5%8f%a3" class="header-anchor"&gt;&lt;/a&gt;ss / netstat：连接状态与监听端口
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;netstat&lt;/code&gt; 是老牌工具，现代 Linux 发行版通常推荐使用 &lt;code&gt;ss&lt;/code&gt;（socket statistics）。两者有一部分相似的短参数，但语法、过滤能力和输出并不完全兼容。排查“端口被谁占了”“连接建立没建立”“TIME_WAIT 是不是爆了”都可以用到它们。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -tlnp &lt;span class="c1"&gt;# 监听中的 TCP 端口（-t tcp -l listen -n 不解析 -p 进程）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -tnp &lt;span class="c1"&gt;# 已建立的 TCP 连接&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -s &lt;span class="c1"&gt;# 连接状态汇总（ESTABLISHED / TIME-WAIT / ... 数量）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -tan state time-wait &lt;span class="c1"&gt;# 只看 TIME-WAIT 状态的连接&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;经典场景：服务起不来报 &lt;code&gt;Address already in use&lt;/code&gt;，用 &lt;code&gt;ss -tlnp | grep :8080&lt;/code&gt; 看端口被哪个进程占着；短连接打多了，&lt;code&gt;ss -s&lt;/code&gt; 里 TIME-WAIT 几万条，就是连接复用要做优化了（详见 TCP 那篇的状态机）。&lt;/p&gt;
&lt;h3 id="dig--nslookup--hostdns"&gt;&lt;a href="#dig--nslookup--hostdns" class="header-anchor"&gt;&lt;/a&gt;dig / nslookup / host：DNS
&lt;/h3&gt;&lt;p&gt;“能访问 IP、按域名访问失败”时，DNS 是优先检查项之一，但 Host / SNI、代理、虚拟主机和应用配置也可能造成相同表象。三个工具各有侧重：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig www.example.com &lt;span class="c1"&gt;# 最详细：显示完整的查询过程和 TTL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig +short www.example.com &lt;span class="c1"&gt;# 只要结果 IP，适合脚本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig @8.8.8.8 www.example.com &lt;span class="c1"&gt;# 指定用哪个 DNS 服务器查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig MX example.com &lt;span class="c1"&gt;# 查特定记录类型（MX/NS/TXT/A ...）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nslookup www.example.com &lt;span class="c1"&gt;# 老牌，交互式，输出不如 dig 清晰&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;host www.example.com &lt;span class="c1"&gt;# 最精简，只给一句话结论&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;排 DNS 时常用 &lt;code&gt;dig @指定DNS 域名&lt;/code&gt; 对比。不同解析器结果不一致，说明问题与解析路径有关，可能是本地缓存或配置，也可能是 split-horizon、区域调度、DNS 劫持或不同解析器命中了不同 CDN 节点，需要结合权威记录和业务预期继续判断。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二tcpdump抓包与过滤语法"&gt;&lt;a href="#%e4%ba%8ctcpdump%e6%8a%93%e5%8c%85%e4%b8%8e%e8%bf%87%e6%bb%a4%e8%af%ad%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;二、tcpdump：抓包与过滤语法
&lt;/h2&gt;&lt;p&gt;上面这些工具都是&amp;quot;问别人&amp;quot;，&lt;strong&gt;tcpdump 是直接看链路上跑的原始字节&lt;/strong&gt;。它是网络排障的终极武器，也是 Wireshark 抓包数据的命令行来源。&lt;/p&gt;
&lt;h3 id="基本用法"&gt;&lt;a href="#%e5%9f%ba%e6%9c%ac%e7%94%a8%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;基本用法
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 &lt;span class="c1"&gt;# 监听 eth0 网卡（-i 指定接口）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -n &lt;span class="c1"&gt;# -n 不反解 IP 为域名（快、输出干净）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -nn &lt;span class="c1"&gt;# -nn 连端口也不反解&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -c &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="c1"&gt;# 抓够 100 个包就停&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -w out.pcap &lt;span class="c1"&gt;# 写入 pcap 文件（给 Wireshark 看）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -w out.pcap -W &lt;span class="m"&gt;1&lt;/span&gt; -G &lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="c1"&gt;# 每 60 秒轮转一个文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;习惯上排障时用 &lt;code&gt;tcpdump -i any -nn&lt;/code&gt; 监听所有网卡、不解析名字，先看清流量再说。&lt;/p&gt;
&lt;h3 id="过滤表达式bpf-语法"&gt;&lt;a href="#%e8%bf%87%e6%bb%a4%e8%a1%a8%e8%be%be%e5%bc%8fbpf-%e8%af%ad%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;过滤表达式（BPF 语法）
&lt;/h3&gt;&lt;p&gt;tcpdump 用的是 &lt;strong&gt;BPF&lt;/strong&gt;（Berkeley Packet Filter）表达式，逻辑是&amp;quot;按条件筛选包&amp;quot;。这套语法也适用于 Wireshark 的抓包过滤器（capture filter，注意和显示过滤器 display filter 不是一回事）。核心原语：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;原语&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;th&gt;示例&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;host&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;源或目的是某 IP&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;host 8.8.8.8&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;src host&lt;/code&gt; / &lt;code&gt;dst host&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;只匹配源 / 目的&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;src host 192.168.1.10&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;net&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;匹配网段&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;net 192.168.1.0/24&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;port&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;源或目的是某端口&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;port 80&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;src port&lt;/code&gt; / &lt;code&gt;dst port&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;只匹配源 / 目的端口&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;dst port 443&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp&lt;/code&gt; / &lt;code&gt;udp&lt;/code&gt; / &lt;code&gt;icmp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;按协议&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tcp and port 80&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;and&lt;/code&gt; / &lt;code&gt;or&lt;/code&gt; / &lt;code&gt;not&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;逻辑组合&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;host A and port 80&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;实战组合：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 抓与某主机的所有 HTTP 流量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -nn host 93.184.216.34 and tcp port &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 抓某个客户端的所有 DNS 查询（UDP 53）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -nn src host 192.168.1.10 and udp port &lt;span class="m"&gt;53&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 只抓 TCP SYN 包（连接建立的第一个包）——发现谁在发起连接&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -nn &lt;span class="s1"&gt;&amp;#39;tcp[tcpflags] &amp;amp; tcp-syn != 0&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 抓 SYN 但不带 ACK 的包（纯握手起始，排除 SYN-ACK）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -nn &lt;span class="s1"&gt;&amp;#39;tcp[tcpflags] &amp;amp; (tcp-syn|tcp-ack) == tcp-syn&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 抓所有 RST（连接被重置）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -nn &lt;span class="s1"&gt;&amp;#39;tcp[tcpflags] &amp;amp; tcp-rst != 0&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;TCP 标志位的写法要特别说明：&lt;code&gt;tcp[tcpflags]&lt;/code&gt; 是 TCP 头里标志字段，后面跟位掩码 &lt;code&gt;&amp;amp;&lt;/code&gt;。可用的标志常量是 &lt;code&gt;tcp-fin&lt;/code&gt;、&lt;code&gt;tcp-syn&lt;/code&gt;、&lt;code&gt;tcp-rst&lt;/code&gt;、&lt;code&gt;tcp-push&lt;/code&gt;、&lt;code&gt;tcp-ack&lt;/code&gt;、&lt;code&gt;tcp-urg&lt;/code&gt;。想抓&amp;quot;某组合位都被置上&amp;quot;，用 &lt;code&gt;== (tcp-syn|tcp-ack)&lt;/code&gt;；想抓&amp;quot;只要含某位&amp;quot;，用 &lt;code&gt;&amp;amp; tcp-syn != 0&lt;/code&gt;。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;语法细节参考官方 &lt;a class="link" href="https://www.tcpdump.org/manpages/pcap-filter.7.html" target="_blank" rel="noopener"
 &gt;pcap-filter(7)&lt;/a&gt; man page。记住一点：&lt;strong&gt;tcpdump 的过滤表达式和 Wireshark 的显示过滤器是两套语法&lt;/strong&gt; 。tcpdump 过滤（BPF）在抓包时就生效，决定哪些包写入 pcap；Wireshark 显示过滤是抓完之后再筛，语法完全不同（如 &lt;code&gt;ip.addr == 8.8.8.8&lt;/code&gt;）。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="三wireshark分层解析利器"&gt;&lt;a href="#%e4%b8%89wireshark%e5%88%86%e5%b1%82%e8%a7%a3%e6%9e%90%e5%88%a9%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;三、Wireshark：分层解析利器
&lt;/h2&gt;&lt;p&gt;tcpdump 适合在服务器上快速看一眼，真正要&amp;quot;逐字段拆解协议&amp;quot;，还得靠 &lt;strong&gt;Wireshark&lt;/strong&gt;。典型工作流是：服务器上 &lt;code&gt;tcpdump -w&lt;/code&gt; 存成 pcap → 下载到本地用 Wireshark 打开。&lt;/p&gt;
&lt;p&gt;Wireshark 的核心能力是&lt;strong&gt;按协议分层展开&lt;/strong&gt;一个包：点开一个帧，能看到以太网头、IP 头、TCP 头、HTTP 报文逐层嵌套，每一层的每个字段（TTL、序号、标志位、窗口大小……）都标好了含义——正好对应第一篇讲的封装。&lt;/p&gt;
&lt;h3 id="显示过滤器display-filter"&gt;&lt;a href="#%e6%98%be%e7%a4%ba%e8%bf%87%e6%bb%a4%e5%99%a8display-filter" class="header-anchor"&gt;&lt;/a&gt;显示过滤器（Display Filter）
&lt;/h3&gt;&lt;p&gt;Wireshark 的显示过滤器是一套独立语法，比 BPF 强大得多：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;过滤表达式&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ip.addr == 8.8.8.8&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;源或目的是 8.8.8.8&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp.port == 443&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;TCP 端口 443&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp.flags.syn == 1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;SYN 标志位置 1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp.flags.reset == 1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;RST 包&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;dns&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;所有 DNS 报文&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;http.request.method == &amp;quot;GET&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;HTTP GET 请求&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tls.handshake.type == 1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;TLS ClientHello&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;还能按协议右键某个字段 → &amp;ldquo;作为过滤器应用&amp;rdquo;，组合条件非常方便。&lt;/p&gt;
&lt;h3 id="常用排障视角"&gt;&lt;a href="#%e5%b8%b8%e7%94%a8%e6%8e%92%e9%9a%9c%e8%a7%86%e8%a7%92" class="header-anchor"&gt;&lt;/a&gt;常用排障视角
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TCP 流跟踪&lt;/strong&gt;：右键一个 TCP 包 → Follow → TCP Stream，把一个连接的请求 / 响应整理成可读的对话，看 HTTP 在裸 TCP 上是怎么一来一回的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统计 → 会话（Conversations）&lt;/strong&gt;：列出所有连接的端点、包数、字节数，快速发现&amp;quot;谁在异常大量通信&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统计 → IO 图表&lt;/strong&gt;：按时间画吞吐量曲线，定位性能毛刺。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wireshark 配合前十二篇的协议知识，基本是&amp;quot;协议的活体解剖&amp;quot;——你能在里面亲眼看到三次握手、TLS 握手、HTTP 请求头、DNS 查询响应。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四分层连通性排障方法论"&gt;&lt;a href="#%e5%9b%9b%e5%88%86%e5%b1%82%e8%bf%9e%e9%80%9a%e6%80%a7%e6%8e%92%e9%9a%9c%e6%96%b9%e6%b3%95%e8%ae%ba" class="header-anchor"&gt;&lt;/a&gt;四、分层连通性排障方法论
&lt;/h2&gt;&lt;p&gt;工具会用了，关键是怎么&lt;strong&gt;有章法地用&lt;/strong&gt; 。网络问题的典型困境是：现象一样（&amp;ldquo;连不上&amp;rdquo;），原因五花八门。乱试一通效率极低，正确做法是&lt;strong&gt;沿协议栈自底向上逐层排除&lt;/strong&gt;，在哪一层断了就停在那查。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用层 DNS 解析对吗？HTTP/TLS 正常吗？应用本身有没有报错？
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▲ │ dig / curl -v / 应用日志
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;传输层 端口通吗？连接能建立吗？TIME_WAIT 堆积了吗？
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▲ │ ss / telnet host port / nc -zv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;网络层 网关能 ping 通吗？路由对吗？哪一跳丢包？
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▲ │ ping / ip route / traceroute / mtr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;链路层 ARP 学到吗？MAC 对得上吗？网卡有没有丢包错包？
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▲ │ arp -a / ip -s link
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;物理层 网线插好了吗？网卡灯亮吗？交换机端口正常吗？
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 眼看 / ethtool
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每一层的判断准则：&lt;strong&gt;这一层通了，才往上查；这一层不通，先修这一层&lt;/strong&gt; 。原因很简单——下面断了，上面必然全断；下面不通时去调应用，纯属浪费时间。&lt;/p&gt;
&lt;h3 id="每层的关键检查项"&gt;&lt;a href="#%e6%af%8f%e5%b1%82%e7%9a%84%e5%85%b3%e9%94%ae%e6%a3%80%e6%9f%a5%e9%a1%b9" class="header-anchor"&gt;&lt;/a&gt;每层的关键检查项
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;物理层&lt;/strong&gt;：看网卡灯、&lt;code&gt;ethtool eth0&lt;/code&gt; 看链路是否 up、有没有大量物理层错误。云服务器里这层基本是虚拟的，可跳过。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;链路层&lt;/strong&gt;：&lt;code&gt;ip neigh&lt;/code&gt;（旧 &lt;code&gt;arp -n&lt;/code&gt;）看 ARP 表，目标 IP 的 MAC 学到了吗？&lt;code&gt;ip -s link&lt;/code&gt; 看网卡有没有 RX/TX 丢包、错误帧。VLAN、二层隔离的问题也在这层。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网络层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ping 网关&lt;/code&gt; 通吗？不通说明本机到网关就有问题（链路层或本机配置）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ping 8.8.8.8&lt;/code&gt; 有回应吗？有回应只说明这条 ICMP 路径此刻可达，不代表所有出口协议和目标都正常；无回应时可换目标并结合 &lt;code&gt;traceroute&lt;/code&gt; / &lt;code&gt;mtr&lt;/code&gt; 观察异常从哪一段开始持续出现。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ip route&lt;/code&gt; 看默认路由在不在、对不对。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;传输层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ss -tlnp&lt;/code&gt; 确认服务真的在监听那个端口、那个地址（监听 &lt;code&gt;127.0.0.1:8080&lt;/code&gt; 是连不上外部访问的）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nc -zv host port&lt;/code&gt; 或 &lt;code&gt;telnet host port&lt;/code&gt; 测端口可达性。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ss -s&lt;/code&gt; 看连接状态，TIME_WAIT / CLOSE_WAIT 堆积是常见病。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;应用层&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dig&lt;/code&gt; 验证 DNS。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -v&lt;/code&gt; 看完整的 HTTP / TLS 交互，分清是 DNS、连接、TLS 还是应用层返回错误。&lt;/li&gt;
&lt;li&gt;看应用自身日志——很多&amp;quot;网络问题&amp;quot;最后是应用抛的异常。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="五实战网站打不开怎么按层排查"&gt;&lt;a href="#%e4%ba%94%e5%ae%9e%e6%88%98%e7%bd%91%e7%ab%99%e6%89%93%e4%b8%8d%e5%bc%80%e6%80%8e%e4%b9%88%e6%8c%89%e5%b1%82%e6%8e%92%e6%9f%a5" class="header-anchor"&gt;&lt;/a&gt;五、实战：网站打不开怎么按层排查
&lt;/h2&gt;&lt;p&gt;把方法论套到一个真实场景。假设用户反馈 &lt;code&gt;https://api.example.com&lt;/code&gt; 打不开，按层走一遍：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 物理层 / 链路层&lt;/strong&gt;（先确认本机网络没断）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ip -s link show eth0 &lt;span class="c1"&gt;# 网卡 up，无错包&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ping 192.168.1.1 &lt;span class="c1"&gt;# ping 网关&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;64&lt;/span&gt; bytes from 192.168.1.1: &lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 ms &lt;span class="c1"&gt;# 通&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;本机到网关正常，底层没问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 网络层&lt;/strong&gt;（确认出网正常）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ping 8.8.8.8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;64&lt;/span&gt; bytes from 8.8.8.8: &lt;span class="nv"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt; ms &lt;span class="c1"&gt;# 出网正常&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ping 1.1.1.1 &lt;span class="c1"&gt;# 换个目标再确认&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;能 ping 通某个公网 IP，说明到该目标的 ICMP 往返路径可达，但不能据此断言整个网络层或所有出口策略都正常。如果这步异常，可更换探测目标，并用 &lt;code&gt;mtr -n 8.8.8.8&lt;/code&gt; 观察异常是否从某一跳开始延续到最终目标。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 应用层 - DNS&lt;/strong&gt;（域名能解析吗）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig api.example.com +short
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;空&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 解析不出来！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dig @8.8.8.8 api.example.com +short
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1.2.3.4 &lt;span class="c1"&gt;# 换 DNS 能解析&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;定位到 DNS 路径差异后，再检查本机、递归解析器和权威记录。使用 systemd-resolved 的新系统可执行 &lt;code&gt;resolvectl flush-caches&lt;/code&gt;；DNS 配置通常由 NetworkManager、systemd-resolved 或 DHCP 管理，不要在未确认管理方式前直接修改可能被自动覆盖的 &lt;code&gt;/etc/resolv.conf&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 传输层&lt;/strong&gt;（端口可达吗，假设 DNS 修好了拿到 IP）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nc -zv 1.2.3.4 &lt;span class="m"&gt;443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Connection to 1.2.3.4 &lt;span class="m"&gt;443&lt;/span&gt; port &lt;span class="o"&gt;[&lt;/span&gt;tcp/https&lt;span class="o"&gt;]&lt;/span&gt; succeeded! &lt;span class="c1"&gt;# 端口通&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;端口通说明 TCP 层没问题。如果这里超时，多半是对端没监听、被防火墙拦或本机出口被限。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 应用层 - HTTP/TLS&lt;/strong&gt;（服务正常响应吗）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -v https://api.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;* Trying 1.2.3.4:443...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;* Connected
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;* TLS handshake &lt;span class="c1"&gt;# 卡在这里？&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; GET / HTTP/2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt; HTTP/2 &lt;span class="m"&gt;500&lt;/span&gt; &lt;span class="c1"&gt;# 服务端报错&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;curl -v&lt;/code&gt; 会展示连接、TLS 握手和 HTTP 请求响应等阶段。失败阶段能帮助确定优先排查方向，但跨层依赖和中间设备会让同一根因表现为不同阶段失败，不能把它当成严格的一一映射。这里若是 TLS 失败，应继续检查证书、时间、SNI 和协议协商；若收到 HTTP 500，说明请求已经到达某个 HTTP 服务，重点转向应用及其后端依赖。&lt;/p&gt;
&lt;p&gt;整个过程对应的方法论就是：&lt;strong&gt;从下到上逐层排除，用工具验证每一层的假设，定位到具体层后再深入&lt;/strong&gt; 。比起一上来就重启服务、瞎改配置，这套方法省时得多。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;网络诊断要&lt;strong&gt;按层排查&lt;/strong&gt;：物理 → 链路（ARP/MAC）→ 网络（ping/路由/traceroute）→ 传输（端口/TIME_WAIT）→ 应用（DNS/HTTP/TLS），一层通了才往上查。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ping&lt;/strong&gt; 观察 ICMP 可达性与 RTT，&lt;strong&gt;traceroute / mtr&lt;/strong&gt; 观察探测路径和异常区间，&lt;strong&gt;ss / netstat&lt;/strong&gt; 看连接与端口，&lt;strong&gt;dig&lt;/strong&gt; 查 DNS。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tcpdump&lt;/strong&gt; 是抓包终极武器，过滤用 BPF 语法（&lt;code&gt;host&lt;/code&gt;/&lt;code&gt;port&lt;/code&gt;/&lt;code&gt;tcp[tcpflags] &amp;amp; tcp-syn&lt;/code&gt;），存成 pcap 交给 Wireshark。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wireshark&lt;/strong&gt; 按协议分层展开，配合系列前十二篇的知识能看懂每个字段；显示过滤器是独立语法，别和 BPF 混。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="系列总结"&gt;&lt;a href="#%e7%b3%bb%e5%88%97%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;系列总结
&lt;/h2&gt;&lt;p&gt;十三篇，从&amp;quot;为什么要分层&amp;quot;到&amp;quot;怎么抓包排障&amp;quot;，TCP/IP 的主干终于串完了。回顾整个系列，可以浓缩成一条主线：&lt;strong&gt;数据在发送端被逐层封装，在接收端被逐层解封装，每一层只解决自己的问题&lt;/strong&gt; 。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;篇&lt;/th&gt;
 &lt;th&gt;主题&lt;/th&gt;
 &lt;th&gt;一句话要点&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;分层模型&lt;/td&gt;
 &lt;td&gt;OSI 七层是参考、TCP/IP 四层贴实际、五层是教学折中&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2–3&lt;/td&gt;
 &lt;td&gt;链路层&lt;/td&gt;
 &lt;td&gt;以太网帧 + MAC 地址，ARP 把 IP 翻译成 MAC，交换机按 MAC 转发&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4–6&lt;/td&gt;
 &lt;td&gt;网络层&lt;/td&gt;
 &lt;td&gt;IP 编址与子网划分，路由怎么选路，ICMP 反馈通断&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7–9&lt;/td&gt;
 &lt;td&gt;传输层&lt;/td&gt;
 &lt;td&gt;TCP 三次握手 / 四次挥手 / 状态机，可靠性与流控拥塞控制，UDP 轻量无连接&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;10–11&lt;/td&gt;
 &lt;td&gt;应用层&lt;/td&gt;
 &lt;td&gt;DNS 分层解析与缓存，HTTP 语义、连接复用与 TLS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;地址机制&lt;/td&gt;
 &lt;td&gt;NAT/NAPT 改写地址省公网 IP，DHCP 自动分配 IP，CGNAT 让家网失去公网 IP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;13&lt;/td&gt;
 &lt;td&gt;诊断方法论&lt;/td&gt;
 &lt;td&gt;工具速查 + 按层排障&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;把这些拼起来，一个 HTTP 请求的全貌就是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用层：构造 HTTP 报文
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ DNS 解析域名 → 拿到 IP（应用层，但贯穿）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ HttpClient / epoll 在这层调度 I/O
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;传输层：套 TCP 头，三次握手建连接，按序可靠传输
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;网络层：套 IP 头，路由逐跳转发
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;链路层：套帧头尾，ARP 找到下一跳 MAC，交换机转发
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;物理层：比特流出网线
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 出口 NAT 改写源 IP:端口 → 公网
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 对端反向解封装，层层上交到应用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;对端应用收到报文，返回响应走反向全程
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每一步都对应系列里某一篇的原理。理解了这条链，再回头看平时写的后端代码，很多&amp;quot;玄学问题&amp;quot;就有了着落。&lt;/p&gt;
&lt;p&gt;网络是后端、云原生、分布式共同的地基，本站还有几篇从应用侧把网络用起来的实战文章，正好衔接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.jiwei.space/posts/dotnet/httpclient-deep-dive/" &gt;HttpClient 的前世今生：从 Socket 耗尽到 .NET 8 韧性管线&lt;/a&gt; ——传输层连接池、Socket 耗尽、DNS 刷新在工程里怎么踩坑怎么治。&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.jiwei.space/posts/linux/epoll-deep-dive/" &gt;epoll 的前世今生：从 select/poll 到 io_uring&lt;/a&gt; ——高并发下传输层的 I/O 复用是怎么演进的。&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.jiwei.space/posts/architecture/request-lifecycle/" &gt;跟着一个 API 请求走完全程：后端全链路解剖&lt;/a&gt; ——把本系列的网络层和业务代码、中间件串起来看。&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.jiwei.space/posts/dotnet/performance/high-concurrency/" &gt;.NET 高并发编程全景：从异步到高性能实战&lt;/a&gt; ——传输层之上，应用怎么榨干单机的并发能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TCP/IP 系列到此完结。原理是死的，排障是活的——希望你下次遇到网络问题时，能从容地掏出这套分层方法论，而不是只会重启服务。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.tcpdump.org/manpages/tcpdump.1.html" target="_blank" rel="noopener"
 &gt;tcpdump man page&lt;/a&gt; / &lt;a class="link" href="https://www.tcpdump.org/manpages/pcap-filter.7.html" target="_blank" rel="noopener"
 &gt;pcap-filter(7)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.wireshark.org/docs/wsug_html_chunked/" target="_blank" rel="noopener"
 &gt;Wireshark User&amp;rsquo;s Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/traviscross/mtr" target="_blank" rel="noopener"
 &gt;mtr — GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://digdns.org/" target="_blank" rel="noopener"
 &gt;Using dig for DNS troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（十二）：NAT、DHCP 与地址机制</title><link>https://www.jiwei.space/posts/networking/tcp-ip/12-nat-dhcp/</link><pubDate>Mon, 08 Jun 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/12-nat-dhcp/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;前十一篇把五层模型从链路一路讲到了应用层：以太网与 ARP、IP 编址与路由、TCP/UDP、HTTP/DNS。但有一个非常现实的问题一直没正面回答——&lt;/p&gt;
&lt;p&gt;你的电脑 IP 多半是 &lt;code&gt;192.168.1.x&lt;/code&gt;，这明明是个&amp;quot;私网地址&amp;quot;，&lt;strong&gt;为什么能访问公网？&lt;/strong&gt; 公司里几千台机器共用一个出口 IP，&lt;strong&gt;外网的响应是怎么精确回到你这台的？&lt;/strong&gt; 还有，这个 IP 是谁分配给你的，为什么重启路由器可能就变了？&lt;/p&gt;
&lt;p&gt;这些问题的答案就是 NAT 和 DHCP——两个支撑整个现代互联网&amp;quot;最后一公里&amp;quot;的地址机制。本篇把它们一次讲透。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一为什么需要-nat"&gt;&lt;a href="#%e4%b8%80%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81-nat" class="header-anchor"&gt;&lt;/a&gt;一、为什么需要 NAT
&lt;/h2&gt;&lt;p&gt;NAT（Network Address Translation，网络地址转换）诞生的直接原因就一个：&lt;strong&gt;IPv4 地址不够用了&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;IPv4 总共约 43 亿个地址，扣掉组播、保留、特殊用途，能分给终端的更少。2011 年 IANA 的全球地址池就发完了，APNIC（亚太）等区域注册局也先后告急。与此同时，联网设备却爆炸式增长——每台手机、每台电视、每个智能插座都要 IP。怎么办？&lt;/p&gt;
&lt;p&gt;NAT 的思路很朴素：&lt;strong&gt;不需要每台设备都有公网 IP，让一群设备共用一个（或少数几个）公网 IP 出网&lt;/strong&gt; 。具体做法是在内网和公网的边界（家用路由器、企业防火墙）上做地址改写：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内网用&lt;strong&gt;私网地址&lt;/strong&gt;，设备之间互相访问；&lt;/li&gt;
&lt;li&gt;出网时，NAT 把源地址从私网改成公网，返回流量再改回来。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就引出两个配套概念：私网地址段（内网用什么 IP），以及转换表（怎么把流量对回正确的内网主机）。&lt;/p&gt;
&lt;p&gt;NAT 还顺带带来了一个工程上的副作用：&lt;strong&gt;隐藏内网结构&lt;/strong&gt; 。公网看到的全是出口 IP，内网拓扑对外不可见，某种程度上也算一层隔离（但别把它当安全机制用）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二私网地址段与公网rfc-1918"&gt;&lt;a href="#%e4%ba%8c%e7%a7%81%e7%bd%91%e5%9c%b0%e5%9d%80%e6%ae%b5%e4%b8%8e%e5%85%ac%e7%bd%91rfc-1918" class="header-anchor"&gt;&lt;/a&gt;二、私网地址段与公网：RFC 1918
&lt;/h2&gt;&lt;p&gt;既然内网不走公网路由，就得有一段&amp;quot;大家都能随便用、但不会被公网路由&amp;quot;的地址。这就是 &lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc1918" target="_blank" rel="noopener"
 &gt;RFC 1918&lt;/a&gt; 定义的三个私网地址段：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;私网地址段&lt;/th&gt;
 &lt;th&gt;CIDR&lt;/th&gt;
 &lt;th&gt;范围&lt;/th&gt;
 &lt;th&gt;典型场景&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;10.0.0.0/8&lt;/td&gt;
 &lt;td&gt;A 类&lt;/td&gt;
 &lt;td&gt;10.0.0.0 – 10.255.255.255&lt;/td&gt;
 &lt;td&gt;企业内网、数据中心（地址多）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;172.16.0.0/12&lt;/td&gt;
 &lt;td&gt;B 类 ×16&lt;/td&gt;
 &lt;td&gt;172.16.0.0 – 172.31.255.255&lt;/td&gt;
 &lt;td&gt;中等规模内网、容器网络&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;192.168.0.0/16&lt;/td&gt;
 &lt;td&gt;C 类 ×256&lt;/td&gt;
 &lt;td&gt;192.168.0.0 – 192.168.255.255&lt;/td&gt;
 &lt;td&gt;家庭网络、小型办公&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这三个段&lt;strong&gt;全球任何组织都可以重复使用&lt;/strong&gt;，因为它们不会出现在公网路由表里——运营商会在边界丢弃目的为私网地址的流量。你家是 &lt;code&gt;192.168.1.x&lt;/code&gt;，邻居家也是，互不冲突，因为各自都在自己的 NAT 后面。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;规范细节：RFC 1918 地址不应在公共互联网中传播路由。访问 &lt;code&gt;192.168.1.1&lt;/code&gt; 时，主机会根据本地路由把流量交给本地链路或私网下一跳，而不会靠公网路由去寻找其他组织中重复使用的同名地址；VPN、重叠私网或更具体的本地路由仍可能改变实际去向。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;与之相对的是&lt;strong&gt;公网地址&lt;/strong&gt;（也叫可路由地址、全球单播地址），由 IANA → RIR → NIR → ISP 层层分配，全球唯一，能在公网被路由。&lt;/p&gt;
&lt;p&gt;除了 RFC 1918，还有几段容易混淆的特殊地址，顺带记住：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;地址段&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;127.0.0.0/8&lt;/td&gt;
 &lt;td&gt;环回（loopback），本机自测，最常用 &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;169.254.0.0/16&lt;/td&gt;
 &lt;td&gt;链路本地（link-local），DHCP 拿不到 IP 时操作系统自动配的&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0.0.0.0&lt;/td&gt;
 &lt;td&gt;&amp;ldquo;本机所有地址&amp;rdquo;，常出现在监听（&lt;code&gt;0.0.0.0:80&lt;/code&gt;）和默认路由&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;知道这些段，后面排查&amp;quot;为什么我的 IP 是 &lt;code&gt;169.254.x.x&lt;/code&gt;&amp;quot;（答：DHCP 失败了）就不会懵。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三nat-工作原理napt"&gt;&lt;a href="#%e4%b8%89nat-%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86napt" class="header-anchor"&gt;&lt;/a&gt;三、NAT 工作原理：NAPT
&lt;/h2&gt;&lt;p&gt;最简单的 NAT 是&amp;quot;一对一&amp;quot;——一个公网 IP 对一个内网 IP，但这等于没省。实际家网、企业网用的几乎都是 &lt;strong&gt;NAPT&lt;/strong&gt;（Network Address Port Translation，也叫 PAT），它&lt;strong&gt;同时改 IP 和端口&lt;/strong&gt;，让多个内网主机共用一个公网 IP。&lt;/p&gt;
&lt;h3 id="一次出网的全过程"&gt;&lt;a href="#%e4%b8%80%e6%ac%a1%e5%87%ba%e7%bd%91%e7%9a%84%e5%85%a8%e8%bf%87%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;一次出网的全过程
&lt;/h3&gt;&lt;p&gt;假设你家出口公网 IP 是 &lt;code&gt;203.0.113.5&lt;/code&gt;，内网两台机器同时访问 &lt;code&gt;8.8.8.8:53&lt;/code&gt;（DNS）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;内网主机 A 192.168.1.10:5000 ──┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ [NAT 网关 203.0.113.5]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;内网主机 B 192.168.1.20:5000 ──┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 出网时，NAT 改写源地址（SNAT）：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ A → 203.0.113.5:30001
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ B → 203.0.113.5:30002
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 公网 8.8.8.8:53
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;NAT 在内部维护一张&lt;strong&gt;转换表（NAT session table）&lt;/strong&gt;，把&amp;quot;内网 IP:端口&amp;quot;映射到&amp;quot;公网 IP:端口&amp;quot;：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方向&lt;/th&gt;
 &lt;th&gt;源&lt;/th&gt;
 &lt;th&gt;目的&lt;/th&gt;
 &lt;th&gt;协议&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;内→外（改写前）&lt;/td&gt;
 &lt;td&gt;192.168.1.10:5000&lt;/td&gt;
 &lt;td&gt;8.8.8.8:53&lt;/td&gt;
 &lt;td&gt;UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;外→内（公网看到的）&lt;/td&gt;
 &lt;td&gt;203.0.113.5:30001&lt;/td&gt;
 &lt;td&gt;8.8.8.8:53&lt;/td&gt;
 &lt;td&gt;UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;内→外（改写前）&lt;/td&gt;
 &lt;td&gt;192.168.1.20:5000&lt;/td&gt;
 &lt;td&gt;8.8.8.8:53&lt;/td&gt;
 &lt;td&gt;UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;外→内（公网看到的）&lt;/td&gt;
 &lt;td&gt;203.0.113.5:30002&lt;/td&gt;
 &lt;td&gt;8.8.8.8:53&lt;/td&gt;
 &lt;td&gt;UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;即便两台内网机器用了&lt;strong&gt;相同的源端口&lt;/strong&gt;（都是 5000），NAT 通过分配&lt;strong&gt;不同的外部端口&lt;/strong&gt;（30001 / 30002）也能区分开来——这就是 NAPT 省地址的精髓。&lt;/li&gt;
&lt;li&gt;返回流量到达 &lt;code&gt;203.0.113.5:30001&lt;/code&gt; 时，NAT 查表反查出 &lt;code&gt;192.168.1.10:5000&lt;/code&gt;，改写目的地址（DNAT）后送回内网。&lt;/li&gt;
&lt;li&gt;这张表是&lt;strong&gt;有状态的&lt;/strong&gt;，条目有空闲超时（TCP 几小时、UDP 几十秒到几分钟不等），超时未用就清除。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;这种出网改源地址的叫 &lt;strong&gt;SNAT&lt;/strong&gt;（Source NAT）。后面端口映射那种&amp;quot;进网改目的地址&amp;quot;的叫 &lt;strong&gt;DNAT&lt;/strong&gt;（Destination NAT）。家用路由器做的 NAPT 本质是 SNAT。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;NAT 让公网 IP 消耗骤降——一个家庭、一栋楼甚至一个小区可以共用少数公网 IP。但它的代价也很明显：&lt;strong&gt;打破了 IP 端到端的语义&lt;/strong&gt; 。公网主机没法主动发起连接到内网主机（因为 NAT 表里没有对应条目，包到不了），这直接导致了后面的 P2P 问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四端口映射--端口转发"&gt;&lt;a href="#%e5%9b%9b%e7%ab%af%e5%8f%a3%e6%98%a0%e5%b0%84--%e7%ab%af%e5%8f%a3%e8%bd%ac%e5%8f%91" class="header-anchor"&gt;&lt;/a&gt;四、端口映射 / 端口转发
&lt;/h2&gt;&lt;p&gt;NAPT 解决了&amp;quot;内网出去&amp;quot;，但反过来&amp;quot;公网进来&amp;quot;呢？默认情况下，公网主动发起到你出口 IP 的连接，NAT 不知道该转给谁，只能丢弃。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;端口映射（port forwarding，也叫端口转发）&lt;/strong&gt; 就是为这个场景准备的：在 NAT 上配一条&lt;strong&gt;静态规则&lt;/strong&gt;，把&amp;quot;公网某个端口&amp;quot;固定转发到&amp;quot;内网某台主机的某端口&amp;quot;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;公网用户 家用路由器 内网服务器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 203.0.113.5 192.168.1.10
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ :8080
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 访问 http://203.0.113.5:8080 ▲
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ──────────────► [DNAT 规则] │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 公网 :8080 → 192.168.1.10:8080
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ─────────────────────────►│
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;常见场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自托管&lt;/strong&gt;：在家用宽带搭个博客、游戏服务器，把公网 80/443/25565 映射到内网那台机器；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;远程访问&lt;/strong&gt;：把 22（SSH）或 3389（RDP）映射到内网某台主机，出门在外也能连回家；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容器 / 虚拟机&lt;/strong&gt;：Docker 的 &lt;code&gt;-p 8080:80&lt;/code&gt;、VirtualBox 的端口转发，本质都是同一个机制，只是在宿主机上做了一层 DNAT。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配置方式各家不同，但概念一致：&lt;strong&gt;一条固定的 &lt;code&gt;公网端口 → 内网 IP:端口&lt;/code&gt; 规则&lt;/strong&gt; 。和 NAPT 的动态转换表不同，端口映射是手动写死的、长期有效的。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;注意端口映射能成立的前提是&lt;strong&gt;你得有公网 IP&lt;/strong&gt; 。下面马上说，现在很多家网已经没有公网 IP 了。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五nat-对-p2p-的阻碍与打洞stun--turn--ice"&gt;&lt;a href="#%e4%ba%94nat-%e5%af%b9-p2p-%e7%9a%84%e9%98%bb%e7%a2%8d%e4%b8%8e%e6%89%93%e6%b4%9estun--turn--ice" class="header-anchor"&gt;&lt;/a&gt;五、NAT 对 P2P 的阻碍与打洞：STUN / TURN / ICE
&lt;/h2&gt;&lt;p&gt;NAT 帮我们省了地址，却给 &lt;strong&gt;P2P（点对点）通信&lt;/strong&gt; 添了大麻烦。&lt;/p&gt;
&lt;p&gt;P2P 的前提是&amp;quot;两台主机互相直接发包&amp;quot;。但 NAT 后面的主机对外不可见，A 想连 B，B 的 NAT 会直接丢包——因为表里没有 A 的条目。语音通话（WebRTC）、BT 下载、游戏联机这类强 P2P 场景全受影响。&lt;/p&gt;
&lt;p&gt;要理解怎么解决，先要知道 NAT 的映射与过滤行为并不完全相同。工程资料常沿用 &lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc3489" target="_blank" rel="noopener"
 &gt;RFC 3489&lt;/a&gt; 的经典分类，从宽松到严格列成下表：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;NAT 类型&lt;/th&gt;
 &lt;th&gt;行为&lt;/th&gt;
 &lt;th&gt;能否打洞&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Full Cone（完全锥型）&lt;/td&gt;
 &lt;td&gt;最宽松：内网主机从内出去后，任何公网主机往那个映射端口发包都能进&lt;/td&gt;
 &lt;td&gt;容易&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Restricted Cone（限制锥型）&lt;/td&gt;
 &lt;td&gt;只允许发往&amp;quot;内网主机曾访问过的那个公网 IP&amp;quot;的包进来&lt;/td&gt;
 &lt;td&gt;可打洞&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Port Restricted Cone（端口限制锥型）&lt;/td&gt;
 &lt;td&gt;还限制&amp;quot;必须是那个 IP 的那个端口&amp;quot;&lt;/td&gt;
 &lt;td&gt;可打洞&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Symmetric（对称型）&lt;/td&gt;
 &lt;td&gt;最严格：对不同目的分配不同外部端口，外部几乎无法预测&lt;/td&gt;
 &lt;td&gt;很难，通常要中转&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;术语演进：RFC 3489 后来被 &lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc5389" target="_blank" rel="noopener"
 &gt;RFC 5389&lt;/a&gt; 取代，RFC 4787 / RFC 5780 等文档改用**映射行为（mapping behavior）&lt;strong&gt;和&lt;/strong&gt;过滤行为（filtering behavior）**两个维度描述 NAT。两者必须分别判断；端口保持、hairpinning 和映射超时也会影响连通性。工程里老叫法仍然流行，因此这里只把它当作历史速记。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;针对这个困境，业界提出了三件套：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;STUN&lt;/strong&gt;（Session Traversal Utilities for NAT）：客户端向公网的 STUN 服务器发请求，STUN 回包告诉它“你在公网看来是哪个 IP:端口”。这个服务器反射地址可作为 ICE 候选之一；打洞能否成功还取决于双方 NAT 的映射、过滤、端口保持和超时行为，不能只用“是否对称型”一个标签判断。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TURN&lt;/strong&gt;（Traversal Using Relays around NAT）：当打洞失败（典型是对称型 NAT），就退而求其次，让一台公网中继服务器转发两端流量。能连通，但代价是延迟增加、中继服务器带宽吃紧。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ICE&lt;/strong&gt;（Interactive Connectivity Establishment）：一套&lt;strong&gt;编排框架&lt;/strong&gt;，把直连、STUN 打洞、TURN 中继都列成候选路径，按优先级依次尝试，选第一个能通的。WebRTC 就是基于 ICE 来协商 P2P 媒体通道的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话：&lt;strong&gt;STUN 负责发现公网映射、尝试打洞；TURN 在打不通时当中继；ICE 负责把两者和直连编排起来选最优路径&lt;/strong&gt; 。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六cgnat为什么家网没有公网-ip"&gt;&lt;a href="#%e5%85%adcgnat%e4%b8%ba%e4%bb%80%e4%b9%88%e5%ae%b6%e7%bd%91%e6%b2%a1%e6%9c%89%e5%85%ac%e7%bd%91-ip" class="header-anchor"&gt;&lt;/a&gt;六、CGNAT：为什么家网没有公网 IP
&lt;/h2&gt;&lt;p&gt;如果你试过在家宽带路由器上配端口映射，却怎么也连不上——很可能你的宽带已经在 &lt;strong&gt;CGNAT&lt;/strong&gt;（Carrier-Grade NAT，运营商级 NAT）后面了，&lt;strong&gt;你拿到的根本不是公网 IP&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;CGNAT 是运营商部署的大规模 NAT，目的和家网 NAT 一样：省公网地址。结构上是&lt;strong&gt;两层 NAT 串联&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你的设备 ──► 家用路由器(NAT) ──► 运营商 CGNAT(NAT) ──► 公网
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.x 10.x / 100.64.x 公网 IP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;你家路由器拿到的是运营商分配的&amp;quot;伪公网 IP&amp;quot;，通常是 &lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc6598" target="_blank" rel="noopener"
 &gt;RFC 6598&lt;/a&gt; 定义的共享地址段 &lt;strong&gt;&lt;code&gt;100.64.0.0/10&lt;/code&gt;&lt;/strong&gt;（100.64.0.0 – 100.127.255.255）；&lt;/li&gt;
&lt;li&gt;这个段&lt;strong&gt;专门给 CGNAT 用&lt;/strong&gt;，和 RFC 1918 私网段区分开，避免和企业内网冲突；&lt;/li&gt;
&lt;li&gt;你的流量到运营商 CGNAT 后，再被改写成真正的公网 IP 出网。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后果就是：&lt;strong&gt;你家路由器做的端口映射，只把端口开到了 &lt;code&gt;100.64.x.x&lt;/code&gt; 那一层，公网根本到不了&lt;/strong&gt; 。这种情况下想做自托管、被外网主动访问，基本只能靠内网穿透（frp、ngrok、Tailscale 之类）走中继了。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;怎么判断自己是不是在 CGNAT 后面？先看路由器 WAN 地址是否位于 &lt;code&gt;100.64.0.0/10&lt;/code&gt; 或 RFC 1918 私网段，再与外部网站看到的出口地址对比。两者不一致是“中间还存在 NAT、代理或 VPN”的强烈迹象，但不能单凭这一点断定就是运营商 CGNAT；双路由、企业出口和 VPN 也会造成类似现象。必要时再结合 traceroute 和运营商信息确认。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;CGNAT 的大量部署，本身就是 IPv4 地址枯竭的直接证据。彻底解决要靠 IPv6（它有 2¹²⁸ 个地址，足以让每粒沙子都有公网 IP），但 IPv6 的全面替换依旧是个漫长的工程。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七dhcp自动分配-ipdora-四步"&gt;&lt;a href="#%e4%b8%83dhcp%e8%87%aa%e5%8a%a8%e5%88%86%e9%85%8d-ipdora-%e5%9b%9b%e6%ad%a5" class="header-anchor"&gt;&lt;/a&gt;七、DHCP：自动分配 IP（DORA 四步）
&lt;/h2&gt;&lt;p&gt;讲完了地址转换，再说地址&lt;strong&gt;怎么来的&lt;/strong&gt;。手动给每台机器配 IP 显然不现实——办公网络、Wi-Fi、容器都要求&amp;quot;插上网线 / 连上 Wi-Fi 就能上网&amp;quot;。这件事由 &lt;strong&gt;DHCP&lt;/strong&gt;（Dynamic Host Configuration Protocol，动态主机配置协议）完成。&lt;/p&gt;
&lt;p&gt;DHCP 不只分配 IP，还一次性把这几样都下发：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IP 地址&lt;/strong&gt; + &lt;strong&gt;子网掩码&lt;/strong&gt;（告诉你 IP 和网络范围）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认网关&lt;/strong&gt;（去往外网的下一跳，详见路由那篇）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS 服务器&lt;/strong&gt;（域名解析用谁）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;租约时间&lt;/strong&gt;（lease time，这个 IP 借你用多久）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dora-四步"&gt;&lt;a href="#dora-%e5%9b%9b%e6%ad%a5" class="header-anchor"&gt;&lt;/a&gt;DORA 四步
&lt;/h3&gt;&lt;p&gt;DHCP 获取地址的过程叫 &lt;strong&gt;DORA&lt;/strong&gt;，是四个报文的首字母：&lt;strong&gt;D&lt;/strong&gt;iscover → &lt;strong&gt;O&lt;/strong&gt;ffer → &lt;strong&gt;R&lt;/strong&gt;equest → &lt;strong&gt;A&lt;/strong&gt;cknowledge。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;客户端 DHCP 服务器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (还没 IP，用 0.0.0.0 作源) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ① DHCPDISCOVER (广播, → UDP 67) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ─────────────────────────────────────────► │ &amp;#34;谁是 DHCP 服务器？我要 IP&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ② DHCPOFFER (→ UDP 68) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◄──────────────────────────────────────── │ &amp;#34;我这有个 IP 给你，附掩码/网关/DNS&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ③ DHCPREQUEST (广播) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ─────────────────────────────────────────► │ &amp;#34;我就要你给的这个&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ④ DHCPACK (→ UDP 68) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◄──────────────────────────────────────── │ &amp;#34;确认，租约 86400 秒，拿去用&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 现在客户端正式配置好 IP，可以通信了 │
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;几个关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;端口&lt;/strong&gt;：服务器监听 &lt;strong&gt;UDP 67&lt;/strong&gt;，客户端监听 &lt;strong&gt;UDP 68&lt;/strong&gt; 。Discover 是广播（因为客户端还不知道服务器在哪），Offer / ACK 通常也是广播或单播取决于实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为什么 Request 要广播&lt;/strong&gt;：可能网络里有多个 DHCP 服务器都回了 Offer，客户端广播 Request 既是在&amp;quot;认领&amp;quot;选中的那个，也是在告诉其余服务器&amp;quot;我没选你，你可以回收那个 IP&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;竞态&lt;/strong&gt;：客户端发 Discover 后可能收到多个 Offer，一般选最先到达的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="租约与续约"&gt;&lt;a href="#%e7%a7%9f%e7%ba%a6%e4%b8%8e%e7%bb%ad%e7%ba%a6" class="header-anchor"&gt;&lt;/a&gt;租约与续约
&lt;/h3&gt;&lt;p&gt;DHCP 给的 IP 不是永久的，而是&lt;strong&gt;租约（lease）&lt;/strong&gt; ，到期不续就回收。续约过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;到租约的 &lt;strong&gt;50%（T1）&lt;/strong&gt;，客户端向原服务器单播 Request 续约；&lt;/li&gt;
&lt;li&gt;到 &lt;strong&gt;87.5%（T2）&lt;/strong&gt;，若原服务器没响应，客户端进入 rebinding，向任意 DHCP 服务器广播求续；&lt;/li&gt;
&lt;li&gt;若到 &lt;strong&gt;100% 还没续上&lt;/strong&gt;，客户端必须释放 IP，重新走一遍 DORA。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是为什么&amp;quot;重启路由器 IP 会变&amp;quot;——租约可能到期被回收，再申请时拿到的是池里另一个 IP。同样，&lt;code&gt;ifconfig&lt;/code&gt; 看到自己 IP 是 &lt;code&gt;169.254.x.x&lt;/code&gt;（链路本地地址），说明 DHCP 整个过程失败了，操作系统给自己随便配了个地址应急。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;DHCP 的初始发现依赖广播，但服务器并非“无状态”：它需要维护地址池、客户端标识、租约和到期时间。服务器短时不可用时，客户端通常仍可在现有租约有效期内使用地址，但续租和重新分配会受影响。同一网段可以部署经过协调的多个 DHCP 服务器，却不能随便接入未授权服务器；交换机的 DHCP Snooping 会限制只有信任端口能够发送服务器侧报文，防止私接 DHCP 把终端带到错误网络。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NAT 的本质&lt;/strong&gt;是在私网/公网边界改写地址，让多台设备共用一个公网 IP，缓解 IPv4 地址枯竭；实际用的 &lt;strong&gt;NAPT&lt;/strong&gt; 同时改 IP 和端口，靠一张有状态的转换表把流量对回正确主机。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RFC 1918&lt;/strong&gt; 定义了三个可重复使用的私网地址段（10/8、172.16/12、192.168/16）；&lt;strong&gt;RFC 6598&lt;/strong&gt; 的 &lt;code&gt;100.64.0.0/10&lt;/code&gt; 专给运营商 &lt;strong&gt;CGNAT&lt;/strong&gt; 用，CGNAT 是家网&amp;quot;没有公网 IP&amp;quot;的根因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;端口映射&lt;/strong&gt;（DNAT）把公网端口固定转发到内网，用于自托管；前提是真公网 IP。&lt;/li&gt;
&lt;li&gt;NAT 破坏了端到端，给 &lt;strong&gt;P2P&lt;/strong&gt; 制造障碍；&lt;strong&gt;STUN 打洞、TURN 中继、ICE 编排&lt;/strong&gt; 是 WebRTC 等场景的标准解法，对称型 NAT 通常只能靠 TURN 中继。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DHCP&lt;/strong&gt; 用 &lt;strong&gt;DORA&lt;/strong&gt; 四步（Discover/Offer/Request/Acknowledge）自动下发 IP、掩码、网关、DNS，配合租约机制管理地址池；端口是 UDP 67（服务器）/ 68（客户端）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇是系列收官：&lt;strong&gt;TCP/IP（十三）：抓包与网络诊断方法论&lt;/strong&gt; ——把前面学的所有协议落到工具上，给一套分层的连通性排障方法论，再把整个系列串一遍。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc1918" target="_blank" rel="noopener"
 &gt;RFC 1918: Address Allocation for Private Internets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc6598" target="_blank" rel="noopener"
 &gt;RFC 6598: IANA-Reserved IPv4 Prefix for Shared Address Space&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc3489" target="_blank" rel="noopener"
 &gt;RFC 3489: STUN (Classic)&lt;/a&gt; / &lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc5389" target="_blank" rel="noopener"
 &gt;RFC 5389: STUN&lt;/a&gt; / &lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc8445" target="_blank" rel="noopener"
 &gt;RFC 8445: ICE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc2131" target="_blank" rel="noopener"
 &gt;RFC 2131: Dynamic Host Configuration Protocol (DHCP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/Carrier-grade_NAT" target="_blank" rel="noopener"
 &gt;Carrier-grade NAT — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（十一）：HTTP、HTTPS 与 TLS</title><link>https://www.jiwei.space/posts/networking/tcp-ip/11-http-tls/</link><pubDate>Sat, 06 Jun 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/11-http-tls/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇讲了 DNS——怎么把 &lt;code&gt;example.com&lt;/code&gt; 这样的域名变成 IP。拿到 IP 之后，浏览器要做的下一件事就是：&lt;strong&gt;建连、发请求、拿数据&lt;/strong&gt;。承载这套交互的，是应用层里最成功的协议——HTTP。&lt;/p&gt;
&lt;p&gt;但光有 HTTP 还不够：它是明文的，链路上任何人都能偷看甚至篡改。于是有了 HTTPS，也就是 &lt;strong&gt;HTTP 套上一层 TLS&lt;/strong&gt;。这一篇把两件事讲透：HTTP 从 1.0 一路演进到 3 到底在解决什么痛点，以及 TLS 那次&amp;quot;握手&amp;quot;究竟握了什么、为什么需要证书链。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;本文是应用层的&amp;quot;主菜&amp;quot;。如果你更关心&amp;quot;一次完整的 API 请求在工程上怎么跑&amp;quot;，可以回看本站的 &lt;a class="link" href="https://www.jiwei.space/posts/architecture/request-lifecycle/" &gt;后端全链路解剖&lt;/a&gt;；想深入 .NET 里 HTTP 客户端的坑（DNS 不刷新、socket 耗尽、连接池），看 &lt;a class="link" href="https://www.jiwei.space/posts/dotnet/httpclient-deep-dive/" &gt;HttpClient 的前世今生&lt;/a&gt;。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一应用层概览http-站在哪儿"&gt;&lt;a href="#%e4%b8%80%e5%ba%94%e7%94%a8%e5%b1%82%e6%a6%82%e8%a7%88http-%e7%ab%99%e5%9c%a8%e5%93%aa%e5%84%bf" class="header-anchor"&gt;&lt;/a&gt;一、应用层概览：HTTP 站在哪儿
&lt;/h2&gt;&lt;p&gt;回忆第五层模型：应用层直接跑在传输层之上。HTTP 绝大多数时候&lt;strong&gt;跑在 TCP 上&lt;/strong&gt;（可靠传输对网页、API 来说是刚需），默认端口 80；加密后的 HTTPS 默认端口 443。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用层： HTTP / HTTPS （应用报文）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ （HTTPS 在这里多一层 TLS，把 HTTP 加密后再交给 TCP）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;传输层： TCP（端口 80 / 443）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;TLS 严格说不是&amp;quot;传输层之上、应用层之下&amp;quot;的独立层（五层模型里它通常算应用层的一部分），但工程上它确实横在 HTTP 和 TCP 之间，对 HTTP 透明——HTTP 该怎么发还怎么发，只是字节流被加了一层加密。&lt;/p&gt;
&lt;p&gt;HTTP 能一统 Web，靠的是&lt;strong&gt;无状态 + 请求-响应 + 文本可读&lt;/strong&gt; 的极简模型。但这个模型在性能上踩过不少坑，每一次大版本升级都是在对这些坑打补丁。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二http10每个请求开一条连接"&gt;&lt;a href="#%e4%ba%8chttp10%e6%af%8f%e4%b8%aa%e8%af%b7%e6%b1%82%e5%bc%80%e4%b8%80%e6%9d%a1%e8%bf%9e%e6%8e%a5" class="header-anchor"&gt;&lt;/a&gt;二、HTTP/1.0：每个请求开一条连接
&lt;/h2&gt;&lt;p&gt;1996 年的 HTTP/1.0（RFC 1945）模型简单粗暴：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client Server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── TCP 三次握手 ──────────────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── HTTP 请求 ─────────────────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── HTTP 响应 ───────────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── TCP 四次挥手（连接关闭）────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── 下一个请求？再握手一遍 ─────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;默认每个请求单独开一条 TCP 连接&lt;/strong&gt;，响应完就关。痛点很明显：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;每个请求都要一次 TCP 握手&lt;/strong&gt;（1 个 RTT）+ 一次 TLS 握手（如果走 HTTPS，又是 1~2 个 RTT）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP 慢启动&lt;/strong&gt; 每条新连接都要从头爬坡，根本用不上已经调好的拥塞窗口；&lt;/li&gt;
&lt;li&gt;一个页面几十个资源，就几十次握手，延迟堆叠非常可怕。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;严格说，HTTP/1.0 并非完全不能复用连接——它有个 &lt;code&gt;Connection: Keep-Alive&lt;/code&gt; 扩展头可以勉强长连，但&lt;strong&gt;非默认&lt;/strong&gt;、且语义不统一。真正把长连接变成默认行为的是 1.1。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="三http11keep-alivehost-头与管线化"&gt;&lt;a href="#%e4%b8%89http11keep-alivehost-%e5%a4%b4%e4%b8%8e%e7%ae%a1%e7%ba%bf%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;三、HTTP/1.1：keep-alive、Host 头与管线化
&lt;/h2&gt;&lt;p&gt;1997 年的 HTTP/1.1（RFC 2068，后由 RFC 2616 / 7230 系列修订）是生命力最长的一版，今天很多服务还跑在它上面。三大关键改进：&lt;/p&gt;
&lt;h3 id="1-持久连接keep-alive成为默认"&gt;&lt;a href="#1-%e6%8c%81%e4%b9%85%e8%bf%9e%e6%8e%a5keep-alive%e6%88%90%e4%b8%ba%e9%bb%98%e8%ae%a4" class="header-anchor"&gt;&lt;/a&gt;1. 持久连接（keep-alive）成为默认
&lt;/h3&gt;&lt;p&gt;连接不再用完即弃，而是&lt;strong&gt;默认保持打开&lt;/strong&gt;，后续请求复用同一条 TCP 连接，省掉了反复握手的开销：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;── TCP 握手 ──▶ 请求1 → 响应1 → 请求2 → 响应2 → …… → 请求N → 响应N ── TCP 关闭 ──▶
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────────── 一条连接复用 ────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-host-头虚拟主机的基石"&gt;&lt;a href="#2-host-%e5%a4%b4%e8%99%9a%e6%8b%9f%e4%b8%bb%e6%9c%ba%e7%9a%84%e5%9f%ba%e7%9f%b3" class="header-anchor"&gt;&lt;/a&gt;2. Host 头：虚拟主机的基石
&lt;/h3&gt;&lt;p&gt;请求行里加了 &lt;code&gt;Host: www.example.com&lt;/code&gt;。这让&lt;strong&gt;一台服务器、一个 IP 上能托管成百上千个域名&lt;/strong&gt; —— 服务器靠 Host 头区分请求要给哪个站点。没有这一条，今天的云托管 / SaaS 多租户根本做不起来。&lt;/p&gt;
&lt;h3 id="3-管线化pipelining想法很美落地很惨"&gt;&lt;a href="#3-%e7%ae%a1%e7%ba%bf%e5%8c%96pipelining%e6%83%b3%e6%b3%95%e5%be%88%e7%be%8e%e8%90%bd%e5%9c%b0%e5%be%88%e6%83%a8" class="header-anchor"&gt;&lt;/a&gt;3. 管线化（pipelining）：想法很美，落地很惨
&lt;/h3&gt;&lt;p&gt;1.1 允许客户端&lt;strong&gt;连发多个请求不等响应&lt;/strong&gt;（流水线）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client：请求1, 请求2, 请求3 ─────▶ Server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client ◀──── 响应1, 响应2, 响应3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;听起来能省掉等待，但有两个致命问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用层队头阻塞&lt;/strong&gt; —— 响应必须&lt;strong&gt;按请求顺序&lt;/strong&gt; 返回，响应 1 慢了，2 和 3 再快也得堵在后面；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代理 / 服务器兼容性差&lt;/strong&gt; —— 不少中间件对管线化支持有 bug，容易串响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果：&lt;strong&gt;主流浏览器默认关闭管线化&lt;/strong&gt;，实际生产里基本没人用，HTTP/2 的多路复用才是它的&amp;quot;正版替代品&amp;quot;。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;1.1 还顺手加了 &lt;strong&gt;chunked transfer encoding（分块传输）&lt;/strong&gt; —— 边生成边发，不用提前知道 Content-Length，对动态页面和流式响应很关键。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="四http2二进制帧多路复用与首部压缩"&gt;&lt;a href="#%e5%9b%9bhttp2%e4%ba%8c%e8%bf%9b%e5%88%b6%e5%b8%a7%e5%a4%9a%e8%b7%af%e5%a4%8d%e7%94%a8%e4%b8%8e%e9%a6%96%e9%83%a8%e5%8e%8b%e7%bc%a9" class="header-anchor"&gt;&lt;/a&gt;四、HTTP/2：二进制帧、多路复用与首部压缩
&lt;/h2&gt;&lt;p&gt;2015 年的 HTTP/2（RFC 7540，来自 Google 的 SPDY）做了一次大手术，但&lt;strong&gt;仍然跑在 TCP 上&lt;/strong&gt;。四个核心特性：&lt;/p&gt;
&lt;h3 id="1-二进制分帧"&gt;&lt;a href="#1-%e4%ba%8c%e8%bf%9b%e5%88%b6%e5%88%86%e5%b8%a7" class="header-anchor"&gt;&lt;/a&gt;1. 二进制分帧
&lt;/h3&gt;&lt;p&gt;抛弃 1.1 的文本格式，把每个请求/响应拆成一个个&lt;strong&gt;二进制帧（frame）&lt;/strong&gt;。一个连接里可以混合传输属于不同请求的帧：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;一条 TCP 连接
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Stream 1： 帧 │ 帧 │ │ 帧 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Stream 3： │ 帧 │ 帧 │ │ 帧
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Stream 5： 帧 │ │ 帧 │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↑ 同一条连接里交织发帧
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-多路复用multiplexing"&gt;&lt;a href="#2-%e5%a4%9a%e8%b7%af%e5%a4%8d%e7%94%a8multiplexing" class="header-anchor"&gt;&lt;/a&gt;2. 多路复用（multiplexing）
&lt;/h3&gt;&lt;p&gt;这是 HTTP/2 的杀手锏。&lt;strong&gt;一条 TCP 连接上同时跑无数个并发请求/响应&lt;/strong&gt;，每个请求是一个独立的 stream，互不阻塞。1.1 的应用层队头阻塞被干掉了——再也不用为了&amp;quot;并发&amp;quot;去开 6~8 条连接。&lt;/p&gt;
&lt;h3 id="3-首部压缩hpack"&gt;&lt;a href="#3-%e9%a6%96%e9%83%a8%e5%8e%8b%e7%bc%a9hpack" class="header-anchor"&gt;&lt;/a&gt;3. 首部压缩（HPACK）
&lt;/h3&gt;&lt;p&gt;1.1 每个请求都带一坨重复的 header（Cookie、User-Agent、Accept …）。HTTP/2 用 &lt;strong&gt;HPACK&lt;/strong&gt; 算法压缩这些头部，还维护一张静态表 + 动态表，重复字段只传索引，能省下可观字节。&lt;/p&gt;
&lt;h3 id="4-服务端推送server-push-已废弃"&gt;&lt;a href="#4-%e6%9c%8d%e5%8a%a1%e7%ab%af%e6%8e%a8%e9%80%81server-push-%e5%b7%b2%e5%ba%9f%e5%bc%83" class="header-anchor"&gt;&lt;/a&gt;4. 服务端推送（Server Push）—— 已废弃
&lt;/h3&gt;&lt;p&gt;服务器可以在客户端请求 A 时，主动把 B、C 一起推过去（&amp;ldquo;你待会儿肯定要这俩&amp;rdquo;）。但实践中&lt;strong&gt;收益不稳、难调优、还浪费带宽&lt;/strong&gt;，Chrome 从 106 版（2022 年）起默认禁用、随后彻底移除。&lt;strong&gt;今天它事实上已经死了&lt;/strong&gt;，替代方案是 &lt;code&gt;103 Early Hints&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="没解决的痛tcp-层队头阻塞"&gt;&lt;a href="#%e6%b2%a1%e8%a7%a3%e5%86%b3%e7%9a%84%e7%97%9btcp-%e5%b1%82%e9%98%9f%e5%a4%b4%e9%98%bb%e5%a1%9e" class="header-anchor"&gt;&lt;/a&gt;没解决的痛：TCP 层队头阻塞
&lt;/h3&gt;&lt;p&gt;HTTP/2 解决了&lt;strong&gt;应用层&lt;/strong&gt; 的队头阻塞，但底层还是一条 TCP 连接。一旦某个 TCP 包丢了，&lt;strong&gt;TCP 会卡住整条连接&lt;/strong&gt; 等重传——所有 stream 上的帧（哪怕本身已经到了接收端）都得等着。并发越多，这个&amp;quot;被一个丢哥拖垮全军&amp;quot;的问题越明显。这正是 HTTP/3 要解决的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五http3扔掉-tcp跑在-quic-上"&gt;&lt;a href="#%e4%ba%94http3%e6%89%94%e6%8e%89-tcp%e8%b7%91%e5%9c%a8-quic-%e4%b8%8a" class="header-anchor"&gt;&lt;/a&gt;五、HTTP/3：扔掉 TCP，跑在 QUIC 上
&lt;/h2&gt;&lt;p&gt;2022 年正式发布的 HTTP/3（RFC 9114）做了一个激进决定：&lt;strong&gt;不再跑 TCP，改跑 QUIC，而 QUIC 跑在 UDP 上&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2： HTTP → TLS → TCP → IP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/3： HTTP → QUIC（内建 TLS 1.3，跑在 UDP） → IP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;QUIC（RFC 9000）把传输层和加密层&lt;strong&gt;揉在了一起&lt;/strong&gt; 重新设计，带来几个关键收益：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;特性&lt;/th&gt;
 &lt;th&gt;解决了什么&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;独立 stream，无 TCP 队头阻塞&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;一个 stream 丢包只卡它自己，其他 stream 照跑&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;集成 TLS 1.3&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;握手和传输合并，1-RTT 建连，复用时 0-RTT&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;连接迁移&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;手机从 Wi-Fi 切 4G，IP 变了连接不断（靠 Connection ID，不靠四元组）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;0-RTT 数据&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;对访问过的服务器，首个请求的数据能和握手一起发出去&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;代价是部署上：QUIC 跑 UDP，而&lt;strong&gt;不少企业防火墙 / 中间盒对 UDP 不友好&lt;/strong&gt;（限速、直接丢），所以实践中 HTTP/3 的升级往往需要先 HTTP/2 协商 &lt;code&gt;Alt-Svc&lt;/code&gt; 再尝试切换。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;各版本一句话对比：&lt;strong&gt;1.0 一请求一连接，1.1 默认长连 + Host，HTTP/2 多路复用但卡在 TCP，HTTP/3 干脆换传输层彻底解决队头阻塞&lt;/strong&gt; 。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;版本&lt;/th&gt;
 &lt;th&gt;年份&lt;/th&gt;
 &lt;th&gt;传输层&lt;/th&gt;
 &lt;th&gt;关键特性&lt;/th&gt;
 &lt;th&gt;主要痛点&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;HTTP/1.0&lt;/td&gt;
 &lt;td&gt;1996&lt;/td&gt;
 &lt;td&gt;TCP&lt;/td&gt;
 &lt;td&gt;每请求一连接（默认）&lt;/td&gt;
 &lt;td&gt;握手开销大、慢启动从头爬&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;HTTP/1.1&lt;/td&gt;
 &lt;td&gt;1997&lt;/td&gt;
 &lt;td&gt;TCP&lt;/td&gt;
 &lt;td&gt;keep-alive、Host 头、管线化、chunked&lt;/td&gt;
 &lt;td&gt;应用层队头阻塞、管线化难落地&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;HTTP/2&lt;/td&gt;
 &lt;td&gt;2015&lt;/td&gt;
 &lt;td&gt;TCP&lt;/td&gt;
 &lt;td&gt;二进制帧、多路复用、HPACK、推送&lt;/td&gt;
 &lt;td&gt;TCP 层队头阻塞、推送已废弃&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;HTTP/3&lt;/td&gt;
 &lt;td&gt;2022&lt;/td&gt;
 &lt;td&gt;QUIC(UDP)&lt;/td&gt;
 &lt;td&gt;独立流、集成 TLS 1.3、0-RTT、连接迁移&lt;/td&gt;
 &lt;td&gt;UDP 在部分网络 / 防火墙受限&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="六https--http--tls为什么需要这层壳"&gt;&lt;a href="#%e5%85%adhttps--http--tls%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81%e8%bf%99%e5%b1%82%e5%a3%b3" class="header-anchor"&gt;&lt;/a&gt;六、HTTPS = HTTP + TLS：为什么需要这层壳
&lt;/h2&gt;&lt;p&gt;HTTP 本身不加密，明文跑在网络上意味着：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;能被窃听&lt;/strong&gt; —— 链路上的任何一跳都能看到你传了什么（密码、token、业务数据）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能被篡改&lt;/strong&gt; —— 中间人可以改响应、插广告、劫持跳转；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法验证身份&lt;/strong&gt; —— 你连的 &lt;code&gt;bank.com&lt;/code&gt; 到底是真银行还是钓鱼站，HTTP 自己说了不算。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TLS（Transport Layer Security，前身 SSL）补三件事，正好对应信息安全的三大目标：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;TLS 提供&lt;/th&gt;
 &lt;th&gt;对应威胁&lt;/th&gt;
 &lt;th&gt;怎么做到&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;机密性（加密）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;窃听&lt;/td&gt;
 &lt;td&gt;协商出对称密钥，加密应用数据&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;身份认证&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;钓鱼 / 中间人冒充&lt;/td&gt;
 &lt;td&gt;服务器出示&lt;strong&gt;证书&lt;/strong&gt;，由受信任的 CA 签名&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;完整性&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;篡改&lt;/td&gt;
 &lt;td&gt;每条记录带 MAC / AEAD 标签，改一个字节都验不过&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;所以 HTTPS 不是&amp;quot;一种新协议&amp;quot;，而是 &lt;strong&gt;HTTP 的字节流先经 TLS 加密，再交给 TCP&lt;/strong&gt;。对 HTTP 来说它完全透明。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七tls-握手到底握了什么"&gt;&lt;a href="#%e4%b8%83tls-%e6%8f%a1%e6%89%8b%e5%88%b0%e5%ba%95%e6%8f%a1%e4%ba%86%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;七、TLS 握手：到底&amp;quot;握&amp;quot;了什么
&lt;/h2&gt;&lt;p&gt;TLS 握手要解决一个核心难题：&lt;strong&gt;双方在一条不安全的信道上，协商出一个只有他们俩知道的对称密钥&lt;/strong&gt;，同时让客户端确认服务器的身份。下面分别看 1.2 和 1.3。&lt;/p&gt;
&lt;h3 id="tls-122-rtt明文握手主体"&gt;&lt;a href="#tls-122-rtt%e6%98%8e%e6%96%87%e6%8f%a1%e6%89%8b%e4%b8%bb%e4%bd%93" class="header-anchor"&gt;&lt;/a&gt;TLS 1.2：2-RTT，明文握手主体
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client Server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── ClientHello (支持的 TLS 版本、加密套件、随机数 Rc) ─▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── ServerHello (选定套件、随机数 Rs) ───────────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── Certificate (服务器证书) ────────────────────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── ServerKeyExchange (DH 参数，视套件而定) ─────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── ServerHelloDone ─────────────────────────────────── │ ← 第 1 个 RTT 结束
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── ClientKeyExchange (DH 公开值) ──────────────────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── ChangeCipherSpec ──────────────────────────────────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── Finished (已加密，含握手摘要) ──────────────────────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── ChangeCipherSpec ────────────────────────────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── Finished (已加密) ────────────────────────────────── │ ← 第 2 个 RTT 结束
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀═══════ 加密的应用数据（HTTP） ═════════════════════▶ │
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;握手干了四件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;协商参数&lt;/strong&gt; —— 双方交换支持的版本、加密套件、两个随机数（Rc、Rs）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证身份&lt;/strong&gt; —— 服务器发证书，客户端用本地信任库验证证书链（见下一节）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建立共享秘密并派生密钥&lt;/strong&gt; —— 以常见的 ECDHE 为例，双方各生成临时密钥对，用本端临时私钥和对端临时公钥算出相同的共享秘密，再结合 Client Random、Server Random 等握手上下文派生会话密钥。证书私钥和 ECDHE 临时私钥都只在本端使用、不会通过网络发送；“私钥永远不上线”并不准确，服务器通常在本机或 HSM 中使用证书私钥完成签名；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确认无误&lt;/strong&gt; —— 双方各发一个 &lt;code&gt;Finished&lt;/code&gt;，包含整段握手的摘要，确保握手没被篡改。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;之后用的对称密钥就是从这里来的。非对称（证书 + DH）只为这一步服务，因为它慢；后续大量数据用对称密钥加密。&lt;/p&gt;
&lt;h3 id="tls-131-rtt握手主体被加密"&gt;&lt;a href="#tls-131-rtt%e6%8f%a1%e6%89%8b%e4%b8%bb%e4%bd%93%e8%a2%ab%e5%8a%a0%e5%af%86" class="header-anchor"&gt;&lt;/a&gt;TLS 1.3：1-RTT，握手主体被加密
&lt;/h3&gt;&lt;p&gt;TLS 1.3（RFC 8446，2018）做了大刀阔斧的精简：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client Server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── ClientHello (套件、随机数、密钥共享 KeyShare) ────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ （附上客户端的 DH 公开值，不再等） │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── ServerHello (套件、随机数、KeyShare) ────────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ▼ 从这条消息之后，所有握手记录全部加密 ▼ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── {EncryptedExtensions} ─────────────────────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── {Certificate} ──────────────────────────────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── {CertificateVerify} (用证书私钥签名) ───────────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── {Finished} ─────────────────────────────────────── │ ← 第 1 个 RTT 结束
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── {Finished} ──────────────────────────────────────▶ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀═══════ 加密的应用数据（HTTP） ═════════════════════▶ │
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;相比 1.2，1.3 有三个显著改进：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;少一个 RTT&lt;/strong&gt; —— 客户端在 &lt;code&gt;ClientHello&lt;/code&gt; 里就带上密钥共享（KeyShare），服务器第一个回包就能算出密钥、开始加密，握手压到 &lt;strong&gt;1-RTT&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;握手加密&lt;/strong&gt; —— &lt;code&gt;ServerHello&lt;/code&gt; 之后的所有消息都加密了，连证书都不再明文暴露（防被动监听窥探服务器证书）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0-RTT 恢复&lt;/strong&gt; —— 如果是复用之前的会话（PSK），客户端可以在 &lt;code&gt;ClientHello&lt;/code&gt; 里&lt;strong&gt;直接捎带应用数据&lt;/strong&gt;，连那 1 个 RTT 都省了。代价是 0-RTT 数据不具备完整的前向保密保证，而且可能跨连接被重放，因此只能发送应用明确判定为&lt;strong&gt;可安全重放&lt;/strong&gt;的数据。幂等请求常是候选，但“幂等”不自动等于“重放安全”，仍要结合计费、审计、限额等业务副作用判断。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;1-RTT / 0-RTT 看似只是省了一个往返，但在移动互联网和高延迟链路上，每省一个 RTT 都直接转化成更快的首字节时间（TTFB）。HTTP/3 把 QUIC 和 TLS 1.3 揉在一起，很大程度就是为了把这几个 RTT 彻底榨干。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="八证书链与-ca-信任凭什么信这张证书"&gt;&lt;a href="#%e5%85%ab%e8%af%81%e4%b9%a6%e9%93%be%e4%b8%8e-ca-%e4%bf%a1%e4%bb%bb%e5%87%ad%e4%bb%80%e4%b9%88%e4%bf%a1%e8%bf%99%e5%bc%a0%e8%af%81%e4%b9%a6" class="header-anchor"&gt;&lt;/a&gt;八、证书链与 CA 信任：凭什么信这张证书
&lt;/h2&gt;&lt;p&gt;握手里客户端要&amp;quot;验证服务器证书&amp;quot;，但这张证书凭什么可信？答案是&lt;strong&gt;信任链&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你的服务器证书 中间 CA 证书 根 CA 证书
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;example.com ──签发──▶ Let&amp;#39;s Encrypt R3 ──签发──▶ ISRG Root X1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; （根证书自签名，预装在
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 操作系统 / 浏览器信任库里）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;验证过程是&lt;strong&gt;沿着链向上找&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;服务器在握手里不只发自己的证书，而是&lt;strong&gt;发整条链&lt;/strong&gt;（服务器证书 + 中间 CA 证书）；&lt;/li&gt;
&lt;li&gt;客户端用中间 CA 的公钥验证服务器证书的签名 → 验证通过，说明服务器证书确实由该中间 CA 签发；&lt;/li&gt;
&lt;li&gt;再用根 CA 的公钥验证中间 CA 证书的签名 → 一路验到&lt;strong&gt;根 CA&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;根 CA 是&lt;strong&gt;自签名&lt;/strong&gt; 的，它的证书&lt;strong&gt;预装在操作系统 / 浏览器的信任库里&lt;/strong&gt;（Windows、macOS、Firefox、Android 各自维护一份）。只要根在信任库里，这条链就算可信。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;信任的根基是&amp;quot;你的设备信任哪些根 CA&amp;quot;&lt;/strong&gt;。这套体系（PKI，Public Key Infrastructure）把&amp;quot;信任某个网站&amp;quot;简化成&amp;quot;信任几百个根 CA&amp;quot;，而根 CA 再通过中间 CA 把签名能力分级下发。&lt;/p&gt;
&lt;p&gt;证书可能出问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;过期&lt;/strong&gt; —— 证书有有效期，过期了浏览器会拦截。线上事故里&amp;quot;证书忘续期&amp;quot;是高频低级错误，运维必须配监控自动续期（Let&amp;rsquo;s Encrypt + certbot / cert-manager）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;吊销&lt;/strong&gt; —— 私钥泄露或站点停业，CA 要把证书提前作废。机制有 CRL（吊销列表，已少用）和 &lt;strong&gt;OCSP&lt;/strong&gt;（在线查状态）；但都偏慢，现代浏览器倾向用 &lt;strong&gt;OCSP Stapling&lt;/strong&gt;（服务器主动把 OCSP 响应钉在握手里）。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;为什么要有中间 CA，不让根直接签？因为&lt;strong&gt;根私钥是整个体系的命根子&lt;/strong&gt;，必须离线存放、极少使用。日常签发全交给中间 CA，根只在签发/轮换中间 CA 时才出动——一旦根私钥泄露，整个信任体系崩塌。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP/1.0&lt;/strong&gt; 一请求一连接、握手开销大；&lt;strong&gt;HTTP/1.1&lt;/strong&gt; 默认 keep-alive、加 Host 头、引入管线化（但几乎没人用）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/2&lt;/strong&gt; 用二进制帧 + 多路复用 + HPACK 干掉了应用层队头阻塞，服务端推送已废弃；但&lt;strong&gt;底层仍是 TCP，丢包会卡住整条连接&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/3&lt;/strong&gt; 换到 QUIC（UDP），用独立 stream 彻底解决 TCP 队头阻塞，集成 TLS 1.3 实现 1-RTT / 0-RTT 和连接迁移；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS = HTTP + TLS&lt;/strong&gt;，TLS 提供加密、身份认证、完整性，对应机密性、真实性、防篡改三个目标；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS 1.2 要 2-RTT、握手明文；TLS 1.3 精简到 1-RTT、握手加密、支持 0-RTT&lt;/strong&gt;（注意重放风险）；&lt;/li&gt;
&lt;li&gt;证书靠 &lt;strong&gt;CA 信任链&lt;/strong&gt; 取信——服务器证书 ← 中间 CA ← 根 CA（自签名、预装在设备信任库），信任的根基是&amp;quot;你的设备信哪些根 CA&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（十二）：NAT、DHCP 与地址机制&lt;/strong&gt; —— 私网 IP 怎么靠 NAT 出网、为什么家用路由器一个公网 IP 能带几十台设备、DHCP 又是怎么自动分配地址的。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc9114" target="_blank" rel="noopener"
 &gt;HTTP/3 — RFC 9114&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc9113" target="_blank" rel="noopener"
 &gt;HTTP/2 — RFC 9113（原 RFC 7540）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://datatracker.ietf.org/doc/html/rfc8446" target="_blank" rel="noopener"
 &gt;TLS 1.3 — RFC 8446&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://developer.chrome.com/blog/removing-push" target="_blank" rel="noopener"
 &gt;Remove HTTP/2 Server Push from Chrome — Chrome Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.cloudflare.com/introducing-0-rtt/" target="_blank" rel="noopener"
 &gt;Introducing Zero Round Trip Time Resumption (0-RTT) — Cloudflare Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（十）：DNS——从域名到 IP 的解析</title><link>https://www.jiwei.space/posts/networking/tcp-ip/10-dns/</link><pubDate>Thu, 04 Jun 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/10-dns/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;前面九篇把物理、链路、网络、传输四层讲透了：网线上的比特怎么成帧、IP 怎么跨网路由、TCP 怎么保证可靠、UDP 怎么图快。从这一篇开始，我们登上最高层——&lt;strong&gt;应用层&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;应用层的协议很多（HTTP、SMTP、SSH、FTP……），但几乎每一个都绕不开同一个前置动作：&lt;strong&gt;拿到目标机器的 IP&lt;/strong&gt;。用户记不住 &lt;code&gt;93.184.216.34&lt;/code&gt;，只记得 &lt;code&gt;example.com&lt;/code&gt;；而 IP 可能因为换机房、换云厂商而变。这套&amp;quot;域名 ↔ IP&amp;quot;的翻译系统，就是 DNS（Domain Name System）。它既是应用层协议，又是一个遍布全球的分布式数据库——这一篇就把它讲透。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一为什么需要-dns"&gt;&lt;a href="#%e4%b8%80%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81-dns" class="header-anchor"&gt;&lt;/a&gt;一、为什么需要 DNS
&lt;/h2&gt;&lt;p&gt;先想一个没有 DNS 的世界：你想上网，就得在浏览器里敲一串 IP；服务换了 IP，得挨个通知所有用户。这显然不可行。DNS 解决三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;人友好&lt;/strong&gt; —— &lt;code&gt;www.example.com&lt;/code&gt; 比 &lt;code&gt;93.184.216.34&lt;/code&gt; 好记；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解耦&lt;/strong&gt; —— 域名稳定，IP 可变，迁移机器只改 DNS 记录，用户无感；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;负载与高可用&lt;/strong&gt; —— 一个域名可以映射到多台机器，DNS 轮询 / 智能调度都能在域名这一层做。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话：&lt;strong&gt;DNS 是互联网的&amp;quot;通讯录&amp;quot;，把人用的名字翻译成网络层要的 IP&lt;/strong&gt; 。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二dns-的层级结构一棵全球共享的树"&gt;&lt;a href="#%e4%ba%8cdns-%e7%9a%84%e5%b1%82%e7%ba%a7%e7%bb%93%e6%9e%84%e4%b8%80%e6%a3%b5%e5%85%a8%e7%90%83%e5%85%b1%e4%ba%ab%e7%9a%84%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;二、DNS 的层级结构：一棵全球共享的树
&lt;/h2&gt;&lt;p&gt;DNS 不是一个巨型数据库（单点会压垮、会被攻击、政治上也不可接受），而是&lt;strong&gt;按域名分段、层层授权的分布式数据库&lt;/strong&gt;。域名的每一个点分隔一段，对应树上的一层：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 根 (root, 写作 &amp;#34;.&amp;#34;, 通常省略)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌─────────────┼─────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; com org cn ← 顶级域 TLD (Top-Level Domain)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; example example com ← 二级域
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; www mail example ← 三级域 / 子域
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; www
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每一层由不同的服务器群体负责：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;层级&lt;/th&gt;
 &lt;th&gt;服务器&lt;/th&gt;
 &lt;th&gt;职责&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;根域&lt;/td&gt;
 &lt;td&gt;根服务器（13 组逻辑实例，用 anycast 扩到上千台）&lt;/td&gt;
 &lt;td&gt;告诉你各 TLD（.com / .org / .cn …）由谁管&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;顶级域 TLD&lt;/td&gt;
 &lt;td&gt;TLD 服务器（由注册局运营，如 Verisign 管 .com）&lt;/td&gt;
 &lt;td&gt;告诉你 &lt;code&gt;example.com&lt;/code&gt; 这类二级域由谁权威&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;权威域&lt;/td&gt;
 &lt;td&gt;权威服务器（域名所有者自建或托管）&lt;/td&gt;
 &lt;td&gt;给出本域名的最终记录（A / AAAA / MX …）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;13 台根服务器&amp;rdquo; 是历史限制——受早期 DNS 报文能塞进的 13 个地址约束。今天靠 &lt;strong&gt;anycast&lt;/strong&gt;，这 13 个标签背后是全球几百个站点、几千台机器，任播让请求自动落到最近的一台。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;授权&lt;/strong&gt; 是这套树能扩展开的关键：根只管到 TLD，TLD 只管到二级域，二级域再往下可以继续授权（子域委托）。没有任何一台服务器需要知道全部答案，每台只负责自己那一段、并知道&amp;quot;再往下该问谁&amp;quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三解析流程一次完整查询走了多远"&gt;&lt;a href="#%e4%b8%89%e8%a7%a3%e6%9e%90%e6%b5%81%e7%a8%8b%e4%b8%80%e6%ac%a1%e5%ae%8c%e6%95%b4%e6%9f%a5%e8%af%a2%e8%b5%b0%e4%ba%86%e5%a4%9a%e8%bf%9c" class="header-anchor"&gt;&lt;/a&gt;三、解析流程：一次完整查询走了多远
&lt;/h2&gt;&lt;p&gt;假设浏览器要访问 &lt;code&gt;www.example.com&lt;/code&gt;，完整流程是这样的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;浏览器 / 应用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (1) 需要 www.example.com 的 IP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;本地解析器 (stub resolver，操作系统内置)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 查本机 DNS 缓存 ── 未命中
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hosts 文件 (/etc/hosts 或 C:\Windows\System32\drivers\etc\hosts)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (hosts 优先级高于 DNS，命中则直接返回) ── 未命中
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (2) 把查询转发出去
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;递归解析器 (recursive resolver，通常 ISP 或 8.8.8.8 / 1.1.1.1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 查自身缓存 ── 未命中
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (3) 问根：www.example.com 在哪？ ──▶ 根服务器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── &amp;#34;我不知道，但 .com 归这几台 TLD 管&amp;#34; ──────────── ┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (4) 问 .com TLD：www.example.com 在哪？ ──▶ TLD 服务器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── &amp;#34;我不知道，但 example.com 归这几台权威管&amp;#34; ────── ┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (5) 问 example.com 权威：www 在哪？ ──▶ 权威服务器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀── &amp;#34;www.example.com 的 A 记录是 93.184.216.34&amp;#34; ─── ┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ (6) 把答案返回客户端；递归解析器按 TTL 缓存委派与答案
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;浏览器拿到 IP，开始 TCP 握手 → 发 HTTP 请求
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;几个工程要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;hosts 文件优先&lt;/strong&gt; —— 这就是为什么改 hosts 能&amp;quot;劫持&amp;quot;域名做本地调试，也常被恶意软件篡改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归解析器做了绝大多数工作&lt;/strong&gt; —— 客户端只问它一次，它要自己去跑 (3)(4)(5) 这条链路。常见的 &lt;code&gt;8.8.8.8&lt;/code&gt;（Google）、&lt;code&gt;1.1.1.1&lt;/code&gt;（Cloudflare）、&lt;code&gt;223.5.5.5&lt;/code&gt;（阿里）就是公共递归解析器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存显著减少权威查询&lt;/strong&gt; —— 日常查询往往在浏览器、操作系统或递归解析器命中；递归解析器还会缓存从根 / TLD 获得的委派信息，因此很多查询不必再次访问根服务器。根、TLD、权威服务器并不是把请求逐级转发并各自缓存最终答案的“链路节点”。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="四递归查询-vs-迭代查询"&gt;&lt;a href="#%e5%9b%9b%e9%80%92%e5%bd%92%e6%9f%a5%e8%af%a2-vs-%e8%bf%ad%e4%bb%a3%e6%9f%a5%e8%af%a2" class="header-anchor"&gt;&lt;/a&gt;四、递归查询 vs 迭代查询
&lt;/h2&gt;&lt;p&gt;这两个词在书里经常并列出现，混淆点在于&amp;quot;谁负责追到底&amp;quot;：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;模式&lt;/th&gt;
 &lt;th&gt;谁干活&lt;/th&gt;
 &lt;th&gt;谁返回最终答案&lt;/th&gt;
 &lt;th&gt;在 DNS 里出现在哪&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;递归查询 (recursive)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;被问的人去追完整条链路&lt;/td&gt;
 &lt;td&gt;被问的人&lt;/td&gt;
 &lt;td&gt;客户端 → 递归解析器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;迭代查询 (iterative)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;问的人自己一级级往下追，被问的只给&amp;quot;下一步去问谁&amp;quot;&lt;/td&gt;
 &lt;td&gt;问的人自己&lt;/td&gt;
 &lt;td&gt;递归解析器 → 根/TLD/权威&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;所以一次解析其实是 &lt;strong&gt;&amp;ldquo;客户端对解析器递归，解析器对权威链路迭代&amp;rdquo;&lt;/strong&gt; 的组合。解析器拿到客户端的递归请求后，自己变成那个一级级追问的人（迭代），最终把答案递归地交回给客户端。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一套 DNS 软件甚至同一台机器可以同时承担权威和递归角色，但生产环境通常会分离职责并严格限制递归访问。公网权威服务一般不为任意客户端递归查询；递归解析器则代表获准客户端向权威体系发起非递归查询。若把递归能力无访问控制地暴露到公网，就可能成为开放解析器并被滥用于 DNS 放大攻击。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五dns-记录类型不只是-ip"&gt;&lt;a href="#%e4%ba%94dns-%e8%ae%b0%e5%bd%95%e7%b1%bb%e5%9e%8b%e4%b8%8d%e5%8f%aa%e6%98%af-ip" class="header-anchor"&gt;&lt;/a&gt;五、DNS 记录类型：不只是 IP
&lt;/h2&gt;&lt;p&gt;DNS 存的远不止&amp;quot;域名 → IP&amp;quot;一种映射，而是一张张&lt;strong&gt;资源记录（Resource Record, RR）&lt;/strong&gt;。每条记录都有类型、域名、值和 TTL。常见类型：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;类型&lt;/th&gt;
 &lt;th&gt;全称&lt;/th&gt;
 &lt;th&gt;作用&lt;/th&gt;
 &lt;th&gt;示例&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Address&lt;/td&gt;
 &lt;td&gt;域名 → IPv4 地址&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;www.example.com. IN A 93.184.216.34&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;AAAA&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;IPv6 Address&lt;/td&gt;
 &lt;td&gt;域名 → IPv6 地址（四个 A，因为 IPv6 地址是 IPv4 的四倍长）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;www.example.com. IN AAAA 2606:2800:220:1:...&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;CNAME&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Canonical Name&lt;/td&gt;
 &lt;td&gt;别名，把一个域名指向另一个域名（常用于 CDN / 别名）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;blog.example.com. IN CNAME example.github.io.&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;MX&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Mail Exchange&lt;/td&gt;
 &lt;td&gt;收邮件的服务器，&lt;strong&gt;带优先级&lt;/strong&gt;（数字越小越优先）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;example.com. IN MX 10 mail.example.com.&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;NS&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Name Server&lt;/td&gt;
 &lt;td&gt;这个域由哪台权威服务器管&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;example.com. IN NS ns1.example.com.&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;TXT&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Text&lt;/td&gt;
 &lt;td&gt;任意文本，常用于域名所有权验证、SPF / DKIM / DMARC 防伪造&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;example.com. IN TXT &amp;quot;v=spf1 -all&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;SOA&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Start of Authority&lt;/td&gt;
 &lt;td&gt;一个区的权威元信息：主服务器、管理员邮箱、序列号、刷新/重试/过期时间、最小 TTL&lt;/td&gt;
 &lt;td&gt;每个 zone 必有一条&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;几个容易踩的细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CNAME 不能和其他记录共存&lt;/strong&gt; —— 同一域名要么是别名（CNAME），要么有自己的 A / MX 等记录，不能既要又要；根域（zone apex，如 &lt;code&gt;example.com&lt;/code&gt; 本身）尤其不能用 CNAME，这就是所谓的 &lt;strong&gt;CNAME flattening / ANAME / ALIAS&lt;/strong&gt; 记录要解决的问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MX 的优先级&lt;/strong&gt; —— 数字小者优先。同等优先级的两条 MX，收件方会随机挑，可做简单负载均衡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NS 决定授权边界&lt;/strong&gt; —— 子域一旦在父域里写了 NS 记录，就等于把该子域&lt;strong&gt;委托&lt;/strong&gt; 出去，之后父域不再回答它的细节。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="六缓存与-ttl为什么改了-dns-不立刻生效"&gt;&lt;a href="#%e5%85%ad%e7%bc%93%e5%ad%98%e4%b8%8e-ttl%e4%b8%ba%e4%bb%80%e4%b9%88%e6%94%b9%e4%ba%86-dns-%e4%b8%8d%e7%ab%8b%e5%88%bb%e7%94%9f%e6%95%88" class="header-anchor"&gt;&lt;/a&gt;六、缓存与 TTL：为什么改了 DNS 不立刻生效
&lt;/h2&gt;&lt;p&gt;DNS 能扛住全球规模，缓存功不可没。最终答案可能被浏览器、操作系统和递归解析器缓存；递归解析器还会缓存 NS、A / AAAA 等委派相关信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;浏览器 DNS 缓存 → 操作系统解析器缓存 → （路由器缓存） → 递归解析器缓存 → ……
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每条缓存能留多久，由记录的 &lt;strong&gt;TTL（Time To Live，秒）&lt;/strong&gt; 决定。TTL 是域名所有者在权威服务器上设的&amp;quot;建议保质期&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这就解释了 DNS 改动的一个经典坑：改完记录，总有部分用户还在访问老 IP。&lt;/strong&gt; 因为各级缓存里的老记录要等自己的 TTL 到期才会刷新，而不同用户命中的递归解析器各不相同、缓存剩余时间也不同。所以线上切换 IP 之前，业内惯用做法是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;提前把 TTL 调小&lt;/strong&gt;（比如从 3600s 降到 60s），等一个旧 TTL 周期过去；&lt;/li&gt;
&lt;li&gt;再做 IP 切换，这样最多 60s 内全网收敛；&lt;/li&gt;
&lt;li&gt;切稳之后再把 TTL 调回大值（减少查询压力）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TTL 是个权衡：&lt;strong&gt;大 TTL 省查询、抗 DNS 抖动，但切换慢；小 TTL 切换快，但查询量大、对权威压力高&lt;/strong&gt; 。CDN / 高频切换场景常用几十秒；稳定服务常用小时级。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七安全与隐私dnssecdohdot"&gt;&lt;a href="#%e4%b8%83%e5%ae%89%e5%85%a8%e4%b8%8e%e9%9a%90%e7%a7%81dnssecdohdot" class="header-anchor"&gt;&lt;/a&gt;七、安全与隐私：DNSSEC、DoH、DoT
&lt;/h2&gt;&lt;p&gt;传统 DNS 有两个先天缺陷：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;明文传输&lt;/strong&gt; —— 查询走 UDP 53（或 TCP 53），链路上任何人都能看到你查了什么域名，甚至篡改返回的 IP；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无身份校验&lt;/strong&gt; —— 解析器拿到一个 IP，无法验证它是不是权威真正签发的，中间人塞个假 IP 你也信。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;两套机制分别补这两个洞，&lt;strong&gt;别混为一谈&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;机制&lt;/th&gt;
 &lt;th&gt;解决什么&lt;/th&gt;
 &lt;th&gt;怎么做&lt;/th&gt;
 &lt;th&gt;端口&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;DNSSEC&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;数据&lt;strong&gt;真实性与完整性&lt;/strong&gt;（防篡改、防伪造）&lt;/td&gt;
 &lt;td&gt;对每条记录&lt;strong&gt;加密码学签名&lt;/strong&gt;，解析器沿信任链验证签名&lt;/td&gt;
 &lt;td&gt;53（不改传输）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;DoT&lt;/strong&gt;（DNS over TLS）&lt;/td&gt;
 &lt;td&gt;传输&lt;strong&gt;加密&lt;/strong&gt;（防窃听）&lt;/td&gt;
 &lt;td&gt;用 TLS 包裹 DNS 查询&lt;/td&gt;
 &lt;td&gt;853&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;DoH&lt;/strong&gt;（DNS over HTTPS）&lt;/td&gt;
 &lt;td&gt;传输&lt;strong&gt;加密&lt;/strong&gt; + 混在 HTTPS 流量里（更难与普通 HTTPS 区分）&lt;/td&gt;
 &lt;td&gt;用 HTTPS 传 DNS 报文，可承载于 HTTP/2 或 HTTP/3 等版本&lt;/td&gt;
 &lt;td&gt;443&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;关键区分：&lt;strong&gt;DNSSEC 签的是&amp;quot;数据对不对&amp;quot;，DoT / DoH 加密的是&amp;quot;传的过程看不看得到&amp;quot;&lt;/strong&gt; 。两者正交，可以同时用（DoH + DNSSEC 验证）。DNSSEC 不提供机密性——签名让数据可被验证，但查询内容依旧明文；DoT / DoH 提供机密性，但如果上游解析器作恶，它返回的假 IP 你没法自己验（所以有人坚持要在客户端做 DNSSEC 验证）。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一个现实工程点：用了 DoH / DoT 之后，企业内网的 DNS 过滤、家长的域名黑白名单都会失效——因为查询绕过了本地解析器直接走加密通道去公网解析器了。这是&amp;quot;隐私&amp;quot;和&amp;quot;可控&amp;quot;的天然张力。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="八动手验证dig-与-nslookup"&gt;&lt;a href="#%e5%85%ab%e5%8a%a8%e6%89%8b%e9%aa%8c%e8%af%81dig-%e4%b8%8e-nslookup" class="header-anchor"&gt;&lt;/a&gt;八、动手验证：dig 与 nslookup
&lt;/h2&gt;&lt;p&gt;讲了一堆原理，落到命令上最直观。&lt;code&gt;dig&lt;/code&gt;（Linux/macOS 自带，Windows 可装 BIND 工具）和 &lt;code&gt;nslookup&lt;/code&gt;（全平台自带）是最常用的两个：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 基本查询：看 www.example.com 的 A 记录（dig 默认查 A）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig www.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 指定记录类型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig example.com MX &lt;span class="c1"&gt;# 看邮件服务器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig example.com NS &lt;span class="c1"&gt;# 看权威服务器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig example.com TXT &lt;span class="c1"&gt;# 看 SPF / 验证记录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig example.com AAAA &lt;span class="c1"&gt;# 看 IPv6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 看完整解析过程（+trace 从根开始迭代追问，相当于手动模拟递归解析器）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig +trace www.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 指定用某个解析器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig @8.8.8.8 www.example.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;dig +trace&lt;/code&gt; 是理解第二节那棵树最好的工具——它会依次打印根、TLD、权威的应答，把整条授权链摆给你看。排 DNS 问题时，先用 &lt;code&gt;dig&lt;/code&gt; 看记录对不对，再看是不是缓存（换个解析器 &lt;code&gt;@1.1.1.1&lt;/code&gt; 对比），最后用 &lt;code&gt;+trace&lt;/code&gt; 看授权链有没有配错。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;DNS 是&lt;strong&gt;应用层的分布式数据库&lt;/strong&gt;，按域名分段、层层授权，没有任何一台服务器需要知道全部答案；&lt;/li&gt;
&lt;li&gt;一次解析通常是 &lt;strong&gt;“客户端请求递归解析器、解析器逐级查询权威体系”&lt;/strong&gt; 的组合；客户端、本机和递归解析器可按 TTL 缓存结果，解析器也会缓存委派信息；&lt;/li&gt;
&lt;li&gt;记录类型不只是 A/AAAA——CNAME 别名、MX 带优先级的邮件、NS 决定授权边界、TXT 承载验证信息、SOA 是区的元信息；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改 DNS 不立刻生效&lt;/strong&gt; 是各级 TTL 缓存的必然结果，切换前先调小 TTL 是标准做法；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNSSEC 防篡改（签名），DoT / DoH 防窃听（加密）&lt;/strong&gt; ，两者正交、可叠加。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（十一）：HTTP、HTTPS 与 TLS&lt;/strong&gt; —— 拿到 IP 之后，浏览器怎么建连、发请求、传页面；HTTP 从 1.0 到 3 演进了什么；HTTPS 套的那层 TLS 又是怎么握手的。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc1034" target="_blank" rel="noopener"
 &gt;RFC 1034 — Domain Names: Concepts and Facilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc1035" target="_blank" rel="noopener"
 &gt;RFC 1035 — Domain Names: Implementation and Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions" target="_blank" rel="noopener"
 &gt;DNSSEC — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cloudflare.com/learning/dns/dns-over-tls/" target="_blank" rel="noopener"
 &gt;DNS over TLS vs. DNS over HTTPS — Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://howdns.works/" target="_blank" rel="noopener"
 &gt;How DNS works (交互式图解)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（九）：TCP 进阶——SACK、Nagle、keepalive 与端口复用</title><link>https://www.jiwei.space/posts/networking/tcp-ip/09-tcp-advanced/</link><pubDate>Tue, 02 Jun 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/09-tcp-advanced/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;前两篇讲完了 TCP 的&amp;quot;宏观调控&amp;quot;——连接管理、可靠性、流量控制、拥塞控制。但 TCP 还有一批&lt;strong&gt;很常用、却常被忽略的&amp;quot;细节机制&amp;quot;&lt;/strong&gt; ：它们大多是默认开启或默认关闭的开关，在文档里不起眼，却直接决定了一个连接在高延迟、小包、长连接、服务器重启这些场景下的真实表现。一个 SACK 没开、一个 Nagle 没关、一个端口没复用，可能就是线上延迟毛刺或&amp;quot;Address already in use&amp;quot;的根因。&lt;/p&gt;
&lt;p&gt;本篇逐个讲清这些进阶机制的原理和&amp;quot;什么时候开 / 关&amp;quot;，最后把目光投向正在替代 TCP 的 QUIC 和多路径 MPTCP。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一sack选择确认"&gt;&lt;a href="#%e4%b8%80sack%e9%80%89%e6%8b%a9%e7%a1%ae%e8%ae%a4" class="header-anchor"&gt;&lt;/a&gt;一、SACK：选择确认
&lt;/h2&gt;&lt;h3 id="累积确认的痛点"&gt;&lt;a href="#%e7%b4%af%e7%a7%af%e7%a1%ae%e8%ae%a4%e7%9a%84%e7%97%9b%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;累积确认的痛点
&lt;/h3&gt;&lt;p&gt;TCP 的 ACK 是&lt;strong&gt;累积的&lt;/strong&gt;：ACK 号 N 表示&amp;quot;序号 N 之前的所有数据都收到了&amp;quot;，对 N 之后的乱序数据则只字不提。一个窗口里如果丢了多个段，发送端只看 ACK 根本不知道后面哪些到了、哪些没到，只能保守地&lt;strong&gt;从 N 开始一个个重传&lt;/strong&gt; ——哪怕其中很多段接收端早就收到了。&lt;/p&gt;
&lt;p&gt;举例，发送端发了 5 个段（S1~S5），S2 丢了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;发送: S1 S2 S3 S4 S5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 丢
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;接收: S1 ✗ S3 S4 S5 （S3/S4/S5 乱序到达）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ACK : ACK S2 重复 ACK S2 重复 ACK S2 ... （累积 ACK 一直卡在 S2）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;没有额外信息时，发送端只知道&amp;quot;S2 起还没齐&amp;quot;，&lt;strong&gt;S3/S4/S5 到底到没到无从判断&lt;/strong&gt; ，最坏情况会把 S2~S5 全重传一遍。&lt;/p&gt;
&lt;h3 id="sack-怎么解决"&gt;&lt;a href="#sack-%e6%80%8e%e4%b9%88%e8%a7%a3%e5%86%b3" class="header-anchor"&gt;&lt;/a&gt;SACK 怎么解决
&lt;/h3&gt;&lt;p&gt;SACK（Selective ACKnowledgment，RFC 2018）在 TCP 头部选项里增加一个 SACK 选项，让接收端&lt;strong&gt;额外报告&amp;quot;我这边已经收到的、但不连续的数据块&amp;quot;&lt;/strong&gt; ：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SACK 选项内容示例（接上例）：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 累积 ACK 号 = S2 起点 （S1 已确认，S2 起待确认）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SACK 块 1 = [S3 起点, S6 起点) ← 告诉发送端：S3 S4 S5 我都收到了
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;发送端拿到 SACK 就知道&lt;strong&gt;只有 S2 真的丢了&lt;/strong&gt; ，只重传 S2，S3/S4/S5 不必重发。一个窗口里丢多个段时，SACK 能在一个 RTT 内把它们一次性补齐，而不是像 NewReno 那样每个丢包耗一个 RTT。&lt;/p&gt;
&lt;p&gt;每个 SACK 选项最多能携带 3~4 个不连续块（受 TCP 选项字段 40 字节的限制，开了时间戳后通常是 3 块）。还有个扩展叫 &lt;strong&gt;D-SACK&lt;/strong&gt;（RFC 2883）：接收端可以用它告诉发送端&amp;quot;你重传的那段我之前已经收过了&amp;quot;，帮助发送端区分真丢包和乱序。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;该不该开&lt;/strong&gt;：几乎总是该开。SACK 在现代操作系&lt;strong&gt;默认就是开启的&lt;/strong&gt; （Linux 的 &lt;code&gt;net.ipv4.tcp_sack = 1&lt;/code&gt;），它在有丢包时显著减少不必要的重传。关掉它基本只在和一些极古老设备的兼容场景里才考虑。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二nagle-算法延迟-ack-与-tcp_nodelay--cork"&gt;&lt;a href="#%e4%ba%8cnagle-%e7%ae%97%e6%b3%95%e5%bb%b6%e8%bf%9f-ack-%e4%b8%8e-tcp_nodelay--cork" class="header-anchor"&gt;&lt;/a&gt;二、Nagle 算法、延迟 ACK 与 TCP_NODELAY / CORK
&lt;/h2&gt;&lt;h3 id="nagle-算法小包合并"&gt;&lt;a href="#nagle-%e7%ae%97%e6%b3%95%e5%b0%8f%e5%8c%85%e5%90%88%e5%b9%b6" class="header-anchor"&gt;&lt;/a&gt;Nagle 算法：小包合并
&lt;/h3&gt;&lt;p&gt;很多交互式应用（Telnet、SSH、游戏）会频繁发 1 字节、几字节的小包，每个小包加上 TCP/IP 头有 40+ 字节，开销极大。Nagle 算法（RFC 896）规定：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;如果连接上有&lt;strong&gt;尚未被确认&lt;/strong&gt; 的小段数据在途，就&lt;strong&gt;先别发新的小段&lt;/strong&gt; ，攒着；等要么&amp;quot;前面的数据被 ACK 了&amp;quot;，要么&amp;quot;攒够一个全尺寸段（MSS）&amp;quot;，再一起发。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;效果是把零散小包合并成大包，减少头部开销。多数系统&lt;strong&gt;默认开启 Nagle&lt;/strong&gt; 。&lt;/p&gt;
&lt;h3 id="和延迟-ack-的冲突"&gt;&lt;a href="#%e5%92%8c%e5%bb%b6%e8%bf%9f-ack-%e7%9a%84%e5%86%b2%e7%aa%81" class="header-anchor"&gt;&lt;/a&gt;和延迟 ACK 的冲突
&lt;/h3&gt;&lt;p&gt;延迟 ACK（Delayed ACK，RFC 1122）是接收端的优化：收到数据后不立刻回 ACK，&lt;strong&gt;最多攒两个全尺寸段、或延迟约 200ms 再回&lt;/strong&gt; ，好让 ACK 能捎带反向数据、或合并多个 ACK。&lt;/p&gt;
&lt;p&gt;这两个优化单独看都合理，&lt;strong&gt;叠在一起却会打架&lt;/strong&gt; ，产生著名的&amp;quot;40ms（或更长）毛刺&amp;quot;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;场景：一端连续写入两个小段，第一段尚未被确认
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 发送端 接收端
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── 第一个小段 ────────────→ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ 暂不立即回 ACK（延迟 ACK）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 应用很快又写入第二个小段 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ Nagle 发现本端已有未确认小段 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 暂缓发送第二段 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀──────── 延迟 ACK ───────── │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ── 第二个小段 ──────────────→ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 第二段被额外压住一个延迟 ACK 周期
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;本质是：Nagle 在等待&lt;strong&gt;本端先前已发送数据&lt;/strong&gt;的 ACK，而接收端正按延迟 ACK 策略暂缓这个 ACK；其间本端后续的小段就可能被多压一个延迟 ACK 周期。Nagle 不会等待“本端对对方数据的 ACK”。&lt;/p&gt;
&lt;h3 id="怎么关tcp_nodelay-与-tcp_cork"&gt;&lt;a href="#%e6%80%8e%e4%b9%88%e5%85%b3tcp_nodelay-%e4%b8%8e-tcp_cork" class="header-anchor"&gt;&lt;/a&gt;怎么关：TCP_NODELAY 与 TCP_CORK
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;选项&lt;/th&gt;
 &lt;th&gt;作用&lt;/th&gt;
 &lt;th&gt;何时用&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;TCP_NODELAY&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;关掉 Nagle&lt;/strong&gt; ，小包立即发&lt;/td&gt;
 &lt;td&gt;低延迟、请求-响应、交互式：SSH / RDP / 游戏 / HTTP/2 多路复用 / 数据库短查询，基本都该开&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;TCP_CORK&lt;/code&gt;（Linux）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;反向操作&lt;/strong&gt; ：把连接&amp;quot;塞住&amp;quot;，攒到 MSS 或显式&amp;quot;拔塞&amp;quot;才发&lt;/td&gt;
 &lt;td&gt;批量场景：先写头部、再写 body，希望它们合到一个包发出去，&lt;code&gt;write(headers); write(body);&lt;/code&gt; 后再拔塞&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;经验法则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;低延迟 / 交互场景，开 TCP_NODELAY&lt;/strong&gt; （关掉 Nagle）。代价是多几个小包，但换来可预测的低延迟。绝大多数 RPC 框架、数据库驱动、HTTP 客户端默认就设了 TCP_NODELAY。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大批量传输、希望合并发送的，用 TCP_CORK&lt;/strong&gt; ，并记得发完拔塞。&lt;/li&gt;
&lt;li&gt;不要&amp;quot;既不开 NODELAY 也不开 CORK&amp;quot;地裸跑一个请求-响应协议——Nagle + 延迟 ACK 的毛刺会让你排查到怀疑人生。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="三tcp-keepalive探活死连接"&gt;&lt;a href="#%e4%b8%89tcp-keepalive%e6%8e%a2%e6%b4%bb%e6%ad%bb%e8%bf%9e%e6%8e%a5" class="header-anchor"&gt;&lt;/a&gt;三、TCP keepalive：探活死连接
&lt;/h2&gt;&lt;h3 id="它解决什么问题"&gt;&lt;a href="#%e5%ae%83%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;它解决什么问题
&lt;/h3&gt;&lt;p&gt;连接建立后，如果对端进程崩溃、被 &lt;code&gt;kill -9&lt;/code&gt;、或者网络中间的链路静默断开（没发 FIN/RST），发送端会一直以为连接还在——直到下次写数据时才发现对方已不在，这中间可能空等几个小时甚至无限期。&lt;strong&gt;keepalive&lt;/strong&gt; 就是 TCP 层的&amp;quot;定期探活&amp;quot;：连接空闲超过阈值后，发一个空探测包，看对端还回不回。&lt;/p&gt;
&lt;h3 id="linux-默认参数保守得离谱"&gt;&lt;a href="#linux-%e9%bb%98%e8%ae%a4%e5%8f%82%e6%95%b0%e4%bf%9d%e5%ae%88%e5%be%97%e7%a6%bb%e8%b0%b1" class="header-anchor"&gt;&lt;/a&gt;Linux 默认参数（保守得离谱）
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;参数&lt;/th&gt;
 &lt;th&gt;默认值&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_time&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;7200 秒（2 小时）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;连接空闲多久后开始发第一个探测&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_intvl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;75 秒&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;相邻探测之间的间隔&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tcp_keepalive_probes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;9 次&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;连续多少次探测失败就判定连接死了&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;也就是说，默认情况下一条死连接要等 &lt;strong&gt;2 小时 + 9×75 秒 ≈ 2 小时 11 分&lt;/strong&gt; 才会被 TCP 层发现。对绝大多数应用这个延迟毫无意义。可在 &lt;code&gt;/proc/sys/net/ipv4/&lt;/code&gt; 改全局默认，或对单个 socket 用 &lt;code&gt;setsockopt&lt;/code&gt; + &lt;code&gt;TCP_KEEPIDLE&lt;/code&gt; / &lt;code&gt;TCP_KEEPINTVL&lt;/code&gt; / &lt;code&gt;TCP_KEEPCNT&lt;/code&gt; 单独设。&lt;/p&gt;
&lt;h3 id="该用-tcp-keepalive-还是应用层心跳"&gt;&lt;a href="#%e8%af%a5%e7%94%a8-tcp-keepalive-%e8%bf%98%e6%98%af%e5%ba%94%e7%94%a8%e5%b1%82%e5%bf%83%e8%b7%b3" class="header-anchor"&gt;&lt;/a&gt;该用 TCP keepalive 还是应用层心跳
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;多数长连接服务（数据库连接池、消息队列、RPC 长连接）倾向于自己做应用层心跳&lt;/strong&gt; ，而不是裸依赖 TCP keepalive：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCP keepalive &lt;strong&gt;只能告诉你&amp;quot;TCP 层通不通&amp;quot;&lt;/strong&gt; ，不能告诉你&amp;quot;对端进程是否真的在干活&amp;quot;（比如对方线程死锁了，TCP 连接依然在）。&lt;/li&gt;
&lt;li&gt;应用层心跳（定时 ping / 业务层探活）能穿越 HTTP 代理、负载均衡、NAT 这些&lt;strong&gt;TCP keepalive 看不到的中间层&lt;/strong&gt; ——很多中间设备会独立维护连接状态，TCP 探活可能根本走不到对端。&lt;/li&gt;
&lt;li&gt;心跳间隔可以根据业务设（通常 30s~60s），比 2 小时合理得多。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：长连接、对断连敏感的场景，&lt;strong&gt;开 keepalive 并把参数调小（如 idle=60s、intvl=10s、probes=3），同时配应用层心跳&lt;/strong&gt; 兜底；纯短连接或无所谓的场景可以不管。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四so_reuseaddr--so_reuseport端口复用"&gt;&lt;a href="#%e5%9b%9bso_reuseaddr--so_reuseport%e7%ab%af%e5%8f%a3%e5%a4%8d%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;四、SO_REUSEADDR / SO_REUSEPORT：端口复用
&lt;/h2&gt;&lt;p&gt;排障时人人都见过 &lt;code&gt;Address already in use&lt;/code&gt;——上次服务没退干净、端口还占着。这两个 socket 选项就是解决&amp;quot;端口能不能重新绑&amp;quot;的，但它们&lt;strong&gt;不是一回事&lt;/strong&gt; 。&lt;/p&gt;
&lt;h3 id="so_reuseaddr"&gt;&lt;a href="#so_reuseaddr" class="header-anchor"&gt;&lt;/a&gt;SO_REUSEADDR
&lt;/h3&gt;&lt;p&gt;最经典用途：&lt;strong&gt;允许新 socket 绑定一个仍处于 TIME_WAIT 状态的端口&lt;/strong&gt; 。服务重启时，上一条连接可能还在 2MSL 的 TIME_WAIT 里占着端口，没这个选项就得干等几十秒到两分钟。设了 SO_REUSEADDR 就能立刻 &lt;code&gt;bind&lt;/code&gt; 成功，实现快速重启。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主要解决 TIME_WAIT 占端口的问题。&lt;/li&gt;
&lt;li&gt;在 Linux 上&lt;strong&gt;不允许&lt;/strong&gt; 两个 socket 同时绑定完全相同的 IP:port。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;注意：TIME_WAIT 出现在&lt;strong&gt;主动关闭&lt;/strong&gt; 连接的一方。对服务器而言，通常是客户端主动断；但服务器若主动关闭（如短连接服务端关连接），它的端口也会进 TIME_WAIT。SO_REUSEADDR 让这些端口的复用不被卡住。客户端侧用固定源端口重连时同理受益。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="so_reuseportlinux-392013"&gt;&lt;a href="#so_reuseportlinux-392013" class="header-anchor"&gt;&lt;/a&gt;SO_REUSEPORT（Linux 3.9+，2013）
&lt;/h3&gt;&lt;p&gt;比 SO_REUSEADDR 强得多：&lt;strong&gt;允许多个 socket 同时绑定完全相同的 IP:port&lt;/strong&gt; ，内核把进来的连接在这些 socket 之间&lt;strong&gt;做负载均衡&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;典型用途：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;零停机重启&lt;/strong&gt; ：新进程绑定同一端口、起来接流量后，老进程再优雅退出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多进程 / 多线程监听同一端口&lt;/strong&gt; ：nginx、Envoy 等用多个 worker 各持一个监听 socket，内核把新连接均匀分发，避免&amp;quot;惊群&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SO_REUSEPORT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 进程 A (socket 1) ┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 进程 B (socket 2) ├─ 都 bind 同一个 IP:80 ── 内核对入站连接做负载均衡
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 进程 C (socket 3) ┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;特性&lt;/th&gt;
 &lt;th&gt;SO_REUSEADDR&lt;/th&gt;
 &lt;th&gt;SO_REUSEPORT&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;绑定 TIME_WAIT 端口&lt;/td&gt;
 &lt;td&gt;可以&lt;/td&gt;
 &lt;td&gt;可以&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;同 IP:port 多 socket 共存&lt;/td&gt;
 &lt;td&gt;不行（Linux）&lt;/td&gt;
 &lt;td&gt;可以&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;内核负载均衡&lt;/td&gt;
 &lt;td&gt;无&lt;/td&gt;
 &lt;td&gt;有（Linux）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;可移植性&lt;/td&gt;
 &lt;td&gt;较通用&lt;/td&gt;
 &lt;td&gt;差（Linux / BSD 语义不同，Windows 的 SO_REUSEADDR 行为另类）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一句话：&lt;strong&gt;SO_REUSEADDR 解决&amp;quot;端口占着没法 bind&amp;quot;，SO_REUSEPORT 解决&amp;quot;多个进程同时监听同一端口&amp;quot;&lt;/strong&gt; 。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五展望quic-与-mptcp"&gt;&lt;a href="#%e4%ba%94%e5%b1%95%e6%9c%9bquic-%e4%b8%8e-mptcp" class="header-anchor"&gt;&lt;/a&gt;五、展望：QUIC 与 MPTCP
&lt;/h2&gt;&lt;p&gt;TCP 已经四十多岁了，内核里固化、中间盒识别、握手慢、队头阻塞这些问题根上很难改。两条&amp;quot;另起炉灶&amp;quot;的演进路线值得关注。&lt;/p&gt;
&lt;h3 id="quicudp-之上的现代传输"&gt;&lt;a href="#quicudp-%e4%b9%8b%e4%b8%8a%e7%9a%84%e7%8e%b0%e4%bb%a3%e4%bc%a0%e8%be%93" class="header-anchor"&gt;&lt;/a&gt;QUIC：UDP 之上的现代传输
&lt;/h3&gt;&lt;p&gt;QUIC（RFC 9000，2021）跑在 &lt;strong&gt;UDP&lt;/strong&gt; 之上，把传输层 + TLS 1.3 加密&lt;strong&gt;揉在一起&lt;/strong&gt; 重新设计，是 HTTP/3 的传输基础。它解决了 TCP 的几个老毛病：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;流级队头阻塞&lt;/strong&gt;：HTTP/2 在单条 TCP 上多路复用，一个包丢了会卡住所有流（TCP 不知道流的概念）。QUIC 在&lt;strong&gt;每条流上独立做丢包恢复&lt;/strong&gt; ，一条流丢包不影响其他流。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;握手慢&lt;/strong&gt;：TCP 三次握手 + TLS 握手要 2~3 个 RTT；QUIC 把传输握手和 TLS 握手合并，&lt;strong&gt;首次连接 1-RTT、重连甚至 0-RTT&lt;/strong&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接迁移&lt;/strong&gt;：QUIC 用连接 ID 而非&amp;quot;四元组（IP+端口）&amp;ldquo;标识连接，手机从 Wi-Fi 切到 4G / 5G，IP 变了连接不断。&lt;/li&gt;
&lt;li&gt;拥塞控制、丢包恢复在应用层实现，可以快速迭代（BBR 等算法在 QUIC 栈里更容易铺开）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价：跑在 UDP 上，某些防火墙 / NAT 对 UDP 不友好；CPU 开销比内核态 TCP 高（但在持续优化）。&lt;/p&gt;
&lt;h3 id="mptcp多路径-tcp"&gt;&lt;a href="#mptcp%e5%a4%9a%e8%b7%af%e5%be%84-tcp" class="header-anchor"&gt;&lt;/a&gt;MPTCP：多路径 TCP
&lt;/h3&gt;&lt;p&gt;MPTCP（RFC 8684，2020）让&lt;strong&gt;一条 TCP 连接同时跨越多条物理路径&lt;/strong&gt; ——比如手机同时用 Wi-Fi 和蜂窝网络收发数据。好处是带宽聚合、路径冗余和无缝切换。Linux 内核 5.6（2020）起原生支持。它对上层应用基本透明，但端到端要求两边都支持，部署普及度还有限。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SACK&lt;/strong&gt;（RFC 2018）让接收端报告已收到的乱序数据块，发送端只补丢的、不瞎重传；默认该开。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nagle&lt;/strong&gt; 攒小包、&lt;strong&gt;延迟 ACK&lt;/strong&gt; 压 ACK，两者叠加会产生延迟毛刺；低延迟场景用 &lt;strong&gt;TCP_NODELAY&lt;/strong&gt; 关掉 Nagle，批量合并发送用 &lt;strong&gt;TCP_CORK&lt;/strong&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP keepalive&lt;/strong&gt; 探活死连接，默认 2 小时才开始探测、约 2h11m 才判死，太保守；长连接建议调小参数并配&lt;strong&gt;应用层心跳&lt;/strong&gt; 。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SO_REUSEADDR&lt;/strong&gt; 解决 TIME_WAIT 占端口、&lt;strong&gt;SO_REUSEPORT&lt;/strong&gt;（Linux 3.9+）让多进程同时监听同一端口并内核负载均衡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;QUIC&lt;/strong&gt;（UDP 之上、流级无队头阻塞、1/0-RTT、连接迁移）是 HTTP/3 的传输基础；&lt;strong&gt;MPTCP&lt;/strong&gt; 让一条连接跨多条路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（十）：DNS&lt;/strong&gt; —— 域名是怎么一步步解析成 IP 的，递归 / 迭代查询、层级与缓存、常见记录类型，以及 DoH / DoT / DNSSEC。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc2018" target="_blank" rel="noopener"
 &gt;RFC 2018 — TCP Selective Acknowledgment Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc896" target="_blank" rel="noopener"
 &gt;RFC 896 — Congestion Control in IP/TCP Internetworks（Nagle 算法）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc1122" target="_blank" rel="noopener"
 &gt;RFC 1122 — Requirements for Internet Hosts（Delayed ACK）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc9000" target="_blank" rel="noopener"
 &gt;RFC 9000 — QUIC: A UDP-Based Multiplexed Transport&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc8684" target="_blank" rel="noopener"
 &gt;RFC 8684 — TCP Extensions for Multipath Operation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ" target="_blank" rel="noopener"
 &gt;SO_REUSEADDR vs SO_REUSEPORT — Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（八）：TCP 拥塞控制——从慢启动到 BBR</title><link>https://www.jiwei.space/posts/networking/tcp-ip/08-tcp-congestion-control/</link><pubDate>Sun, 31 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/08-tcp-congestion-control/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇把 TCP 的可靠性机制讲完了：序号与确认保证&amp;quot;发出去的都到了&amp;quot;，超时重传和快重传补上丢失的段，滑动窗口和流量控制让发送方不会淹没接收方。但这里有个被刻意回避的问题：&lt;strong&gt;如果丢包不是因为接收方缓存满了，而是中间网络本身堵了呢&lt;/strong&gt; ？&lt;/p&gt;
&lt;p&gt;流量控制只盯着两端的收发能力，对中间路径一无所知。一个连接如果只顾自己往里灌数据，路由器缓冲区一旦撑满就开始丢包，丢包触发重传，重传又加剧拥堵——这就是 1986 年著名的&amp;quot;互联网拥塞崩溃&amp;quot;（congestion collapse）的成因。本篇就讲 TCP 怎么&lt;strong&gt;主动探测网络的承受能力&lt;/strong&gt; ，也就是拥塞控制（congestion control）的四个经典算法，以及从 Reno 到 BBR 的思路演进。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一流量控制-vs-拥塞控制保护对象不同"&gt;&lt;a href="#%e4%b8%80%e6%b5%81%e9%87%8f%e6%8e%a7%e5%88%b6-vs-%e6%8b%a5%e5%a1%9e%e6%8e%a7%e5%88%b6%e4%bf%9d%e6%8a%a4%e5%af%b9%e8%b1%a1%e4%b8%8d%e5%90%8c" class="header-anchor"&gt;&lt;/a&gt;一、流量控制 vs 拥塞控制：保护对象不同
&lt;/h2&gt;&lt;p&gt;这是最容易混的一对概念，先一张表说清：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;流量控制 Flow Control&lt;/th&gt;
 &lt;th&gt;拥塞控制 Congestion Control&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;保护谁&lt;/td&gt;
 &lt;td&gt;接收端（别撑爆接收缓存）&lt;/td&gt;
 &lt;td&gt;中间网络（别压垮路由器 / 链路）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;信号来自哪&lt;/td&gt;
 &lt;td&gt;接收端通过 ACK 头里的窗口字段（rwnd）告诉发送端&lt;/td&gt;
 &lt;td&gt;发送端自己&lt;strong&gt;猜&lt;/strong&gt;（没人直接告诉你网络堵没堵）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;窗口&lt;/td&gt;
 &lt;td&gt;rwnd（接收窗口）&lt;/td&gt;
 &lt;td&gt;cwnd（拥塞窗口，发送端自己维护）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;关系&lt;/td&gt;
 &lt;td&gt;协议里明文协商&lt;/td&gt;
 &lt;td&gt;纯靠发送端算法估算&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一句话：&lt;strong&gt;流量控制是接收端&amp;quot;明示&amp;quot;你慢一点，拥塞控制是发送端&amp;quot;自觉&amp;quot;慢一点&lt;/strong&gt; 。两者并不互斥，实际能往网络里塞多少数据，取两者的较小值（见下一节）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二两个窗口cwnd-与-rwnd"&gt;&lt;a href="#%e4%ba%8c%e4%b8%a4%e4%b8%aa%e7%aa%97%e5%8f%a3cwnd-%e4%b8%8e-rwnd" class="header-anchor"&gt;&lt;/a&gt;二、两个窗口：cwnd 与 rwnd
&lt;/h2&gt;&lt;p&gt;发送端在任一时刻&amp;quot;在途未确认&amp;quot;的数据量（FlightSize），不能超过两个窗口的较小值：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;实际发送上限 = min(cwnd, rwnd)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cwnd&lt;/strong&gt;（congestion window，拥塞窗口）：发送端自己估算的&amp;quot;网络能吞多少&amp;quot;，是拥塞控制的核心状态变量，动态变化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rwnd&lt;/strong&gt;（receive window，接收窗口）：接收端在 ACK 报文里捎带的&amp;quot;我还能收多少&amp;quot;，上一篇流量控制讲过。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 发送端能发多少？
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌───────────┴───────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cwnd ── 网络承受力 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────── min ──────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rwnd ── 接收端承受力
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;rwnd 是被动接收的（接收端说了算），而 cwnd 完全由发送端的拥塞控制算法自己驱动——下面四个算法就是围绕&amp;quot;cwnd 怎么涨、怎么砍&amp;quot;展开的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三经典四算法慢启动拥塞避免快重传快恢复"&gt;&lt;a href="#%e4%b8%89%e7%bb%8f%e5%85%b8%e5%9b%9b%e7%ae%97%e6%b3%95%e6%85%a2%e5%90%af%e5%8a%a8%e6%8b%a5%e5%a1%9e%e9%81%bf%e5%85%8d%e5%bf%ab%e9%87%8d%e4%bc%a0%e5%bf%ab%e6%81%a2%e5%a4%8d" class="header-anchor"&gt;&lt;/a&gt;三、经典四算法：慢启动、拥塞避免、快重传、快恢复
&lt;/h2&gt;&lt;p&gt;这是 RFC 5681 定义的 TCP 拥塞控制基本盘，几乎所有教科书都按这套讲（Reno / NewReno 实现）。核心是两个变量：&lt;strong&gt;cwnd&lt;/strong&gt; 和 &lt;strong&gt;ssthresh&lt;/strong&gt;（slow start threshold，慢启动阈值）。&lt;/p&gt;
&lt;h3 id="1-慢启动slow-start指数增长"&gt;&lt;a href="#1-%e6%85%a2%e5%90%af%e5%8a%a8slow-start%e6%8c%87%e6%95%b0%e5%a2%9e%e9%95%bf" class="header-anchor"&gt;&lt;/a&gt;1. 慢启动（Slow Start）：指数增长
&lt;/h3&gt;&lt;p&gt;连接刚建立，发送端对网络一无所知，先&amp;quot;小心翼翼地试探&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始 cwnd = IW（Initial Window）。老协议是 1 个 MSS，RFC 6928 把它提到 &lt;strong&gt;IW10 = min(10×MSS, 14600 字节)&lt;/strong&gt; ，现代 Linux 默认就是 IW10。&lt;/li&gt;
&lt;li&gt;每收到一个确认 ACK，cwnd 增加 1 个 MSS。&lt;/li&gt;
&lt;li&gt;一个 RTT 内能收到的 ACK 数 ≈ cwnd / MSS，所以经过一个 RTT 后 cwnd 大约翻倍——&lt;strong&gt;指数增长&lt;/strong&gt; （1 → 2 → 4 → 8 → …）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;ldquo;慢&amp;quot;启动并不慢，它只是&amp;quot;起步慢、加速快&amp;rdquo;。指数增长会很快把网络塞满，所以必须有个刹车——就是 ssthresh：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 cwnd ≥ ssthresh 时，退出慢启动，进入拥塞避免。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-拥塞避免congestion-avoidance线性增长"&gt;&lt;a href="#2-%e6%8b%a5%e5%a1%9e%e9%81%bf%e5%85%8dcongestion-avoidance%e7%ba%bf%e6%80%a7%e5%a2%9e%e9%95%bf" class="header-anchor"&gt;&lt;/a&gt;2. 拥塞避免（Congestion Avoidance）：线性增长
&lt;/h3&gt;&lt;p&gt;进了拥塞避免，发送端认为已经接近网络容量，改成&amp;quot;谨慎试探&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每经过一个 RTT，cwnd 才增加 1 个 MSS（实现上：每个 ACK 让 cwnd 加 SMSS×SMSS/cwnd）。&lt;/li&gt;
&lt;li&gt;也就是 &lt;strong&gt;线性增长&lt;/strong&gt; ，和慢启动的指数增长形成鲜明对比。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么从指数切到线性？因为指数增长以 2 的幂次逼近极限，越往后越容易瞬间冲破网络容量；线性增长让 cwnd 在接近容量时缓慢上探，给网络留缓冲。&lt;/p&gt;
&lt;h3 id="3-快重传fast-retransmit3-个重复-ack"&gt;&lt;a href="#3-%e5%bf%ab%e9%87%8d%e4%bc%a0fast-retransmit3-%e4%b8%aa%e9%87%8d%e5%a4%8d-ack" class="header-anchor"&gt;&lt;/a&gt;3. 快重传（Fast Retransmit）：3 个重复 ACK
&lt;/h3&gt;&lt;p&gt;慢启动和拥塞避免回答&amp;quot;cwnd 怎么涨&amp;quot;，那什么时候砍？最直观的信号是超时（RTO），但等 RTO 太慢（往往要几百毫秒到秒级）。快重传用一个更早的信号：&lt;strong&gt;重复 ACK&lt;/strong&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接收端收到失序的段会立即回一个重复 ACK（ACK 号还是上次那个最大的连续确认号）。&lt;/li&gt;
&lt;li&gt;发送端如果连续收到 &lt;strong&gt;3 个重复 ACK&lt;/strong&gt; ，就判定中间那个段丢了，&lt;strong&gt;不等超时、立即重传&lt;/strong&gt; 丢失的段。&lt;/li&gt;
&lt;li&gt;为什么是 3 个？因为单个重复 ACK 可能只是乱序（网络重排），连收 3 个基本能确认是真的丢了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-快恢复fast-recovery砍半不归零"&gt;&lt;a href="#4-%e5%bf%ab%e6%81%a2%e5%a4%8dfast-recovery%e7%a0%8d%e5%8d%8a%e4%b8%8d%e5%bd%92%e9%9b%b6" class="header-anchor"&gt;&lt;/a&gt;4. 快恢复（Fast Recovery）：砍半不归零
&lt;/h3&gt;&lt;p&gt;快重传之后 cwnd 怎么调？这里有个关键区分，取决于&amp;quot;丢包是哪种信号触发的&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;超时（RTO）&lt;/strong&gt;：认为网络出现了更严重的拥塞信号 → ssthresh 按规范根据 FlightSize 计算，cwnd 降到不超过 &lt;strong&gt;1 SMSS（一个完整报文段）&lt;/strong&gt;，再从慢启动增长。这里不是降到 0，也不等于恢复到现代常见的 IW10。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快重传（3 dup ACK）&lt;/strong&gt;：网络其实还能传（毕竟重复 ACK 说明后面的段都到了），只是局部丢包 → 不必回到慢启动，只把 cwnd 砍半。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;快恢复的具体步骤（Reno）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;收到第 3 个重复 ACK 时：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1. ssthresh = max(cwnd / 2, 2) # 新阈值砍半
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2. 重传那个丢失的段 # 快重传
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 3. cwnd = ssthresh + 3 # 临时抬高 3 个 MSS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #（3 个 dup ACK 意味着有 3 个段已离开网络）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 此后每再收到一个重复 ACK：cwnd += 1 # 窗口膨胀，允许继续发新数据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 直到收到一个新的（非重复）ACK：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cwnd = ssthresh # 膨胀结束，回到阈值
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 进入拥塞避免（线性增长） # 注意：不回慢启动
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这就是&amp;quot;快恢复&amp;quot;的含义：&lt;strong&gt;砍半，但不归零&lt;/strong&gt; ——比超时那条路温和得多。&lt;/p&gt;
&lt;h3 id="ssthresh-的角色"&gt;&lt;a href="#ssthresh-%e7%9a%84%e8%a7%92%e8%89%b2" class="header-anchor"&gt;&lt;/a&gt;ssthresh 的角色
&lt;/h3&gt;&lt;p&gt;ssthresh 是慢启动和拥塞避免的分水岭，本身是动态的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始值通常很大（或设为任意大）。&lt;/li&gt;
&lt;li&gt;每次发生拥塞（快重传或超时）就更新：ssthresh = 当前 cwnd 的一半。&lt;/li&gt;
&lt;li&gt;于是 cwnd 在网络抖动中不断&amp;quot;涨上去 → 砍一半 → 再涨&amp;quot;，ssthresh 像个跟随学习的天花板。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="四cwnd-随时间变化示意图"&gt;&lt;a href="#%e5%9b%9bcwnd-%e9%9a%8f%e6%97%b6%e9%97%b4%e5%8f%98%e5%8c%96%e7%a4%ba%e6%84%8f%e5%9b%be" class="header-anchor"&gt;&lt;/a&gt;四、cwnd 随时间变化示意图
&lt;/h2&gt;&lt;p&gt;把上面几个阶段画在一张图上，cwnd 的典型轨迹长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cwnd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↑
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╮ 收到 3 个重复 ACK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╭─╯ → 快重传 + 快恢复
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╭─╯ ssthresh = cwnd/2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╭─╯ cwnd 落回 ssthresh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 指数增长 ╭─╯ 后继续线性(拥塞避免)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │(慢启动) ╭─╯
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╭─ ← cwnd ≥ ssthresh：转为线性增长
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╭─╯
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╭─╯
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ╭─╯
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─┴──────────────────────────────────→ 时间
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 建立连接
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果是&lt;strong&gt;超时&lt;/strong&gt;而不是快重传，就把图上的“落到 ssthresh”换成“cwnd 落到 1 SMSS，再重新走指数慢启动”。看懂这张图就抓住了经典 Reno 的主线：&lt;strong&gt;指数上探 → 遇阻降低窗口 → 线性再上探&lt;/strong&gt;。两条下降路径里，快重传后的恢复较温和，超时后的窗口收缩更激进，但都不是把 cwnd “归零”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五丢包作为拥塞信号的局限"&gt;&lt;a href="#%e4%ba%94%e4%b8%a2%e5%8c%85%e4%bd%9c%e4%b8%ba%e6%8b%a5%e5%a1%9e%e4%bf%a1%e5%8f%b7%e7%9a%84%e5%b1%80%e9%99%90" class="header-anchor"&gt;&lt;/a&gt;五、丢包作为拥塞信号的局限
&lt;/h2&gt;&lt;p&gt;上面整套 Reno 逻辑，都是把&lt;strong&gt;丢包当作拥塞的唯一信号&lt;/strong&gt; 。这在 1980 年代的网络里成立——路由器缓存小，满了就丢。但在现代网络里这套假设出了三个问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Bufferbloat（缓冲膨胀）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现代路由器 / 交换机缓冲区都很大（为了尽量不丢包）。基于丢包的算法会一直加 cwnd，直到把缓冲区&lt;strong&gt;填满&lt;/strong&gt; 才触发丢包——而这意味着每个包都要在满缓冲区里排上几百毫秒，延迟暴涨。结果就是：&lt;strong&gt;吞吐看着不低，但延迟和抖动高得离谱&lt;/strong&gt; 。这就是著名的 Bufferbloat 问题，对交互应用（SSH、游戏、视频通话）是灾难。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 把随机丢包误判为拥塞&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;无线网络（Wi-Fi、4G / 5G）会因信号波动随机丢包，这根本不是&amp;quot;网络堵了&amp;quot;。但 Reno / Cubic 照样砍 cwnd，导致无线场景下吞吐上不去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 高 BDP 链路利用不足&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;长肥管道（卫星、跨洲骨干）的&amp;quot;带宽 × 延迟&amp;quot;（BDP）很大，基于丢包的算法要&amp;quot;填满&amp;quot;整条管道才能逼近满速，而一旦丢包砍半又要很久爬回来。&lt;/p&gt;
&lt;p&gt;Reno 的直接改进是 &lt;strong&gt;Cubic&lt;/strong&gt;（Linux 2.6.19 起，2006 年至今的默认算法）：把&amp;quot;砍半后线性恢复&amp;quot;换成一条三次函数曲线，恢复更快、对高 BDP 更友好。但&lt;strong&gt;它依然是基于丢包的&lt;/strong&gt; ，根本局限没动。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六bbr基于带宽与-rtt-的模型"&gt;&lt;a href="#%e5%85%adbbr%e5%9f%ba%e4%ba%8e%e5%b8%a6%e5%ae%bd%e4%b8%8e-rtt-%e7%9a%84%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;六、BBR：基于带宽与 RTT 的模型
&lt;/h2&gt;&lt;p&gt;BBR（&lt;strong&gt;B&lt;/strong&gt;ottleneck &lt;strong&gt;B&lt;/strong&gt;andwidth and &lt;strong&gt;R&lt;/strong&gt;Tt）是 Google 在 2016 年提出的拥塞控制算法（Linux 4.9 起可选）。它换了个根本思路：&lt;strong&gt;不把丢包当唯一信号，而是直接建模网络的带宽和延迟&lt;/strong&gt; 。&lt;/p&gt;
&lt;h3 id="核心思路"&gt;&lt;a href="#%e6%a0%b8%e5%bf%83%e6%80%9d%e8%b7%af" class="header-anchor"&gt;&lt;/a&gt;核心思路
&lt;/h3&gt;&lt;p&gt;BBR 持续测量并维护两个核心估计量：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BtlBw&lt;/strong&gt;（Bottleneck Bandwidth）：瓶颈链路的可用带宽——发送端通过观察 ACK 的到达速率（单位时间被确认的数据量）取一个滑动窗口内的最大值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTprop&lt;/strong&gt;（Round-trip propagation time）：链路的最小往返传播时延——在没有排队的情况下测到的 RTT 最小值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者的乘积就是 &lt;strong&gt;BDP&lt;/strong&gt;（Bandwidth-Delay Product），也就是&amp;quot;把管道填满但不溢出&amp;quot;的理想在途数据量：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;理想 cwnd ≈ BtlBw × RTprop = BDP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;BBR 以 BtlBw 和 RTprop 为模型基础，用 **pacing（按速率平滑发送）**和拥塞窗口上限控制在途数据，并通过不同增益阶段周期性探测带宽。它的目标是在维持高吞吐的同时减少不必要的排队，但在途量并不恒等于一个 BDP，也不能保证缓冲区始终“半空”或“近空”。&lt;/p&gt;
&lt;h3 id="和-reno--cubic-的根本区别"&gt;&lt;a href="#%e5%92%8c-reno--cubic-%e7%9a%84%e6%a0%b9%e6%9c%ac%e5%8c%ba%e5%88%ab" class="header-anchor"&gt;&lt;/a&gt;和 Reno / Cubic 的根本区别
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Reno / Cubic（基于丢包）&lt;/th&gt;
 &lt;th&gt;BBR（基于模型）&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;主要控制依据&lt;/td&gt;
 &lt;td&gt;丢包（及 ECN）&lt;/td&gt;
 &lt;td&gt;带宽、RTT 模型；新版本也参考丢包 / ECN&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;发送控制&lt;/td&gt;
 &lt;td&gt;主要调整 cwnd&lt;/td&gt;
 &lt;td&gt;pacing + cwnd 上限 + 周期性探测&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;延迟表现&lt;/td&gt;
 &lt;td&gt;容易在深缓冲路径积累排队&lt;/td&gt;
 &lt;td&gt;目标是减少持续排队，但不保证所有路径都更低延迟&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;随机丢包&lt;/td&gt;
 &lt;td&gt;照砍 cwnd（误判）&lt;/td&gt;
 &lt;td&gt;不轻易砍&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;适用场景&lt;/td&gt;
 &lt;td&gt;传统有线、公平性友好&lt;/td&gt;
 &lt;td&gt;高 BDP、长距、无线、低延迟&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="现状"&gt;&lt;a href="#%e7%8e%b0%e7%8a%b6" class="header-anchor"&gt;&lt;/a&gt;现状
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;BBR 在 Linux 上通过 &lt;code&gt;sysctl net.ipv4.tcp_congestion_control=bbr&lt;/code&gt; 开启，需要内核 ≥ 4.9。&lt;/li&gt;
&lt;li&gt;主流 Linux 发行版的默认仍是 &lt;strong&gt;Cubic&lt;/strong&gt; ，BBR 作为可选；部分对吞吐 / 延迟敏感的场景（CDN、跨洲链路、Google 内部、YouTube）已大规模启用 BBR。&lt;/li&gt;
&lt;li&gt;BBR 自身也在演进（v1、v2、v3），v2 起更注重和 Cubic 的公平性、对丢包 / ECN 做出更克制的反应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话总结 BBR：&lt;strong&gt;与其等&amp;quot;丢包&amp;quot;这个事后信号，不如主动测出网络的真实容量&lt;/strong&gt; 。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;流量控制&lt;/strong&gt; 保护接收端（rwnd），&lt;strong&gt;拥塞控制&lt;/strong&gt; 保护网络（cwnd），实际发送上限 = min(cwnd, rwnd)。&lt;/li&gt;
&lt;li&gt;经典四算法：&lt;strong&gt;慢启动&lt;/strong&gt;（指数增长）→ 越过 ssthresh 进入&lt;strong&gt;拥塞避免&lt;/strong&gt;（线性增长）→ 丢包时&lt;strong&gt;快重传&lt;/strong&gt;（3 个重复 ACK）→ &lt;strong&gt;快恢复&lt;/strong&gt;（窗口明显降低但不回到 1 SMSS）；超时后则把 cwnd 降到不超过 1 SMSS，再回慢启动。&lt;/li&gt;
&lt;li&gt;ssthresh 是慢启动与拥塞避免的分水岭，每次拥塞后动态更新。&lt;/li&gt;
&lt;li&gt;Reno / Cubic 主要依赖丢包（也可结合 ECN）感知拥塞，在 Bufferbloat、随机丢包和高 BDP 场景可能遇到局限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BBR&lt;/strong&gt; 主要用带宽（BtlBw）与最小 RTT（RTprop）建模，通过 pacing 与窗口上限控制发送；它不以丢包作为首要信号，但新版本仍会参考丢包 / ECN，实际效果取决于路径与版本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（九）：TCP 进阶&lt;/strong&gt; —— SACK 选择确认、Nagle 与延迟 ACK 的冲突、keepalive 探活、SO_REUSEADDR / REUSEPORT 端口复用，以及 QUIC 与 MPTCP 简介。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc5681" target="_blank" rel="noopener"
 &gt;RFC 5681 — TCP Congestion Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc6928" target="_blank" rel="noopener"
 &gt;RFC 6928 — Increasing TCP&amp;rsquo;s Initial Window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.ietf.org/archive/id/draft-cardwell-iccrg-bbr-congestion-control-01.html" target="_blank" rel="noopener"
 &gt;BBR Congestion Control — IETF Draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/TCP_congestion_control" target="_blank" rel="noopener"
 &gt;TCP Congestion Control — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/Bufferbloat" target="_blank" rel="noopener"
 &gt;Bufferbloat — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（七）：TCP 可靠性——序号、重传与滑动窗口</title><link>https://www.jiwei.space/posts/networking/tcp-ip/07-tcp-reliability/</link><pubDate>Fri, 29 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/07-tcp-reliability/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇把 TCP 的连接生命周期走了一遍：握手建状态、挥手拆状态、中间一张 11 状态的状态机。但建立连接只是搭好了一座桥，真正的难题在桥上：&lt;strong&gt;IP 层是不可靠的&lt;/strong&gt;——包会丢、会乱序、会重复、会损坏。TCP 怎么在这堆&amp;quot;不靠谱&amp;quot;之上，给应用层交付一条&lt;strong&gt;按顺序、不丢、不重、不出错&lt;/strong&gt;的字节流？&lt;/p&gt;
&lt;p&gt;这一篇就来拆 TCP 最核心的能力——&lt;strong&gt;可靠性&lt;/strong&gt;。它靠三件事撑起来：&lt;strong&gt;给每个字节编号（序号）、收到就回执（确认）、丢了就补（重传）&lt;/strong&gt; 。围绕这三件套，再配上&lt;strong&gt;滑动窗口&lt;/strong&gt;让&amp;quot;等确认&amp;quot;这件事可以批量进行，TCP 才既可靠又不会慢到没法用。&lt;/p&gt;
&lt;p&gt;文末会把&lt;strong&gt;流量控制&lt;/strong&gt;讲清——它是收发两端之间&amp;quot;别发太快压垮我&amp;quot;的协调；和下一篇要讲的&lt;strong&gt;拥塞控制&lt;/strong&gt;（&amp;ldquo;别发太快压垮网络&amp;rdquo;）是两回事，这一篇先把边界划清楚。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一不可靠的-ip-之上如何造出可靠"&gt;&lt;a href="#%e4%b8%80%e4%b8%8d%e5%8f%af%e9%9d%a0%e7%9a%84-ip-%e4%b9%8b%e4%b8%8a%e5%a6%82%e4%bd%95%e9%80%a0%e5%87%ba%e5%8f%af%e9%9d%a0" class="header-anchor"&gt;&lt;/a&gt;一、不可靠的 IP 之上，如何造出可靠
&lt;/h2&gt;&lt;p&gt;IP 层是&amp;quot;尽力而为&amp;quot;（best-effort）的：路由器拥塞了就丢包、链路抖动了就乱序，IP 自己&lt;strong&gt;一概不负责&lt;/strong&gt;。TCP 要做的，就是在应用看来&amp;quot;假装&amp;quot;这些都没发生。它的工具箱只有三件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;序号（Sequence Number）&lt;/strong&gt;：给发送的每个字节编个号，接收端据此&lt;strong&gt;去重、排顺&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确认（Acknowledgment）&lt;/strong&gt;：接收端收到数据后回个 ACK，告诉发送端&amp;quot;我到这里了&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重传（Retransmission）&lt;/strong&gt;：发送端等不到确认就&lt;strong&gt;再发一次&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套思路有个学名：&lt;strong&gt;ARQ（Automatic Repeat reQuest，自动重传请求）&lt;/strong&gt; 。下面先看序号和确认怎么用，再看 ARQ 有哪几种、TCP 选了哪种。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二字节流序号与确认号"&gt;&lt;a href="#%e4%ba%8c%e5%ad%97%e8%8a%82%e6%b5%81%e5%ba%8f%e5%8f%b7%e4%b8%8e%e7%a1%ae%e8%ae%a4%e5%8f%b7" class="header-anchor"&gt;&lt;/a&gt;二、字节流、序号与确认号
&lt;/h2&gt;&lt;p&gt;TCP 传的不是&amp;quot;一条条消息&amp;quot;，而是&lt;strong&gt;连续的字节流&lt;/strong&gt;。序号针对的是&lt;strong&gt;字节&lt;/strong&gt;，不是报文段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送端把自己要发的数据看成一长串字节，&lt;strong&gt;给每个字节一个编号&lt;/strong&gt;；TCP 头里的序号字段，表示&lt;strong&gt;这个报文段里第一个字节的编号&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;初始序号（ISN）不是 0，而是握手时协商的随机值——上一篇文章里那个 &lt;code&gt;seq=x&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举个例子，假设 ISN = 1000：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;发送的字节流： [1000 1001 1002 ... 1023] [1024 ... 2047] [2048 ...]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── 第一个段，seq=1000 ──┘ └ seq=1024 ──┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="确认号累计确认"&gt;&lt;a href="#%e7%a1%ae%e8%ae%a4%e5%8f%b7%e7%b4%af%e8%ae%a1%e7%a1%ae%e8%ae%a4" class="header-anchor"&gt;&lt;/a&gt;确认号：累计确认
&lt;/h3&gt;&lt;p&gt;TCP 的确认号含义是：&lt;strong&gt;我下一个期望收到的字节编号&lt;/strong&gt;。换句话说，&amp;ldquo;这个编号之前的所有字节我都收到了&amp;rdquo;——这叫&lt;strong&gt;累计确认（cumulative ACK）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;延续上例，接收端收到 &lt;code&gt;seq=1000&lt;/code&gt;、长度 1024 字节的段后，回：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ACK, ack = 1000 + 1024 = 2024 # &amp;#34;2024 之前的都收到了，给我 2024&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;累计确认的好处：&lt;strong&gt;中间丢一个，后面的 ACK 照样能回来&lt;/strong&gt;，发送端看确认号就能推断出哪里断了；坏处：接收端&lt;strong&gt;乱序到达&lt;/strong&gt;的段没法直接确认（ACK 还是只回&amp;quot;按顺序连续到哪了&amp;quot;），这部分后面 SACK 再补。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;SYN 和 FIN 虽然不携带用户数据，但&lt;strong&gt;各占一个序号&lt;/strong&gt;（上一篇握手 / 挥手里 &lt;code&gt;ack=x+1&lt;/code&gt;、&lt;code&gt;ack=u+1&lt;/code&gt; 就是这个原因），这样它们也能被确认、能被重传，语义统一。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="三arq-的三种思想停等--回退-n-步--选择重传"&gt;&lt;a href="#%e4%b8%89arq-%e7%9a%84%e4%b8%89%e7%a7%8d%e6%80%9d%e6%83%b3%e5%81%9c%e7%ad%89--%e5%9b%9e%e9%80%80-n-%e6%ad%a5--%e9%80%89%e6%8b%a9%e9%87%8d%e4%bc%a0" class="header-anchor"&gt;&lt;/a&gt;三、ARQ 的三种思想：停等 → 回退 N 步 → 选择重传
&lt;/h2&gt;&lt;p&gt;&amp;ldquo;发一段、等确认、丢了重发&amp;quot;这件事，可以做得粗，也可以做得细。经典的三种思想：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;策略&lt;/th&gt;
 &lt;th&gt;发多少再等&lt;/th&gt;
 &lt;th&gt;丢了怎么办&lt;/th&gt;
 &lt;th&gt;信道利用&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;停等（Stop-and-Wait）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;发 1 段，等 ACK&lt;/td&gt;
 &lt;td&gt;重发那 1 段&lt;/td&gt;
 &lt;td&gt;极低（一来一回只能发一段）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;回退 N 步（GBN）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;一次发 N 段（窗口）&lt;/td&gt;
 &lt;td&gt;从丢失段起，&lt;strong&gt;后面全部重发&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;较高，但重发浪费&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;选择重传（SR）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;一次发 N 段&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;只重发丢失的那段&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;最高，但实现复杂&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;停等&lt;/strong&gt;是教科书的起点，但一次只在途一段，在跨地域高延迟链路上等于把带宽饿死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回退 N 步&lt;/strong&gt;允许&amp;quot;一次在途 N 段&amp;rdquo;，配合累计确认很自然；代价是乱序或丢一段时，&lt;strong&gt;已经发出去但未确认的整批都得重来&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择重传&lt;/strong&gt;只补丢的那段，需要接收端能缓存乱序段、发送端能选择性重发，实现更复杂，要靠 &lt;strong&gt;SACK（Selective ACK，RFC 2018）选项&lt;/strong&gt; 支持。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tcp-选了什么"&gt;&lt;a href="#tcp-%e9%80%89%e4%ba%86%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;TCP 选了什么
&lt;/h3&gt;&lt;p&gt;TCP 的&lt;strong&gt;累计确认&lt;/strong&gt;与 Go-Back-N 有相似之处，但真实 TCP 不能简单归类为标准的 Go-Back-N：超时重传、快速重传、Reno / NewReno 恢复和 SACK 会共同决定补传哪些数据。累计 ACK 只能指出“下一个期望字节”；如果两端协商了 SACK（多数现代系统默认开启），接收端还能报告已经收到的非连续区间，发送端据此更精确地选择补传内容。&lt;/p&gt;
&lt;p&gt;一句话总结：&lt;strong&gt;TCP 是“滑动窗口 + 累计确认 + 快速 / 超时重传 + 可选 SACK”的工程组合&lt;/strong&gt;，不是某种单一 ARQ 模型的纯实现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四重传超时-rto-与-rtt-估算"&gt;&lt;a href="#%e5%9b%9b%e9%87%8d%e4%bc%a0%e8%b6%85%e6%97%b6-rto-%e4%b8%8e-rtt-%e4%bc%b0%e7%ae%97" class="header-anchor"&gt;&lt;/a&gt;四、重传超时 RTO 与 RTT 估算
&lt;/h2&gt;&lt;p&gt;&amp;ldquo;等多久还没收到 ACK 就算丢了&amp;rdquo;——这个阈值叫 &lt;strong&gt;RTO（Retransmission Timeout）&lt;/strong&gt; 。设长了，真丢包时干等、延迟飙升；设短了，包没丢只是慢了就误重传，反而拥塞。理想 RTO 应该&lt;strong&gt;紧跟着网络实际的往返时延 RTT&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="rtt-怎么测rto-怎么算"&gt;&lt;a href="#rtt-%e6%80%8e%e4%b9%88%e6%b5%8brto-%e6%80%8e%e4%b9%88%e7%ae%97" class="header-anchor"&gt;&lt;/a&gt;RTT 怎么测、RTO 怎么算
&lt;/h3&gt;&lt;p&gt;TCP 会持续测量报文段的往返时间（SampleRTT = 收到 ACK 的时刻 − 发送时刻），但单次样本抖动大，所以要平滑。RFC 6298 给的经典做法是两个 EWMA（指数加权移动平均）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EstimatedRTT = ⅞ · EstimatedRTT + ⅛ · SampleRTT # 平滑后的 RTT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DevRTT = ¾ · DevRTT + ¼ · |SampleRTT − EstimatedRTT| # RTT 的抖动幅度
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RTO = EstimatedRTT + 4 · DevRTT # 上界留足余量
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;工程实现里还会给 RTO 加下限（比如至少 1 秒）并加上退避：&lt;strong&gt;每次重传后 RTO 翻倍&lt;/strong&gt;（指数退避），避免重传把拥塞的网络越搞越糟。&lt;/p&gt;
&lt;h3 id="karn-算法重传过的段别用来测-rtt"&gt;&lt;a href="#karn-%e7%ae%97%e6%b3%95%e9%87%8d%e4%bc%a0%e8%bf%87%e7%9a%84%e6%ae%b5%e5%88%ab%e7%94%a8%e6%9d%a5%e6%b5%8b-rtt" class="header-anchor"&gt;&lt;/a&gt;Karn 算法：重传过的段别用来测 RTT
&lt;/h3&gt;&lt;p&gt;这里有个歧义：一段发出后被重传过，最后收到的 ACK 到底是确认&amp;quot;第一次&amp;quot;还是&amp;quot;重传那一次&amp;quot;？分不清就会把 RTT 测错。&lt;strong&gt;Karn 算法&lt;/strong&gt;规定：&lt;strong&gt;重传过的报文段，其 ACK 不参与 RTT 采样&lt;/strong&gt;；同时配合上面的指数退避。简单粗暴但有效。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五滑动窗口让等确认可以批量进行"&gt;&lt;a href="#%e4%ba%94%e6%bb%91%e5%8a%a8%e7%aa%97%e5%8f%a3%e8%ae%a9%e7%ad%89%e7%a1%ae%e8%ae%a4%e5%8f%af%e4%bb%a5%e6%89%b9%e9%87%8f%e8%bf%9b%e8%a1%8c" class="header-anchor"&gt;&lt;/a&gt;五、滑动窗口：让&amp;quot;等确认&amp;quot;可以批量进行
&lt;/h2&gt;&lt;p&gt;如果发一段就停下等一个 ACK（停等），在长肥管道（高带宽 × 高延迟）上吞吐会很惨。TCP 的解法是&lt;strong&gt;滑动窗口&lt;/strong&gt;：允许发送端在&lt;strong&gt;还没收到确认&lt;/strong&gt;的情况下，连续发出一段范围内的数据。这个&amp;quot;范围&amp;quot;就是窗口。&lt;/p&gt;
&lt;h3 id="发送缓冲区的四段划分"&gt;&lt;a href="#%e5%8f%91%e9%80%81%e7%bc%93%e5%86%b2%e5%8c%ba%e7%9a%84%e5%9b%9b%e6%ae%b5%e5%88%92%e5%88%86" class="header-anchor"&gt;&lt;/a&gt;发送缓冲区的四段划分
&lt;/h3&gt;&lt;p&gt;把发送缓冲区从左到右切成四段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 已发送且已确认 │ 已发送未确认（在途） │ 可发送但未发送 │ 超出窗口、暂不可发
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ◀────────────────────────▶│◀──────────────────────────▶│◀──────────────────▶│◀──────────────────▶
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ◀───── 发送窗口 = min(rwnd, cwnd) ─────▶
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;已发送且已确认&lt;/strong&gt;：可以回收缓冲区了；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;已发送未确认&lt;/strong&gt;：还在路上，丢了要重传；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可发送但未发送&lt;/strong&gt;：在窗口内、应用层可以立刻塞进来发；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;超出窗口、暂不可发&lt;/strong&gt;：受窗口限制，得等窗口右移。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;随着 ACK 到来，&lt;strong&gt;左边界（= 已确认位置）右移，整窗口向右&amp;quot;滑动&amp;quot;&lt;/strong&gt;，于是新的字节不断进入&amp;quot;可发送&amp;quot;区——这就是&amp;quot;滑动窗口&amp;quot;名字的由来。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;发送窗口的实际大小 = &lt;code&gt;min(rwnd, cwnd)&lt;/code&gt;。&lt;code&gt;rwnd&lt;/code&gt; 是&lt;strong&gt;接收端告知的接收窗口&lt;/strong&gt;（这一篇的主题，流量控制）；&lt;code&gt;cwnd&lt;/code&gt; 是&lt;strong&gt;发送端自己估的拥塞窗口&lt;/strong&gt;（下一篇的主题，拥塞控制）。两个约束谁小听谁的。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="接收窗口"&gt;&lt;a href="#%e6%8e%a5%e6%94%b6%e7%aa%97%e5%8f%a3" class="header-anchor"&gt;&lt;/a&gt;接收窗口
&lt;/h3&gt;&lt;p&gt;接收端这边也有一个窗口，本质是&amp;quot;&lt;strong&gt;接收缓冲区还剩多少空间&lt;/strong&gt;&amp;quot;。它把这个值写进每个 ACK 的窗口字段回传给发送端——这就是 &lt;strong&gt;rwnd（receive window）&lt;/strong&gt;。发送端据此调整自己的发送窗口，&lt;strong&gt;永远不让在途数据超过接收端能存下的量&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六零窗口与窗口探测"&gt;&lt;a href="#%e5%85%ad%e9%9b%b6%e7%aa%97%e5%8f%a3%e4%b8%8e%e7%aa%97%e5%8f%a3%e6%8e%a2%e6%b5%8b" class="header-anchor"&gt;&lt;/a&gt;六、零窗口与窗口探测
&lt;/h2&gt;&lt;p&gt;接收端处理慢、缓冲区满时，会回一个 &lt;strong&gt;rwnd = 0&lt;/strong&gt; 的 ACK，告诉发送端&amp;quot;&lt;strong&gt;先别发了&lt;/strong&gt;&amp;quot;。发送端就停。&lt;/p&gt;
&lt;p&gt;这里有个死锁风险：接收端后来腾出空间、发了个&amp;quot;窗口更新&amp;quot;的 ACK 想让发送端继续，&lt;strong&gt;但这个 ACK 如果丢了&lt;/strong&gt;，双方就永久卡住——发送端在等&amp;quot;窗口打开&amp;quot;，接收端在等&amp;quot;数据进来&amp;quot;。&lt;/p&gt;
&lt;p&gt;TCP 用 &lt;strong&gt;持久计时器（persist timer）+ 窗口探测（window probe）&lt;/strong&gt; 破局：发送端收到 0 窗口后启动持久计时器，到点就发一个&lt;strong&gt;只含 1 字节数据的探测段&lt;/strong&gt;，逼接收端回一个带最新 rwnd 的 ACK。探测按指数退避反复进行（上限约 60 秒一次），&lt;strong&gt;直到窗口重新打开&lt;/strong&gt;。这样即便窗口更新丢了，连接也不会永久挂死。&lt;/p&gt;
&lt;h3 id="顺带糊涂窗口综合征sws"&gt;&lt;a href="#%e9%a1%ba%e5%b8%a6%e7%b3%8a%e6%b6%82%e7%aa%97%e5%8f%a3%e7%bb%bc%e5%90%88%e5%be%81sws" class="header-anchor"&gt;&lt;/a&gt;顺带：糊涂窗口综合征（SWS）
&lt;/h3&gt;&lt;p&gt;反过来，如果接收端每次只腾出几字节就急着通告、发送端也每次只发几字节，TCP 头的开销就会把有效吞吐吃光——这叫&lt;strong&gt;糊涂窗口综合征（Silly Window Syndrome）&lt;/strong&gt; 。接收端和发送端各有 Clark 算法做规避：接收端在缓冲区没腾出&amp;quot;够大&amp;quot;的空间前，&lt;strong&gt;继续通告 rwnd=0&lt;/strong&gt;；发送端也攒够一段才发。这些细节 TCP 实现都自动处理，应用层一般无感。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七流量控制收发两端之间的限速"&gt;&lt;a href="#%e4%b8%83%e6%b5%81%e9%87%8f%e6%8e%a7%e5%88%b6%e6%94%b6%e5%8f%91%e4%b8%a4%e7%ab%af%e4%b9%8b%e9%97%b4%e7%9a%84%e9%99%90%e9%80%9f" class="header-anchor"&gt;&lt;/a&gt;七、流量控制：收发两端之间的限速
&lt;/h2&gt;&lt;p&gt;把上面几节串起来，&lt;strong&gt;流量控制（flow control）&lt;/strong&gt; 的全貌就清楚了：它是&lt;strong&gt;接收端用来限制发送端&lt;/strong&gt;的机制，目的是&amp;quot;别发得比我处理得快，免得压垮我的缓冲区&amp;quot;。手段就是&lt;strong&gt;滑动窗口 + rwnd&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接收端通过 ACK 的窗口字段&lt;strong&gt;实时通报&lt;/strong&gt;自己还剩多少缓冲；&lt;/li&gt;
&lt;li&gt;发送端据此&lt;strong&gt;调整在途数据上限&lt;/strong&gt;，永远不超过；&lt;/li&gt;
&lt;li&gt;窗口为 0 时发送端停下，靠窗口探测等重新开放。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="流量控制-vs-拥塞控制别搞混"&gt;&lt;a href="#%e6%b5%81%e9%87%8f%e6%8e%a7%e5%88%b6-vs-%e6%8b%a5%e5%a1%9e%e6%8e%a7%e5%88%b6%e5%88%ab%e6%90%9e%e6%b7%b7" class="header-anchor"&gt;&lt;/a&gt;流量控制 vs 拥塞控制：别搞混
&lt;/h3&gt;&lt;p&gt;这俩名字像、都限制发送速率，但&lt;strong&gt;作用对象完全不同&lt;/strong&gt;，是 TCP 窗口那 &lt;code&gt;min(rwnd, cwnd)&lt;/code&gt; 里的两个独立分量：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;流量控制（Flow Control）&lt;/th&gt;
 &lt;th&gt;拥塞控制（Congestion Control）&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;保护谁&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;接收端（别压垮对端缓冲）&lt;/td&gt;
 &lt;td&gt;网络（别压垮路由器 / 链路）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;谁感知&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;接收端（缓冲剩余空间）&lt;/td&gt;
 &lt;td&gt;发送端（靠丢包 / 延迟推断）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;靠什么限速&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;rwnd&lt;/code&gt;（ACK 里带）&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;cwnd&lt;/code&gt;（发送端内部维护）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;作用范围&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;点对点，两个端之间&lt;/td&gt;
 &lt;td&gt;整条路径上的所有连接共享&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一句话：&lt;strong&gt;流量控制是&amp;quot;接收端喊停&amp;quot;，拥塞控制是&amp;quot;发送端自律&amp;quot;&lt;/strong&gt; 。下一篇就专门讲拥塞控制——TCP 怎么在没有网络层任何反馈的情况下，靠&amp;quot;试&amp;quot;和&amp;quot;退&amp;quot;把全网吞吐调到尽量高又不崩溃。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;TCP 的可靠性三板斧是&lt;strong&gt;序号、确认、重传&lt;/strong&gt;；序号针对&lt;strong&gt;字节&lt;/strong&gt;，确认是&lt;strong&gt;累计确认&lt;/strong&gt;（&amp;ldquo;下一个期望的字节&amp;rdquo;），SYN/FIN 各占一个序号。&lt;/li&gt;
&lt;li&gt;ARQ 有三种思想——&lt;strong&gt;停等、回退 N 步、选择重传&lt;/strong&gt;；TCP 是&amp;quot;回退 N 的窗口 + 累计确认 + 快速 / 超时重传 + 可选 SACK&amp;quot;的工程混合体。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTO&lt;/strong&gt; 紧跟 RTT 动态估算（RFC 6298 的 EWMA + 4 倍偏差），重传段不参与采样（Karn 算法），重传后 RTO 指数退避。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;滑动窗口&lt;/strong&gt;让&amp;quot;等确认&amp;quot;可以批量进行：发送窗口 = &lt;code&gt;min(rwnd, cwnd)&lt;/code&gt;，随 ACK 到来向右滑动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零窗口&lt;/strong&gt;靠&lt;strong&gt;持久计时器 + 1 字节窗口探测&lt;/strong&gt;破解死锁；糊涂窗口综合征靠 Clark 算法规避。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流量控制&lt;/strong&gt;（收发之间，靠 rwnd）与&lt;strong&gt;拥塞控制&lt;/strong&gt;（作用于网络，靠 cwnd）是两件事，分别约束窗口的两个分量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（八）：TCP 拥塞控制&lt;/strong&gt; —— 慢启动、拥塞避免、快速重传 / 快速恢复，以及为什么 TCP 能&amp;quot;自发&amp;quot;地把全网的吞吐挤到一个不错的平衡点。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc9293" target="_blank" rel="noopener"
 &gt;RFC 9293: Transmission Control Protocol (TCP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc6298" target="_blank" rel="noopener"
 &gt;RFC 6298: Computing TCP&amp;rsquo;s Retransmission Timer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc2018" target="_blank" rel="noopener"
 &gt;RFC 2018: TCP Selective Acknowledgment Options (SACK)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc6429" target="_blank" rel="noopener"
 &gt;RFC 6429: TCP Sender Clarification for Persist Condition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc7323" target="_blank" rel="noopener"
 &gt;RFC 7323: TCP Extensions for High Performance（时间戳 / PAWS，取代 RFC 1323）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（六）：TCP 连接管理——握手、挥手与状态机</title><link>https://www.jiwei.space/posts/networking/tcp-ip/06-tcp-connection/</link><pubDate>Wed, 27 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/06-tcp-connection/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇我们认识了传输层的两位主角 TCP 与 UDP，也拆解了 TCP 报文段头部里的那些字段——序号、确认号、标志位（SYN/ACK/FIN/RST）、窗口大小……当时说&amp;quot;这些字段怎么用，留到后面讲&amp;quot;，这一篇就来还债。&lt;/p&gt;
&lt;p&gt;TCP 把自己叫作&amp;quot;面向连接&amp;quot;的协议，可这个&amp;quot;连接&amp;quot;到底是什么？它&lt;strong&gt;不是一条物理通路&lt;/strong&gt;，而是通信两端各自维护的一组状态（各自的序号、窗口、计时器）。连接管理要回答的核心问题就两个：&lt;strong&gt;怎么把这组状态建起来，又怎么干净地拆掉&lt;/strong&gt; 。建起来的过程叫握手（三次），拆掉的过程叫挥手（四次），中间还牵出一堆状态和一个让无数后端工程师踩过坑的 &lt;code&gt;TIME_WAIT&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这一篇就把这些一次讲透：握手为什么是三次、挥手为什么是四次、完整的状态机长什么样、&lt;code&gt;TIME_WAIT&lt;/code&gt; 为什么存在，以及一个高频实战问题——客户端短连接打太快把临时端口耗光。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一先说清tcp-的连接是什么"&gt;&lt;a href="#%e4%b8%80%e5%85%88%e8%af%b4%e6%b8%85tcp-%e7%9a%84%e8%bf%9e%e6%8e%a5%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;一、先说清：TCP 的&amp;quot;连接&amp;quot;是什么
&lt;/h2&gt;&lt;p&gt;TCP 的连接不是物理的，而是&lt;strong&gt;逻辑上的状态同步&lt;/strong&gt;。两台主机上的 TCP 实体各自记住一组变量，主要包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对端 IP 与端口（连同自己的，构成四元组）；&lt;/li&gt;
&lt;li&gt;自己的发送序号、对端要确认的下一个序号；&lt;/li&gt;
&lt;li&gt;发送窗口、接收窗口（下一篇讲）；&lt;/li&gt;
&lt;li&gt;一堆计时器（重传、保活、持久……）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一条 TCP 连接&lt;strong&gt;由四元组唯一标识&lt;/strong&gt;：&lt;code&gt;(源 IP, 源端口, 目的 IP, 目的端口)&lt;/code&gt;。理论上同一台机器上只要四元组之一不同，就能并存多条连接——这也是后面理解&amp;quot;端口耗尽&amp;quot;的关键。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;UDP 没有这些建连过程，发完就走，所以叫&amp;quot;无连接&amp;quot;。TCP 要先握手把这些状态协商好（尤其是双方的初始序号），然后才能可靠地收发字节流。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二三次握手为什么是三次"&gt;&lt;a href="#%e4%ba%8c%e4%b8%89%e6%ac%a1%e6%8f%a1%e6%89%8b%e4%b8%ba%e4%bb%80%e4%b9%88%e6%98%af%e4%b8%89%e6%ac%a1" class="header-anchor"&gt;&lt;/a&gt;二、三次握手：为什么是三次
&lt;/h2&gt;&lt;p&gt;建立一条 TCP 连接，客户端和服务端要互相确认两件事：&lt;strong&gt;我能听见你，你也能听见我&lt;/strong&gt;；还要协商好各自的初始序号（ISN）。整个过程三个报文：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 客户端 服务端
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; CLOSED LISTEN
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ─── SYN, seq=x ─────────────────────────▶│ &amp;#34;我想建连，我的 ISN 是 x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ SYN-SENT │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ SYN-RECEIVED
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀──────────── SYN+ACK, seq=y, ack=x+1 ────│ &amp;#34;同意；我的 ISN 是 y，期待 x+1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ─── ACK, seq=x+1, ack=y+1 ──────────────▶│ &amp;#34;收到，期待 y+1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ESTABLISHED │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ESTABLISHED
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;要点（后面要考）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SYN 和 FIN 各&lt;strong&gt;消耗一个序号&lt;/strong&gt;，所以对 SYN 的确认是 &lt;code&gt;ack = x + 1&lt;/code&gt;，而不是 &lt;code&gt;x&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;中间那个报文是 &lt;code&gt;SYN+ACK&lt;/code&gt;（标志位同时置位），把&amp;quot;服务端的 SYN&amp;quot;和&amp;quot;对客户端 SYN 的 ACK&amp;quot;合二为一——这正是省下一次往返的原因；&lt;/li&gt;
&lt;li&gt;握手完成后，双方都进入 &lt;code&gt;ESTABLISHED&lt;/code&gt;，可以双向传数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="为什么不是两次"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e6%98%af%e4%b8%a4%e6%ac%a1" class="header-anchor"&gt;&lt;/a&gt;为什么不是两次
&lt;/h3&gt;&lt;p&gt;这是面试高频题，也是真正理解 TCP 的分水岭。两次握手的意思是：服务端一发完自己的 &lt;code&gt;SYN+ACK&lt;/code&gt; 就直接 &lt;code&gt;ESTABLISHED&lt;/code&gt;，开始等数据。问题有两个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;服务端的初始序号没有得到确认&lt;/strong&gt;。三次握手中，客户端的第三个 ACK 确认了服务端的 SYN 和初始序号。两次握手缺这一步，服务端若直接认定连接建立，就无法确认双方是否已经完成双向序号同步。“确认客户端的接收能力”可以帮助直觉理解，但规范层面的核心是&lt;strong&gt;同步并确认双方的初始序号&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;挡不住历史重复的 SYN&lt;/strong&gt;。网络里会残留延迟报文。设想一个&lt;strong&gt;旧的、早已失效的 SYN&lt;/strong&gt; 晚到服务端，两次握手会让服务端立刻开一条连接干等，直到超时才释放——这就是经典的资源浪费。三次握手里，客户端收到服务端对这个旧 SYN 的回应时，能发现序号对不上（这是给一个早已不存在的连接的确认），于是回一个 RST 拆掉它。&lt;strong&gt;这第三步本质上是一次&amp;quot;双向确认 + 旧报文过滤&amp;quot;&lt;/strong&gt; 。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="为什么不是四次"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e6%98%af%e5%9b%9b%e6%ac%a1" class="header-anchor"&gt;&lt;/a&gt;为什么不是四次
&lt;/h3&gt;&lt;p&gt;因为中间的“服务端 SYN”和“服务端对客户端 SYN 的 ACK”本来就能合并成一个 &lt;code&gt;SYN+ACK&lt;/code&gt; 报文，没必要拆成两个独立包。三次已经足够让双方的 SYN 和初始序号都得到确认，再拆成四次不会增加必要的协议信息。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一句话：&lt;strong&gt;三次握手 = 双方各自发一次 SYN + 各自收到一次对 SYN 的 ACK，其中一方的 SYN 与对另一方的 ACK 合并成了一个报文&lt;/strong&gt; 。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="三四次挥手为什么-ack-和-fin-要分开"&gt;&lt;a href="#%e4%b8%89%e5%9b%9b%e6%ac%a1%e6%8c%a5%e6%89%8b%e4%b8%ba%e4%bb%80%e4%b9%88-ack-%e5%92%8c-fin-%e8%a6%81%e5%88%86%e5%bc%80" class="header-anchor"&gt;&lt;/a&gt;三、四次挥手：为什么 ACK 和 FIN 要分开
&lt;/h2&gt;&lt;p&gt;TCP 是&lt;strong&gt;全双工&lt;/strong&gt;的——双方都能独立地&amp;quot;发完数据&amp;quot;。所以关闭一条连接不能像握手那样一步到位，而要&lt;strong&gt;各自单独关闭自己的发送通道&lt;/strong&gt;。这就是四次：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 主动关闭方 (A) 被动关闭方 (B)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ESTABLISHED ESTABLISHED
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 应用调用 close() │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ─── FIN, seq=u, ack=v ──────────────────▶│ &amp;#34;我没数据要发了&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ FIN-WAIT-1 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ CLOSE-WAIT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀──────────────────── ACK, ack=u+1 ───────│ &amp;#34;收到你的 FIN&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ FIN-WAIT-2 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ...B 还可以继续发剩余数据... │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ◀──────────────────── FIN, seq=w, ack=u+1─│ &amp;#34;我也发完了&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ LAST-ACK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ─── ACK, ack=w+1 ──────────────────────▶│
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ TIME-WAIT (等 2×MSL) │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ CLOSED
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ──(2×MSL 后)──▶ CLOSED
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="为什么中间的-ack-和-fin-不合并"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%ad%e9%97%b4%e7%9a%84-ack-%e5%92%8c-fin-%e4%b8%8d%e5%90%88%e5%b9%b6" class="header-anchor"&gt;&lt;/a&gt;为什么中间的 ACK 和 FIN 不合并
&lt;/h3&gt;&lt;p&gt;因为它们触发于&lt;strong&gt;不同时刻&lt;/strong&gt;。A 发 FIN 表示&amp;quot;A 发完了&amp;quot;；B 收到后&lt;strong&gt;立刻&lt;/strong&gt;回 ACK。但 B 此刻可能&lt;strong&gt;自己还有数据没发完&lt;/strong&gt;——它处于 &lt;code&gt;CLOSE-WAIT&lt;/code&gt;，发送通道还开着。等 B 也把数据发完、自己的应用也调了 close，B 才发自己的 FIN。&lt;/p&gt;
&lt;p&gt;握手时服务端的 SYN 和 ACK 是&amp;quot;同时&amp;quot;要发的（都在握手那一瞬间），可以合并；挥手时 B 的 ACK 是&amp;quot;立刻&amp;quot;发的，而 FIN 要等到 B 应用层决定关闭，&lt;strong&gt;两者通常隔得很远&lt;/strong&gt;，所以分开发。只有当 B 恰好也没有数据要发时，ACK 和 FIN 才可能挤进同一个包——但那是优化，不是常态。&lt;/p&gt;
&lt;h3 id="全双工的半关闭"&gt;&lt;a href="#%e5%85%a8%e5%8f%8c%e5%b7%a5%e7%9a%84%e5%8d%8a%e5%85%b3%e9%97%ad" class="header-anchor"&gt;&lt;/a&gt;全双工的&amp;quot;半关闭&amp;quot;
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;FIN-WAIT-2&lt;/code&gt; 状态下，A 已经关闭了&lt;strong&gt;发送方向&lt;/strong&gt;，但&lt;strong&gt;接收方向还开着&lt;/strong&gt;——B 仍可以继续给 A 推数据，直到 B 也发 FIN。这叫&amp;quot;半关闭&amp;quot;（half-close）。虽然 TCP 协议支持，但应用层很少主动用它，多数实现里一关就双向关。&lt;/p&gt;
&lt;h3 id="三个值得记住的状态"&gt;&lt;a href="#%e4%b8%89%e4%b8%aa%e5%80%bc%e5%be%97%e8%ae%b0%e4%bd%8f%e7%9a%84%e7%8a%b6%e6%80%81" class="header-anchor"&gt;&lt;/a&gt;三个值得记住的状态
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLOSE_WAIT&lt;/code&gt;：&lt;strong&gt;被动关闭方&lt;/strong&gt;收到 FIN、回了 ACK 之后进入。它表示&amp;quot;对端不再发了，但我可能还要把缓冲区里剩下的数据发完，然后等我的应用层调 close&amp;quot;。如果应用层迟迟不关 socket，连接就会&lt;strong&gt;长期停在 &lt;code&gt;CLOSE_WAIT&lt;/code&gt;&lt;/strong&gt;——这是排查应用层 bug（忘了关连接、句柄泄漏）时的常见信号。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LAST_ACK&lt;/code&gt;：被动关闭方发完自己的 FIN 后进入，等最后一个 ACK。ACK 一到就 &lt;code&gt;CLOSED&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLOSING&lt;/code&gt;：少见。当 A 发完 FIN 进入 &lt;code&gt;FIN-WAIT-1&lt;/code&gt;，&lt;strong&gt;没等到对端的 ACK，却先等到了对端的 FIN&lt;/strong&gt;（两边几乎同时主动关闭）时，A 跳过 &lt;code&gt;FIN-WAIT-2&lt;/code&gt; 直接进 &lt;code&gt;CLOSING&lt;/code&gt;，再收到 ACK 后进 &lt;code&gt;TIME-WAIT&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="四完整-tcp-状态机11-个状态"&gt;&lt;a href="#%e5%9b%9b%e5%ae%8c%e6%95%b4-tcp-%e7%8a%b6%e6%80%81%e6%9c%ba11-%e4%b8%aa%e7%8a%b6%e6%80%81" class="header-anchor"&gt;&lt;/a&gt;四、完整 TCP 状态机：11 个状态
&lt;/h2&gt;&lt;p&gt;把握手和挥手的状态串起来，就是那张著名的 TCP 状态转换图。RFC 793（现已被 RFC 9293 取代）里画的就是它，一共 &lt;strong&gt;11 个状态&lt;/strong&gt;（含概念上的初始 / 终态 &lt;code&gt;CLOSED&lt;/code&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 被动打开 主动打开 (connect)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌─────────┐ 收 SYN / 发 SYN+ACK ┌─────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌─────│ LISTEN │◀──────────────────────│ CLOSED │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └────┬────┘ └────┬────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 收 SYN │ 发 SYN │ 发 SYN
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 发 SYN+ACK │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ┌──────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌──────────────┐ ┌─────│ SYN-SENT │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ SYN-RECEIVED │ ◀─── 收 SYN / 发 SYN+ACK ─│ └────┬─────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────┬───────┘ 收 SYN+ACK / 发 ACK │ │ 收 SYN+ACK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 收 ACK │ │ 发 ACK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────────────▶ ESTABLISHED ◀────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 主动关闭 / 发 FIN │ 收 FIN / 发 ACK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌───────────┐ ┌───────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ FIN-WAIT-1│ │ CLOSE-WAIT│
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─────┬─────┘ └─────┬─────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 收 ACK │ │ close() / 发 FIN
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ │ 收 FIN+ACK ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌──────────┐ │ ┌──────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │FIN-WAIT-2│ │ │ LAST-ACK │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └────┬─────┘ │ └────┬─────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 收 FIN│ / 发 ACK │ │ 收 ACK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ │ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌──────────┐ │ ┌────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │TIME-WAIT │ │ │ CLOSED │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └────┬─────┘ │ └────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2×MSL│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ CLOSED │◀─────┘ (FIN-WAIT-1 收到对端 FIN/ACK → CLOSING → 收 ACK → TIME-WAIT)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;各状态一览（按出现顺序）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;状态&lt;/th&gt;
 &lt;th&gt;谁会进入&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CLOSED&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;起点 / 终点&lt;/td&gt;
 &lt;td&gt;虚构状态，连接不存在&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;LISTEN&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;服务端&lt;/td&gt;
 &lt;td&gt;等待连接请求&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYN-SENT&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;客户端&lt;/td&gt;
 &lt;td&gt;发了 SYN，等 SYN+ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYN-RECEIVED&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;服务端&lt;/td&gt;
 &lt;td&gt;收到 SYN 并回了 SYN+ACK，等最后 ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ESTABLISHED&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;双方&lt;/td&gt;
 &lt;td&gt;数据传输中&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;FIN-WAIT-1&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;主动关闭方&lt;/td&gt;
 &lt;td&gt;发了 FIN，等 ACK 或对端 FIN&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;FIN-WAIT-2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;主动关闭方&lt;/td&gt;
 &lt;td&gt;收到对端 ACK，等对端 FIN（半关闭）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CLOSE-WAIT&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;被动关闭方&lt;/td&gt;
 &lt;td&gt;收到对端 FIN，等本端应用 close&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CLOSING&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;主动关闭方&lt;/td&gt;
 &lt;td&gt;两边同时关闭的特例&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;LAST-ACK&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;被动关闭方&lt;/td&gt;
 &lt;td&gt;发了 FIN，等最后一个 ACK&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;TIME-WAIT&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;主动关闭方&lt;/td&gt;
 &lt;td&gt;等待 2×MSL，确保旧报文消亡&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;排查线上连接问题，&lt;code&gt;netstat -an&lt;/code&gt; 或 &lt;code&gt;ss -tan&lt;/code&gt; 看到的就是这些状态名。下一篇讲可靠性时还会回到这张图——&lt;strong&gt;建立连接是序号协商的起点，拆连接是收尾的边界&lt;/strong&gt; 。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五time_wait-详解那个让运维失眠的状态"&gt;&lt;a href="#%e4%ba%94time_wait-%e8%af%a6%e8%a7%a3%e9%82%a3%e4%b8%aa%e8%ae%a9%e8%bf%90%e7%bb%b4%e5%a4%b1%e7%9c%a0%e7%9a%84%e7%8a%b6%e6%80%81" class="header-anchor"&gt;&lt;/a&gt;五、TIME_WAIT 详解：那个让运维失眠的状态
&lt;/h2&gt;&lt;p&gt;正常关闭时，&lt;code&gt;TIME_WAIT&lt;/code&gt; 通常由&lt;strong&gt;主动关闭连接的一方&lt;/strong&gt;承担，持续时间 &lt;strong&gt;2×MSL&lt;/strong&gt;（Maximum Segment Lifetime，报文最大生存时间）；如果双方同时关闭，两端都可能进入 &lt;code&gt;TIME_WAIT&lt;/code&gt;。RFC 9293 沿用 2 分钟的规范 MSL 取值，因此规范示例中的 2×MSL 是 4 分钟；Linux 实现通常约 &lt;strong&gt;60 秒&lt;/strong&gt;，不能直接通过普通 sysctl 调整。&lt;/p&gt;
&lt;p&gt;为什么必须是主动关闭方、必须等 2×MSL？有两个独立的原因，缺一不可。&lt;/p&gt;
&lt;h3 id="原因一让本连接的旧报文彻底消亡"&gt;&lt;a href="#%e5%8e%9f%e5%9b%a0%e4%b8%80%e8%ae%a9%e6%9c%ac%e8%bf%9e%e6%8e%a5%e7%9a%84%e6%97%a7%e6%8a%a5%e6%96%87%e5%bd%bb%e5%ba%95%e6%b6%88%e4%ba%a1" class="header-anchor"&gt;&lt;/a&gt;原因一：让本连接的旧报文彻底消亡
&lt;/h3&gt;&lt;p&gt;网络里会残留延迟或重复的报文。如果 &lt;code&gt;TIME_WAIT&lt;/code&gt; 一结束、四元组立刻被复用到一条&lt;strong&gt;新的、碰巧同四元组的连接&lt;/strong&gt;，上一个连接里&amp;quot;还在路上&amp;quot;的旧报文就可能被新连接错误接收，造成数据错乱。2×MSL 是一个保守上界：一个报文最多在网络里活 1 个 MSL，加上可能的、由对端重发的方向再 1 个 MSL，合起来 2×MSL 之后，这条四元组相关的旧报文就一定已经消失。&lt;/p&gt;
&lt;h3 id="原因二保证最后一个-ack-可达"&gt;&lt;a href="#%e5%8e%9f%e5%9b%a0%e4%ba%8c%e4%bf%9d%e8%af%81%e6%9c%80%e5%90%8e%e4%b8%80%e4%b8%aa-ack-%e5%8f%af%e8%be%be" class="header-anchor"&gt;&lt;/a&gt;原因二：保证最后一个 ACK 可达
&lt;/h3&gt;&lt;p&gt;主动关闭方发的最后那个 ACK（对被动方的 FIN 的确认）可能丢。如果丢了，被动方在 &lt;code&gt;LAST-ACK&lt;/code&gt; 等不到确认，会&lt;strong&gt;重发 FIN&lt;/strong&gt;；这时主动方还活着（在 &lt;code&gt;TIME_WAIT&lt;/code&gt;），就能再回一个 ACK，让被动方正常进入 &lt;code&gt;CLOSED&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果没有 &lt;code&gt;TIME_WAIT&lt;/code&gt;，主动方一发完最后一个 ACK 就直接 &lt;code&gt;CLOSED&lt;/code&gt;，被动方一旦没收到这个 ACK、重发 FIN 过来，会收到一个 RST——被动方只能以&amp;quot;异常方式&amp;quot;关闭，连接收尾不可靠。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;这两个原因合起来就是为什么 RFC 反复强调 &lt;code&gt;TIME_WAIT&lt;/code&gt; 是 TCP 健壮性的一部分，&lt;strong&gt;不是可以随便关掉的&amp;quot;包袱&amp;quot;&lt;/strong&gt; 。它由主动关闭方承担，这是协议天然的设计代价。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="六实战客户端高频短连接--time_wait-堆积--端口耗尽"&gt;&lt;a href="#%e5%85%ad%e5%ae%9e%e6%88%98%e5%ae%a2%e6%88%b7%e7%ab%af%e9%ab%98%e9%a2%91%e7%9f%ad%e8%bf%9e%e6%8e%a5--time_wait-%e5%a0%86%e7%a7%af--%e7%ab%af%e5%8f%a3%e8%80%97%e5%b0%bd" class="header-anchor"&gt;&lt;/a&gt;六、实战：客户端高频短连接 → TIME_WAIT 堆积 → 端口耗尽
&lt;/h2&gt;&lt;p&gt;理论说完了，来看一个真实坑。场景：一个 .NET / Java 服务作为客户端，&lt;strong&gt;高频地对下游发起短连接&lt;/strong&gt;——典型的就是轮询、或每次请求都 &lt;code&gt;new HttpClient()&lt;/code&gt; 而不复用。每条连接用完就关，下游一般也会被动关，但&lt;strong&gt;很多情况下客户端是先关的那一方&lt;/strong&gt;（请求方发完数据就 close）。&lt;/p&gt;
&lt;p&gt;于是：每条连接可能在客户端留下一个 &lt;code&gt;TIME_WAIT&lt;/code&gt;，暂时保留对应的连接四元组，其中包含一个&lt;strong&gt;临时端口（ephemeral port）&lt;/strong&gt;。连接虽然关闭了，但同一四元组不能立即无条件复用。&lt;/p&gt;
&lt;h3 id="数字一算就明白了"&gt;&lt;a href="#%e6%95%b0%e5%ad%97%e4%b8%80%e7%ae%97%e5%b0%b1%e6%98%8e%e7%99%bd%e4%ba%86" class="header-anchor"&gt;&lt;/a&gt;数字一算就明白了
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Linux 临时端口范围默认 &lt;code&gt;32768–60999&lt;/code&gt;，约 &lt;strong&gt;28000 个&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIME_WAIT&lt;/code&gt; 约 60 秒；&lt;/li&gt;
&lt;li&gt;如果只有一个本地源地址，并持续连接&lt;strong&gt;同一个目的 IP:端口&lt;/strong&gt;，&lt;code&gt;28000 / 60 ≈ 470 次/秒&lt;/code&gt; 可以作为非常粗的最坏情况量级估算。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这不是客户端的总建连上限：TCP 由四元组区分，同一临时端口可以用于不同目的端点，多本地地址、IPv6 和内核的安全复用策略也会改变结果。在上述受限场景中，如果建连速率持续超过可用四元组的回收速度，新连接可能报：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;connect: Cannot assign requested address (EADDRNOTAVAIL)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;更阴的是：&lt;strong&gt;即便流量回落，之前堆积的 &lt;code&gt;TIME_WAIT&lt;/code&gt; 也得等满 60 秒才陆续释放&lt;/strong&gt;，这期间新连接仍然连不上——这就是&amp;quot;恢复后也连不上&amp;quot;的现象。&lt;/p&gt;
&lt;h3 id="怎么治"&gt;&lt;a href="#%e6%80%8e%e4%b9%88%e6%b2%bb" class="header-anchor"&gt;&lt;/a&gt;怎么治
&lt;/h3&gt;&lt;p&gt;按&amp;quot;治本 → 治标&amp;quot;排序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连接复用（治本）&lt;/strong&gt;。短连接的本质问题是&amp;quot;用一次就建一次、关一次&amp;quot;。改成&lt;strong&gt;长连接 + 连接池&lt;/strong&gt;，让一条 TCP 连接承载多次请求，&lt;code&gt;TIME_WAIT&lt;/code&gt; 自然不再堆积。.NET 里就是用单例 &lt;code&gt;HttpClient&lt;/code&gt; 或 &lt;code&gt;IHttpClientFactory&lt;/code&gt; 管理的 &lt;code&gt;HttpClient&lt;/code&gt;——这个问题在&lt;a class="link" href="https://www.jiwei.space/posts/dotnet/httpclient-deep-dive/" &gt;《HttpClient 的前世今生》&lt;/a&gt;里讲得很透，连&amp;quot;socket 耗尽&amp;quot;现象都是同一个根因的两种表现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;合理设计关闭方，但别只为转移状态而改协议&lt;/strong&gt;。正常关闭时谁主动关，谁通常承担 &lt;code&gt;TIME_WAIT&lt;/code&gt;；HTTP keep-alive 连接也常由服务端在空闲超时后关闭。不过，刻意把关闭责任推给下游只是转移资源压力，还可能破坏连接复用和协议语义，必须结合两端容量与业务行为设计。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;谨慎评估 &lt;code&gt;net.ipv4.tcp_tw_reuse&lt;/code&gt;（治标）&lt;/strong&gt;。当前 Linux 文档定义 &lt;code&gt;0&lt;/code&gt; 为禁用、&lt;code&gt;1&lt;/code&gt; 为全局启用、&lt;code&gt;2&lt;/code&gt; 为仅 loopback 流量启用，默认值是 &lt;code&gt;2&lt;/code&gt;。它允许内核在协议判定安全时为新连接复用 TIME-WAIT socket，但官方明确提示不要在缺少专家评估时修改。不要把它当成通用推荐，更不能代替连接池和长连接。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;扩大临时端口范围&lt;/strong&gt;：&lt;code&gt;net.ipv4.ip_local_port_range = 10000 65535&lt;/code&gt; 之类，把池子做大，单纯把阈值推高，没解决根因。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;别用 &lt;code&gt;tcp_tw_recycle&lt;/code&gt;&lt;/strong&gt;。这个开关在 Linux 4.12 已经被&lt;strong&gt;彻底移除&lt;/strong&gt;——它在 NAT / 负载均衡环境下会基于时间戳丢弃合法连接，害处远大于收益。网上老文章还在推荐，别信。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

 &lt;blockquote&gt;
 &lt;p&gt;经验法则：看到 &lt;code&gt;TIME_WAIT&lt;/code&gt; 多先别慌，&lt;strong&gt;它是 TCP 的正常行为&lt;/strong&gt;，量级到几万也不一定有问题。真正要看的是&lt;strong&gt;是否伴随着端口耗尽 / 建连失败&lt;/strong&gt;——有才治，没有就让协议自己跑。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="七syn-flood-与-syn-cookies"&gt;&lt;a href="#%e4%b8%83syn-flood-%e4%b8%8e-syn-cookies" class="header-anchor"&gt;&lt;/a&gt;七、SYN flood 与 SYN cookies
&lt;/h2&gt;&lt;p&gt;握手的状态机里有个软肋：服务端收到 SYN 就要分配资源、进入 &lt;code&gt;SYN-RECEIVED&lt;/code&gt;，等第三个 ACK。攻击者只要&lt;strong&gt;发海量伪造源 IP 的 SYN、但永远不发第三个 ACK&lt;/strong&gt;，服务端的半连接队列（SYN backlog）就会被占满，正常用户反而连不进来——这就是 SYN flood。&lt;/p&gt;
&lt;h3 id="syn-cookies无状态地扛过去"&gt;&lt;a href="#syn-cookies%e6%97%a0%e7%8a%b6%e6%80%81%e5%9c%b0%e6%89%9b%e8%bf%87%e5%8e%bb" class="header-anchor"&gt;&lt;/a&gt;SYN cookies：无状态地扛过去
&lt;/h3&gt;&lt;p&gt;防御思路是 Bernstein 提出的 &lt;strong&gt;SYN cookies&lt;/strong&gt;：收到 SYN 时&lt;strong&gt;先不分配任何状态&lt;/strong&gt;，而是把&amp;quot;这条连接的身份信息&amp;quot;用一个服务端秘钥做 MAC，&lt;strong&gt;编码进 SYN+ACK 的初始序号&lt;/strong&gt;里发回去。服务端什么也不记，直接忘掉这条连接。&lt;/p&gt;
&lt;p&gt;当（真正的）客户端回第三个 ACK 时，它的确认号 = 之前的序号 + 1，服务端从里面&lt;strong&gt;反解并校验 MAC&lt;/strong&gt;：合法就认、重建连接状态；伪造源 IP 的攻击者根本收不到 SYN+ACK，自然也回不出合法 ACK。&lt;/p&gt;
&lt;p&gt;代价是 Cookie 可编码的状态空间有限，一些 TCP 选项可能无法完整保留；现代 Linux 可借助时间戳等机制保存更多协商信息，具体限制取决于内核实现。Linux 通常默认 &lt;code&gt;tcp_syncookies=1&lt;/code&gt;，在 SYN 队列溢出时启用。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;SYN flood 是典型的&amp;quot;利用协议状态机的资源占用做放大&amp;quot;的攻击。同理，让主动关闭方堆积 &lt;code&gt;TIME_WAIT&lt;/code&gt; 也可以是一种&amp;quot;被动 DoS&amp;quot;——这也是上一节强调复用连接的另一个理由。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;三次握手&lt;/strong&gt;让双方同步并确认初始序号，也能正确处理历史重复 SYN；&lt;strong&gt;不是两次&lt;/strong&gt;（服务端 SYN 尚未被确认）也&lt;strong&gt;不是四次&lt;/strong&gt;（中间 SYN 和 ACK 可合并）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;四次挥手&lt;/strong&gt;源于 TCP 的全双工：每一方都要单独关闭自己的发送方向，中间的 ACK 与 FIN 因时刻不同通常无法合并。&lt;/li&gt;
&lt;li&gt;TCP 有 &lt;strong&gt;11 个状态&lt;/strong&gt;，串成那张经典状态机；&lt;code&gt;ESTABLISHED&lt;/code&gt; 是中点，两侧分别是建立与拆除路径。&lt;/li&gt;
&lt;li&gt;正常关闭时 &lt;strong&gt;&lt;code&gt;TIME_WAIT&lt;/code&gt; 通常由主动关闭方承担&lt;/strong&gt;，同时关闭时两端都可能进入；它持续 &lt;strong&gt;2×MSL&lt;/strong&gt;，目的是&lt;strong&gt;隔离旧报文&lt;/strong&gt;并允许重发最后的 ACK——是 TCP 健壮性的代价，不是包袱。&lt;/li&gt;
&lt;li&gt;客户端&lt;strong&gt;高频短连接&lt;/strong&gt;会把 &lt;code&gt;TIME_WAIT&lt;/code&gt; 堆到临时端口耗尽（&lt;code&gt;EADDRNOTAVAIL&lt;/code&gt;），治本是&lt;strong&gt;连接复用&lt;/strong&gt;，治标是 &lt;code&gt;tcp_tw_reuse&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN flood&lt;/strong&gt; 借握手占用半连接资源，&lt;strong&gt;SYN cookies&lt;/strong&gt; 用&amp;quot;序号里编码 MAC&amp;quot;实现无状态握手来扛。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（七）：TCP 可靠性——序号、重传与滑动窗口&lt;/strong&gt; —— 连接建好之后，TCP 到底怎么在会丢包、乱序、重复的 IP 之上，拼出一条不出错的字节流。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc9293" target="_blank" rel="noopener"
 &gt;RFC 9293: Transmission Control Protocol (TCP)&lt;/a&gt;（取代 RFC 793，现行 TCP 规范）&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc793" target="_blank" rel="noopener"
 &gt;RFC 793: Transmission Control Protocol&lt;/a&gt;（经典，状态机原图出处）&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc4987" target="_blank" rel="noopener"
 &gt;RFC 4987: TCP SYN Flooding Attacks and Common Mitigations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://cr.yp.to/syncookies.html" target="_blank" rel="noopener"
 &gt;SYN cookies — D. J. Bernstein&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux" target="_blank" rel="noopener"
 &gt;Coping with the TCP TIME-WAIT state on busy Linux servers — Vincent Bernat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.jiwei.space/posts/dotnet/httpclient-deep-dive/" &gt;HttpClient 的前世今生（本站）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（五）：传输层与 UDP——端口与无连接</title><link>https://www.jiwei.space/posts/networking/tcp-ip/05-transport-udp/</link><pubDate>Mon, 25 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/05-transport-udp/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇把网络层的 IP 地址、子网、路由和 ICMP 讲透了。但有个问题一直没回答：IP 只能把包送到&lt;strong&gt;某台主机&lt;/strong&gt;，而一台机器上同时跑着浏览器、SSH、数据库几十个进程，一个进来的 IP 包到底该交给谁？&lt;/p&gt;
&lt;p&gt;这个&amp;quot;交给谁&amp;quot;的问题，正是&lt;strong&gt;传输层&lt;/strong&gt;要解决的。这一篇先讲传输层的职责和它的核心概念&lt;strong&gt;端口号&lt;/strong&gt;，再展开两个传输层协议中较简单的那个——&lt;strong&gt;UDP&lt;/strong&gt; ：它为什么这么轻、靠什么校验、适合什么场景，并和 TCP 做一次选型对比。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一传输层从主机到主机到进程到进程"&gt;&lt;a href="#%e4%b8%80%e4%bc%a0%e8%be%93%e5%b1%82%e4%bb%8e%e4%b8%bb%e6%9c%ba%e5%88%b0%e4%b8%bb%e6%9c%ba%e5%88%b0%e8%bf%9b%e7%a8%8b%e5%88%b0%e8%bf%9b%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;一、传输层：从&amp;quot;主机到主机&amp;quot;到&amp;quot;进程到进程&amp;quot;
&lt;/h2&gt;&lt;p&gt;网络层（IP）解决的是&lt;strong&gt;主机到主机&lt;/strong&gt;：路由器逐跳转发，把包送到目的 IP 那台机器。但它不关心机器内部。传输层在网络层之上，做两件 IP 做不了的事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进程到进程（端到端）的分发&lt;/strong&gt; —— 一台主机上多个进程共用一个 IP，传输层靠&lt;strong&gt;端口号&lt;/strong&gt;区分，把数据准确交给目标进程。这个&amp;quot;多路分用（demultiplexing）&amp;ldquo;是传输层最根本的职责；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;端到端的可靠性与流量控制（可选）&lt;/strong&gt; —— IP 是无连接、不可靠的，要不要重传、要不要按序、要不要限速，由传输层协议决定（TCP 提供，UDP 不提供）。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;发送端进程 接收端进程
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP(80) HTTP(80)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SSH(22) ┌──── 传输层 ────┐ SSH(22)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; DNS(53) ──► │ 按端口号分发 │ ──► DNS(53)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▲
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 网络层只管送到这台主机
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; （IP 不区分进程）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以传输层是&lt;strong&gt;第一个真正的端到端层&lt;/strong&gt;：数据从源进程的端口，直达目的进程的端口。理解了这一点，TCP/UDP 的所有设计都围绕&amp;quot;如何更好地完成端到端通信&amp;quot;展开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二端口号与套接字"&gt;&lt;a href="#%e4%ba%8c%e7%ab%af%e5%8f%a3%e5%8f%b7%e4%b8%8e%e5%a5%97%e6%8e%a5%e5%ad%97" class="header-anchor"&gt;&lt;/a&gt;二、端口号与套接字
&lt;/h2&gt;&lt;p&gt;端口号是一个 &lt;strong&gt;16 位的整数&lt;/strong&gt;，取值范围 0 ~ 65535。IANA 把它分成三段：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;范围&lt;/th&gt;
 &lt;th&gt;名称&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;th&gt;例子&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;0 – 1023&lt;/td&gt;
 &lt;td&gt;知名端口（well-known）&lt;/td&gt;
 &lt;td&gt;系统级服务常驻，Unix 下通常需要特权&lt;/td&gt;
 &lt;td&gt;22=SSH、53=DNS、80=HTTP、443=HTTPS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1024 – 49151&lt;/td&gt;
 &lt;td&gt;注册端口（registered）&lt;/td&gt;
 &lt;td&gt;IANA 为应用登记&lt;/td&gt;
 &lt;td&gt;3306=MySQL、5432=PostgreSQL、6379=Redis&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;49152 – 65535&lt;/td&gt;
 &lt;td&gt;动态 / 私有端口（dynamic）&lt;/td&gt;
 &lt;td&gt;临时端口，客户端发起连接时由系统随机分配&lt;/td&gt;
 &lt;td&gt;——&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;客户端发起请求时，操作系统从动态端口范围里临时挑一个作为&lt;strong&gt;源端口&lt;/strong&gt;，去连服务端的&lt;strong&gt;知名端口&lt;/strong&gt;。这个动态范围因系统而异：Linux 默认大致是 32768&lt;del&gt;60999（&lt;code&gt;cat /proc/sys/net/ipv4/ip_local_port_range&lt;/code&gt; 可查），Windows 默认是 49152&lt;/del&gt;65535。IANA 的建议标准是 49152~65535（RFC 6335）。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="套接字socket-ip--端口"&gt;&lt;a href="#%e5%a5%97%e6%8e%a5%e5%ad%97socket-ip--%e7%ab%af%e5%8f%a3" class="header-anchor"&gt;&lt;/a&gt;套接字（Socket）= IP + 端口
&lt;/h3&gt;&lt;p&gt;一个 &lt;strong&gt;套接字&lt;/strong&gt; 用一个二元组唯一标识一条通信端点：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;套接字 = （IP 地址，端口号）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;例如：192.168.1.10:54321 → 本机某个客户端的临时端口
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 93.184.216.34:443 → 某网站 HTTPS 服务
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;一条 TCP/UDP 连接（或数据流）由&lt;strong&gt;两端的套接字共同确定&lt;/strong&gt;：&lt;code&gt;(源IP, 源端口, 目的IP, 目的端口, 协议)&lt;/code&gt;，这叫&lt;strong&gt;五元组&lt;/strong&gt;。抓包工具里看到的一条连接，就是靠五元组区分的。后面讲 TCP 会再回到这个概念。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三udp无连接的极简协议"&gt;&lt;a href="#%e4%b8%89udp%e6%97%a0%e8%bf%9e%e6%8e%a5%e7%9a%84%e6%9e%81%e7%ae%80%e5%8d%8f%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;三、UDP：无连接的极简协议
&lt;/h2&gt;&lt;p&gt;UDP（User Datagram Protocol，用户数据报协议）是传输层的&amp;quot;极简派&amp;rdquo;——RFC 768 一个文档就讲完了，核心特点四个字：&lt;strong&gt;尽力而为&lt;/strong&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无连接&lt;/strong&gt; —— 发数据前不需要先建连，拿了 IP 和端口直接发，也不维护对端状态；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不保证可靠&lt;/strong&gt; —— 不确认、不重传，包丢了就丢了，应用层自己处理；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不保证顺序&lt;/strong&gt; —— 后发的可能先到，到达顺序不保证；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有流量 / 拥塞控制&lt;/strong&gt; —— 不会因为对端慢就降速，也不会因为网络拥塞就退让（所以 UDP 容易&amp;quot;冲&amp;quot;，也因此在某些场景下比 TCP 快）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UDP 的设计哲学是：&lt;strong&gt;传输层只做最少的搬运——加上端口、做个校验，剩下的全交给应用层&lt;/strong&gt; 。这让它天然适合&amp;quot;宁可丢一点、也别等&amp;quot;的场景。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四udp-头部与校验和"&gt;&lt;a href="#%e5%9b%9budp-%e5%a4%b4%e9%83%a8%e4%b8%8e%e6%a0%a1%e9%aa%8c%e5%92%8c" class="header-anchor"&gt;&lt;/a&gt;四、UDP 头部与校验和
&lt;/h2&gt;&lt;p&gt;UDP 的头部只有 &lt;strong&gt;8 字节&lt;/strong&gt;，是 TCP 头部（最少 20 字节）的零头：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 1 2 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 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
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 源端口 (16) | 目的端口 (16) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 长度 (16) | 校验和 (16) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 数据（若有）... |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;四个字段各 2 字节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;源端口&lt;/strong&gt; —— 可选，不用时填 0；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目的端口&lt;/strong&gt; —— 必填，接收端靠它分发；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长度&lt;/strong&gt; —— 整个 UDP 数据报（头部 + 数据）的字节数，最小值 8（只有头、没数据）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;校验和&lt;/strong&gt; —— 覆盖&amp;quot;伪首部 + UDP 头 + 数据&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="伪首部pseudo-header连-ip-层一起校验"&gt;&lt;a href="#%e4%bc%aa%e9%a6%96%e9%83%a8pseudo-header%e8%bf%9e-ip-%e5%b1%82%e4%b8%80%e8%b5%b7%e6%a0%a1%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;伪首部（pseudo header）：连 IP 层一起校验
&lt;/h3&gt;&lt;p&gt;校验和最值得说的设计是&lt;strong&gt;伪首部&lt;/strong&gt;：它不是真正传输的数据，只是计算校验和时&lt;strong&gt;临时拼在 UDP 头前面&lt;/strong&gt;的 12 字节，内容来自 IP 头部：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 1 2 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 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
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 源 IP 地址 (32) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 目的 IP 地址 (32) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 0 (8) | 协议号 (8)=17 | UDP 长度 (16) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为什么要把 IP 地址也算进来？因为 UDP 想顺带验证：&lt;strong&gt;这个包确实发给了正确的 IP 和正确的协议（UDP 的 IP 协议号是 17）&lt;/strong&gt; 。如果 IP 层把包投错了地址、或把别的协议误当成 UDP，校验和就对不上，UDP 会直接丢弃——等于给&amp;quot;端口分发&amp;quot;多上了一层保险。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一个 IPv4 的细节：UDP 校验和&lt;strong&gt;在 IPv4 里是可选的&lt;/strong&gt;（填 0 表示“不校验”）；IPv6 默认要求非零 UDP 校验和，因为 IPv6 没有头部校验和。RFC 6935 / RFC 6936 为特定、受控的隧道封装定义了零校验和例外，但普通 UDP 应用不应把例外当常规做法。实际工程中的通用 UDP 实现通常都会开启校验和。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五udp-适用场景"&gt;&lt;a href="#%e4%ba%94udp-%e9%80%82%e7%94%a8%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;五、UDP 适用场景
&lt;/h2&gt;&lt;p&gt;正因为&amp;quot;轻、快、不啰嗦&amp;quot;，UDP 在这些场景里是更优解：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;场景&lt;/th&gt;
 &lt;th&gt;为什么选 UDP&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;DNS&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;查询报文极小（通常一个包搞定），一来一回就完事；若用 TCP，每次查询都三次握手，开销远大于数据本身&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;DHCP&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;客户端连 IP 都还没有，没法建连；靠 UDP 广播/组播完成地址发现，天然契合无连接&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;音视频通话、直播&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;容忍丢包（丢一帧花屏而已），但受不了 TCP 那种&amp;quot;丢包 → 重传 → 阻塞&amp;quot;带来的延迟和卡顿&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;在线游戏&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;状态实时刷新，旧数据重传毫无价值，宁可丢也要保住最新帧&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;QUIC / HTTP/3&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;在 UDP 之上自行实现可靠性、加密、多路复用和拥塞控制，绕开 TCP 内核栈的演进包袱&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最后一条尤其重要：&lt;strong&gt;QUIC 选择&amp;quot;在 UDP 上重建一套类 TCP 的可靠传输&amp;quot;&lt;/strong&gt; ，而不是直接用 TCP，是因为 TCP 的连接管理、拥塞控制都焊死在操作系统内核和中间网络设备里，升级一次要等十年；而 UDP 是个&amp;quot;白板&amp;quot;，把控制逻辑放回用户态就能快速迭代——这是现代协议设计的一个重要趋势。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六tcp-vs-udp-选型对比"&gt;&lt;a href="#%e5%85%adtcp-vs-udp-%e9%80%89%e5%9e%8b%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;六、TCP vs UDP 选型对比
&lt;/h2&gt;&lt;p&gt;回到工程实践：到底该选 TCP 还是 UDP？一张表说清差异：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;维度&lt;/th&gt;
 &lt;th&gt;TCP&lt;/th&gt;
 &lt;th&gt;UDP&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;连接方式&lt;/td&gt;
 &lt;td&gt;面向连接（三次握手建连）&lt;/td&gt;
 &lt;td&gt;无连接，拿来就发&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;可靠性&lt;/td&gt;
 &lt;td&gt;可靠（确认 + 超时重传）&lt;/td&gt;
 &lt;td&gt;不可靠，尽力而为&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;数据顺序&lt;/td&gt;
 &lt;td&gt;按序交付，接收端无序感&lt;/td&gt;
 &lt;td&gt;不保证顺序，可能乱序&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;流量控制&lt;/td&gt;
 &lt;td&gt;有（滑动窗口）&lt;/td&gt;
 &lt;td&gt;无&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;拥塞控制&lt;/td&gt;
 &lt;td&gt;有（慢启动、AIMD 等）&lt;/td&gt;
 &lt;td&gt;无（应用层自行决定）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;头部开销&lt;/td&gt;
 &lt;td&gt;最少 20 字节&lt;/td&gt;
 &lt;td&gt;固定 8 字节&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;传输效率 / 延迟&lt;/td&gt;
 &lt;td&gt;有握手和确认开销，延迟略高&lt;/td&gt;
 &lt;td&gt;极低开销，延迟低&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;通信模式&lt;/td&gt;
 &lt;td&gt;一对一（点对点）&lt;/td&gt;
 &lt;td&gt;一对一、一对多（广播/组播）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;典型场景&lt;/td&gt;
 &lt;td&gt;网页、文件传输、邮件、数据库&lt;/td&gt;
 &lt;td&gt;DNS、DHCP、音视频、游戏、QUIC&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个简单的选型口诀：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据必须完整、不能丢&lt;/strong&gt; —— 选 TCP（文件传输、交易、数据库连接）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时性优先、容忍少量丢包&lt;/strong&gt; —— 选 UDP（音视频、游戏）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询极小、一问一答&lt;/strong&gt; —— 选 UDP（DNS）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要组播 / 广播&lt;/strong&gt; —— 只能 UDP（TCP 是严格的点对点）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;想要可靠又想摆脱 TCP 内核束缚&lt;/strong&gt; —— 在 UDP 上自建可靠层（QUIC）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（六）：TCP 连接管理&lt;/strong&gt; —— TCP 怎么靠三次握手建立连接、四次挥手断开、为什么是&amp;quot;三次&amp;quot;不是&amp;quot;两次&amp;quot;、TIME_WAIT 状态存在的意义，把传输层里最经典也最常被追问的部分讲透。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc768" target="_blank" rel="noopener"
 &gt;User Datagram Protocol (RFC 768)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.iana.org/assignments/service-names-port-numbers" target="_blank" rel="noopener"
 &gt;Service Name and Transport Protocol Port Number Registry — IANA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc6335" target="_blank" rel="noopener"
 &gt;Internet Assigned Numbers Authority Procedures (RFC 6335)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc9000" target="_blank" rel="noopener"
 &gt;QUIC: A UDP-Based Multiplexed and Secure Transport (RFC 9000)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（四）：IP 地址、子网与路由</title><link>https://www.jiwei.space/posts/networking/tcp-ip/04-ip-addressing-routing/</link><pubDate>Sat, 23 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/04-ip-addressing-routing/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;前几篇走完了分层模型、链路层，又把网络层的 IP 数据报整体结构（头部字段、TTL 的作用）过了一遍。这一篇聚焦 IP 协议里被引用最多、面试最爱考、工程上最常踩坑的部分：&lt;strong&gt;IP 地址到底怎么编排、子网怎么划分、路由器怎么查表转发，以及 ICMP 怎么撑起 ping 和 traceroute&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;这些知识在云原生里天天见——配置 Pod 的 CIDR、规划 VPC 子网、写路由表、排查&amp;quot;为什么 ping 不通&amp;quot;，全靠它。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一ipv4-地址32-位与点分十进制"&gt;&lt;a href="#%e4%b8%80ipv4-%e5%9c%b0%e5%9d%8032-%e4%bd%8d%e4%b8%8e%e7%82%b9%e5%88%86%e5%8d%81%e8%bf%9b%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;一、IPv4 地址：32 位与点分十进制
&lt;/h2&gt;&lt;p&gt;IPv4 地址是一个 &lt;strong&gt;32 位的无符号整数&lt;/strong&gt;，为了方便记忆，把它每 8 位切一段、转成十进制、用点连起来，这就是&amp;quot;点分十进制&amp;quot;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192 .168 .1 .10
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;11000000 .10101000 .00000001 .00001010
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─8位─┘ └─8位─┘ └─8位─┘ └─8位─┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 第1字节 第2字节 第3字节 第4字节
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以 IPv4 地址总共只有 2³² = 4294967296（约 43 亿）个，这正是地址紧缺、要搞 IPv6 和 NAT 的根本原因。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;注意 IP 地址是&lt;strong&gt;网络层地址&lt;/strong&gt;，跨网可路由；而 MAC 地址是链路层地址，只在同一链路内有效。这两套地址体系互不替代，后面讲 ARP 会把它们接起来。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二网络位与主机位地址的两段结构"&gt;&lt;a href="#%e4%ba%8c%e7%bd%91%e7%bb%9c%e4%bd%8d%e4%b8%8e%e4%b8%bb%e6%9c%ba%e4%bd%8d%e5%9c%b0%e5%9d%80%e7%9a%84%e4%b8%a4%e6%ae%b5%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;二、网络位与主机位：地址的两段结构
&lt;/h2&gt;&lt;p&gt;光知道 32 位还不够，关键在于这 32 位要拆成&lt;strong&gt;两段&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; IPv4 地址（32 位）= 网络位 ｜ 主机位
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──┬─┘ └──┬─┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 标识&amp;#34;哪个网络&amp;#34; 标识&amp;#34;网络里的哪台主机&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; （路由器靠它转发）（路由到目的网后用它在链路内定位）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;网络位（network prefix）&lt;/strong&gt; —— 决定这个地址属于哪个网段，路由器逐跳转发时只看这部分；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主机位（host part）&lt;/strong&gt; —— 在同一网段内区分不同主机，只有&amp;quot;到了最后一跳、进了目的网络&amp;quot;之后才用得上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;历史上有 A/B/C 三类地址，靠固定的前几位决定网络位长度（A 类 /8、B 类 /16、C 类 /24）。但这种&amp;quot;定长分类&amp;quot;太死板：一个 /16 网段有 65534 个主机地址，对一个几十台机器的小公司是巨大浪费；一个 /252 又装不下大企业。于是有了&lt;strong&gt;子网划分&lt;/strong&gt;和 &lt;strong&gt;CIDR&lt;/strong&gt;（无类别域间路由）——网络位长度不再由&amp;quot;类别&amp;quot;决定，而是可以任意指定。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三子网掩码与-cidr-记法"&gt;&lt;a href="#%e4%b8%89%e5%ad%90%e7%bd%91%e6%8e%a9%e7%a0%81%e4%b8%8e-cidr-%e8%ae%b0%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;三、子网掩码与 CIDR 记法
&lt;/h2&gt;&lt;p&gt;既然网络位长度可变，就需要一种方式告诉别人&amp;quot;前多少位是网络位&amp;quot;。有两种等价写法：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 子网掩码（dotted mask）&lt;/strong&gt; —— 把网络位全置 1、主机位全置 0，也写成点分十进制：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;前 24 位是网络位：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;掩码 = 11111111.11111111.11111111.00000000 = 255.255.255.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;前 22 位是网络位：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;掩码 = 11111111.11111111.11111100.00000000 = 255.255.252.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2. CIDR 记法&lt;/strong&gt; —— 在 IP 地址后面直接写 &lt;code&gt;/n&lt;/code&gt;，n 就是网络位的位数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.10/24 → 前 24 位是网络位，掩码 255.255.255.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10.0.0.5/22 → 前 22 位是网络位，掩码 255.255.252.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;工程里几乎都用 CIDR 记法，因为它一眼就能看出网络位长度。常见的 CIDR 与掩码对应：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;CIDR&lt;/th&gt;
 &lt;th&gt;子网掩码&lt;/th&gt;
 &lt;th&gt;主机位&lt;/th&gt;
 &lt;th&gt;可用主机数&lt;/th&gt;
 &lt;th&gt;典型用途&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;/8&lt;/td&gt;
 &lt;td&gt;255.0.0.0&lt;/td&gt;
 &lt;td&gt;24&lt;/td&gt;
 &lt;td&gt;16777214&lt;/td&gt;
 &lt;td&gt;A 类整段、10/8 私有&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;/16&lt;/td&gt;
 &lt;td&gt;255.255.0.0&lt;/td&gt;
 &lt;td&gt;16&lt;/td&gt;
 &lt;td&gt;65534&lt;/td&gt;
 &lt;td&gt;中型网络、172.16/12 子段&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;/24&lt;/td&gt;
 &lt;td&gt;255.255.255.0&lt;/td&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;254&lt;/td&gt;
 &lt;td&gt;最常见的子网大小&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;/28&lt;/td&gt;
 &lt;td&gt;255.255.255.240&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;14&lt;/td&gt;
 &lt;td&gt;小网段、云上分配合&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;/30&lt;/td&gt;
 &lt;td&gt;255.255.255.252&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;点对点链路&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;/32&lt;/td&gt;
 &lt;td&gt;255.255.255.255&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;单机路由、主机精确匹配&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;两个特例：&lt;strong&gt;可用主机数 = 2^(主机位) − 2&lt;/strong&gt; ，因为主机位全 0 的那个是&amp;quot;网络地址&amp;quot;、全 1 的那个是&amp;quot;广播地址&amp;quot;，都不能分配给主机。/31 是个特殊例外（RFC 3021），点对点链路上只有两台主机、不需要广播，所以两个地址都可用；/32 则表示单个主机，没有主机位。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="四动手算网络地址广播地址可用主机数"&gt;&lt;a href="#%e5%9b%9b%e5%8a%a8%e6%89%8b%e7%ae%97%e7%bd%91%e7%bb%9c%e5%9c%b0%e5%9d%80%e5%b9%bf%e6%92%ad%e5%9c%b0%e5%9d%80%e5%8f%af%e7%94%a8%e4%b8%bb%e6%9c%ba%e6%95%b0" class="header-anchor"&gt;&lt;/a&gt;四、动手算：网络地址、广播地址、可用主机数
&lt;/h2&gt;&lt;p&gt;这是面试高频题，也是规划子网的基本功。规则只有三条：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;网络地址&lt;/strong&gt; = IP 地址 AND 子网掩码（按位与，主机位清零）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广播地址&lt;/strong&gt; = 网络地址 OR （主机位全 1）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可用主机数&lt;/strong&gt; = 2^(主机位) − 2，范围是&amp;quot;网络地址 + 1&amp;quot;到&amp;quot;广播地址 − 1&amp;quot;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="例-119216811024"&gt;&lt;a href="#%e4%be%8b-119216811024" class="header-anchor"&gt;&lt;/a&gt;例 1：192.168.1.10/24
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/24 → 主机位 = 32 − 24 = 8，掩码 255.255.255.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;IP : 192.168.1.10 = 11000000.10101000.00000001.00001010
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;掩码: 255.255.255.0 = 11111111.11111111.11111111.00000000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; AND ─────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;网络地址: 192.168.1.0 = 11000000.10101000.00000001.00000000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;广播地址: 192.168.1.255 = 11000000.10101000.00000001.11111111 (主机位全 1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;可用主机数 = 2^8 − 2 = 254
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;可用范围 : 192.168.1.1 ~ 192.168.1.254
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="例-21000522非字节边界更接近真实场景"&gt;&lt;a href="#%e4%be%8b-21000522%e9%9d%9e%e5%ad%97%e8%8a%82%e8%be%b9%e7%95%8c%e6%9b%b4%e6%8e%a5%e8%bf%91%e7%9c%9f%e5%ae%9e%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;例 2：10.0.0.5/22（非字节边界，更接近真实场景）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/22 → 主机位 = 32 − 22 = 10，掩码 255.255.252.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;第 3 字节的掩码是 11111100 = 252（注意不是 255，跨了字节边界）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;IP : 10.0.0.5 = 00001010.00000000.00000000.00000101
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;掩码: 255.255.252.0 = 11111111.11111111.11111100.00000000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; AND ──────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;网络地址: 10.0.0.0 = 00001010.00000000.00000000.00000000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;把后 10 位主机位全置 1 得广播地址：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;广播地址: 10.0.3.255 = 00001010.00000000.00000011.11111111
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↑第3字节后2位=11=3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;可用主机数 = 2^10 − 2 = 1022
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;可用范围 : 10.0.0.1 ~ 10.0.3.254
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;第二个例子最容易出错的地方是&lt;strong&gt;第 3 字节的边界&lt;/strong&gt;：掩码 252 说明第 3 字节里只有前 2 位属于网络位，后 2 位属于主机位。所以广播地址的第 3 字节是 &lt;code&gt;00000000 | 00000011 = 3&lt;/code&gt;，整个网段横跨 10.0.0.0 ~ 10.0.3.255 这四个&amp;quot;第 3 字节&amp;quot;。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一个速算技巧：对 /24 以上（即掩码在第 4 字节内）的子网，&amp;ldquo;块大小&amp;rdquo;= 256 − 掩码字节。比如 /28 掩码是 .240，块大小 = 256 − 240 = 16，所以子网的第 4 字节从 0 开始按 16 递增：.0、.16、.32……这台主机落在哪个块里，网络地址就是那个块的起点。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五vlsm-与私有地址段"&gt;&lt;a href="#%e4%ba%94vlsm-%e4%b8%8e%e7%a7%81%e6%9c%89%e5%9c%b0%e5%9d%80%e6%ae%b5" class="header-anchor"&gt;&lt;/a&gt;五、VLSM 与私有地址段
&lt;/h2&gt;&lt;h3 id="vlsm可变长子网掩码"&gt;&lt;a href="#vlsm%e5%8f%af%e5%8f%98%e9%95%bf%e5%ad%90%e7%bd%91%e6%8e%a9%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;VLSM：可变长子网掩码
&lt;/h3&gt;&lt;p&gt;CIDR 让网络位长度任意，而 &lt;strong&gt;VLSM（Variable Length Subnet Masking，可变长子网掩码）&lt;/strong&gt; 进一步允许&lt;strong&gt;对同一个主网，按不同子网的实际大小切出不同掩码长度&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;举个规划例子：一个公司拿到 192.168.10.0/24，要分给几个不同规模的部门：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;部门 A（120 台）: 192.168.10.0/25 → 126 个可用（最省的能盖住 120 的子网）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;部门 B（50 台） : 192.168.10.128/26 → 62 个可用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;部门 C（25 台） : 192.168.10.192/27 → 30 个可用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;点对点互联（2台）: 192.168.10.224/30 → 2 个可用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;剩余未分配 : 192.168.10.228/30 ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;没有 VLSM 之前（只能用定长子网掩码 FLSM），所有子网都得按最大的那个来切，地址浪费严重。VLSM 让掩码长度&amp;quot;按需伸缩&amp;quot;，是现代网络和云上 VPC 子网规划的基础。&lt;/p&gt;
&lt;h3 id="私有地址段rfc-1918"&gt;&lt;a href="#%e7%a7%81%e6%9c%89%e5%9c%b0%e5%9d%80%e6%ae%b5rfc-1918" class="header-anchor"&gt;&lt;/a&gt;私有地址段（RFC 1918）
&lt;/h3&gt;&lt;p&gt;不是所有地址都能在公网路由。RFC 1918 划出三段&lt;strong&gt;私有地址&lt;/strong&gt;，供内网自由使用、不用申请，出公网时靠 NAT 转换：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;名称&lt;/th&gt;
 &lt;th&gt;网段&lt;/th&gt;
 &lt;th&gt;CIDR&lt;/th&gt;
 &lt;th&gt;容量&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;A 类私有&lt;/td&gt;
 &lt;td&gt;10.0.0.0 ~ 10.255.255.255&lt;/td&gt;
 &lt;td&gt;10.0.0.0/8&lt;/td&gt;
 &lt;td&gt;2²⁴（约 1677 万）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;B 类私有&lt;/td&gt;
 &lt;td&gt;172.16.0.0 ~ 172.31.255.255&lt;/td&gt;
 &lt;td&gt;172.16.0.0/12&lt;/td&gt;
 &lt;td&gt;2²⁰（约 100 万）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;C 类私有&lt;/td&gt;
 &lt;td&gt;192.168.0.0 ~ 192.168.255.255&lt;/td&gt;
 &lt;td&gt;192.168.0.0/16&lt;/td&gt;
 &lt;td&gt;2¹⁶（65536）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;家里路由器的 LAN、公司办公网、数据中心内网、云上 VPC、Docker 默认网桥——绝大多数内网都落在这三段里。还有几个特殊段也值得知道：&lt;strong&gt;169.254.0.0/16&lt;/strong&gt;（链路本地，DHCP 拿不到地址时的 fallback）、&lt;strong&gt;127.0.0.0/8&lt;/strong&gt;（环回，localhost）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六路由表与最长前缀匹配"&gt;&lt;a href="#%e5%85%ad%e8%b7%af%e7%94%b1%e8%a1%a8%e4%b8%8e%e6%9c%80%e9%95%bf%e5%89%8d%e7%bc%80%e5%8c%b9%e9%85%8d" class="header-anchor"&gt;&lt;/a&gt;六、路由表与最长前缀匹配
&lt;/h2&gt;&lt;p&gt;IP 地址规划好之后，&lt;strong&gt;路由器靠&amp;quot;路由表&amp;quot;决定每个包往哪转发&lt;/strong&gt;。一条路由条目至少包含三要素：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目的网络（含掩码） | 下一跳（Next Hop） | 出接口
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;比如一台主机的路由表可能长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目的网络 掩码 下一跳 接口
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0.0.0.0 0.0.0.0 192.168.1.1 eth0 ← 默认路由（兜底）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.0 255.255.255.0 0.0.0.0(本机) eth0 ← 直连子网
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10.0.0.0 255.255.252.0 192.168.1.254 eth0 ← 去往 10.0.0.0/22
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="最长前缀匹配longest-prefix-match"&gt;&lt;a href="#%e6%9c%80%e9%95%bf%e5%89%8d%e7%bc%80%e5%8c%b9%e9%85%8dlongest-prefix-match" class="header-anchor"&gt;&lt;/a&gt;最长前缀匹配（Longest Prefix Match）
&lt;/h3&gt;&lt;p&gt;当一个目的地址能匹配多条路由时，&lt;strong&gt;路由器永远选掩码最长（最具体）的那条&lt;/strong&gt;——因为掩码越长、网络位越精确，指向的网段越小。&lt;/p&gt;
&lt;p&gt;举例：要发往 &lt;code&gt;10.0.0.50&lt;/code&gt;，路由表里有 &lt;code&gt;10.0.0.0/22&lt;/code&gt; 和 &lt;code&gt;10.0.0.0/24&lt;/code&gt; 两条。两者都能匹配（50 落在这两个网段里），但 /24 比 /22 更长更具体，于是走 /24 那条。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目的 10.0.0.50
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 命中 10.0.0.0/22 (网络位 22)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ 命中 10.0.0.0/24 (网络位 24) ← 更长，优先选这条
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="默认路由与默认网关"&gt;&lt;a href="#%e9%bb%98%e8%ae%a4%e8%b7%af%e7%94%b1%e4%b8%8e%e9%bb%98%e8%ae%a4%e7%bd%91%e5%85%b3" class="header-anchor"&gt;&lt;/a&gt;默认路由与默认网关
&lt;/h3&gt;&lt;p&gt;如果没有任何具体路由能匹配目的地址怎么办？交给&lt;strong&gt;默认路由 0.0.0.0/0&lt;/strong&gt;。它的掩码是 0 位，能匹配&lt;strong&gt;任意&lt;/strong&gt;地址，所以它是&amp;quot;兜底&amp;quot;——按最长前缀匹配规则，它永远排在最后才被选中。&lt;/p&gt;
&lt;p&gt;家宽场景里，主机的默认路由下一跳就是&lt;strong&gt;默认网关（家用路由器的 LAN 口 IP）&lt;/strong&gt;，所有&amp;quot;非本网段&amp;quot;的流量都甩给它，由它继续往运营商转发。&lt;code&gt;ip route&lt;/code&gt; 或 &lt;code&gt;route -n&lt;/code&gt; 看到的 &lt;code&gt;default via ...&lt;/code&gt; 就是这条。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;default via 192.168.1.1 dev eth0 ← 等价于 0.0.0.0/0 via 192.168.1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="七icmpping-与-traceroute-的底层"&gt;&lt;a href="#%e4%b8%83icmpping-%e4%b8%8e-traceroute-%e7%9a%84%e5%ba%95%e5%b1%82" class="header-anchor"&gt;&lt;/a&gt;七、ICMP：ping 与 traceroute 的底层
&lt;/h2&gt;&lt;p&gt;IP 协议本身只管&amp;quot;尽力把包送到&amp;quot;，不反馈任何状态。&lt;strong&gt;ICMP（Internet Control Message Protocol，互联网控制报文协议）&lt;/strong&gt; 就是 IP 层的&amp;quot;信使&amp;quot;，用来报告差错和探测网络——ping 和 traceroute 都建立在它之上。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;严格说 ICMP 工作在网络层（和 IP 同层，封装在 IP 包里，协议号 1），&lt;strong&gt;没有端口号&lt;/strong&gt;，不涉及传输层。所以&amp;quot;ping 某个端口&amp;quot;在 ICMP 意义上是不成立的——ping 只能测主机可达性。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="pingecho-request--echo-reply"&gt;&lt;a href="#pingecho-request--echo-reply" class="header-anchor"&gt;&lt;/a&gt;ping：Echo Request / Echo Reply
&lt;/h3&gt;&lt;p&gt;ping 的原理极其简单——发一个 &lt;strong&gt;ICMP Echo Request（类型 8，代码 0）&lt;/strong&gt; 给目标，目标收到后回一个 &lt;strong&gt;Echo Reply（类型 0，代码 0）&lt;/strong&gt;，一来一回就能算 RTT 和丢包：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;本机 目标主机
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │── Echo Request (Type 8) ─────────►│
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │◄──────── Echo Reply (Type 0) ──────┤
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 记录往返时间 RTT │
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;ping 不通的原因很多：目标离线、路由不可达、防火墙&lt;strong&gt;丢弃了 ICMP&lt;/strong&gt;（很多生产环境出于安全只放行业务端口、禁 ping，所以&amp;quot;ping 不通 ≠ 服务不可用&amp;quot;，这点排查时要记牢）。&lt;/p&gt;
&lt;h3 id="traceroute靠-ttl-递增一跳跳摸路径"&gt;&lt;a href="#traceroute%e9%9d%a0-ttl-%e9%80%92%e5%a2%9e%e4%b8%80%e8%b7%b3%e8%b7%b3%e6%91%b8%e8%b7%af%e5%be%84" class="header-anchor"&gt;&lt;/a&gt;traceroute：靠 TTL 递增一跳跳摸路径
&lt;/h3&gt;&lt;p&gt;traceroute 巧妙利用了 IP 头部里的 &lt;strong&gt;TTL（Time To Live，生存时间）&lt;/strong&gt; 字段。每经过一台路由器，TTL 减 1；一旦减到 0，路由器就&lt;strong&gt;丢弃该包并回送一个 ICMP Time Exceeded（类型 11）&lt;/strong&gt; 给源。traceroute 正是靠这个回包&amp;quot;自报家门&amp;quot;的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;源 路由器A 路由器B 目标
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ TTL=1 ─────────────────►│
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ TTL-1=0，丢弃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │◄──── ICMP Time Exceeded──┤ （记录 A 的地址 + 耗时）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ TTL=2 ─────────────────►│转发, TTL=1►│
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ TTL-1=0，丢弃
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │◄────────── ICMP Time Exceeded─────────┤ （记录 B）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ TTL=3 ─────────────────►│ ─────────►│ ─────────►│
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 到达目标
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │◄────────── Echo Reply / Port Unreachable ───┤ （结束）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;过程是：先发 TTL=1，第一台路由器把 TTL 减到 0、回 Time Exceeded，于是知道了第一跳；再发 TTL=2，第二台路由器回包，知道第二跳……如此递增，直到某个探测包真正到达目标，目标回一个应答（ICMP Echo Reply 或 UDP 端口不可达），路径就摸完了。&lt;/p&gt;
&lt;p&gt;一个&lt;strong&gt;跨平台要注意的细节&lt;/strong&gt;：探测包用什么协议，不同系统不一样——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linux 的 &lt;code&gt;traceroute&lt;/code&gt;&lt;/strong&gt;（传统默认）发 &lt;strong&gt;UDP&lt;/strong&gt; 探测包，目的端口用一个递增的高端口，目标通常因端口未监听而回 &lt;strong&gt;ICMP Port Unreachable（类型 3）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows 的 &lt;code&gt;tracert&lt;/code&gt;&lt;/strong&gt; 发 &lt;strong&gt;ICMP Echo Request&lt;/strong&gt; 探测包，目标回 &lt;strong&gt;Echo Reply&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两者定位每一跳都靠中间路由器回的 &lt;strong&gt;ICMP Time Exceeded（类型 11）&lt;/strong&gt;，只是&amp;quot;到达目标后收到的那个结束应答&amp;quot;不同。现代 Linux 也可以用 &lt;code&gt;traceroute -I&lt;/code&gt; 强制走 ICMP、&lt;code&gt;mtr&lt;/code&gt; 做持续式路径探测，在排查抖动时更顺手。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;IPv4 地址是 32 位整数，点分十进制只是写法；核心在于它被切成&lt;strong&gt;网络位 + 主机位&lt;/strong&gt; ；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子网掩码&lt;/strong&gt;和 &lt;strong&gt;CIDR &lt;code&gt;/n&lt;/code&gt;&lt;/strong&gt; 是描述网络位长度的两种等价方式，可用主机数 = 2^(主机位) − 2；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络地址&lt;/strong&gt; = IP AND 掩码，&lt;strong&gt;广播地址&lt;/strong&gt; = 主机位全 1；非字节边界（如 /22）的算法要练熟；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VLSM&lt;/strong&gt; 让同一主网按需切出不同掩码；&lt;strong&gt;私有地址段&lt;/strong&gt;（10/8、172.16/12、192.168/16）供内网自由使用；&lt;/li&gt;
&lt;li&gt;路由器按&lt;strong&gt;最长前缀匹配&lt;/strong&gt;选路，&lt;strong&gt;默认路由 0.0.0.0/0&lt;/strong&gt; 是兜底；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ICMP&lt;/strong&gt; 是 IP 层信使，&lt;strong&gt;ping&lt;/strong&gt; 用 Echo Request/Reply 测可达性，&lt;strong&gt;traceroute&lt;/strong&gt; 靠 TTL 递增 + Time Exceeded 逐跳摸路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（五）：传输层与 UDP&lt;/strong&gt; —— 从&amp;quot;主机到主机&amp;quot;上升到&amp;quot;进程到进程&amp;quot;，讲清端口号、套接字，以及 UDP 这个&amp;quot;极简&amp;quot;传输层协议的头部、校验和和适用场景。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc1918" target="_blank" rel="noopener"
 &gt;Address Allocation for Private Internets (RFC 1918)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc4632" target="_blank" rel="noopener"
 &gt;Classless Inter-Domain Routing (CIDR) — RFC 4632&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc3021" target="_blank" rel="noopener"
 &gt;Using 31-Bit Prefixes on Point-to-Point Links (RFC 3021)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml" target="_blank" rel="noopener"
 &gt;ICMP Parameters — IANA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/Traceroute" target="_blank" rel="noopener"
 &gt;traceroute — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（三）：IP 协议——数据报、头部与分片</title><link>https://www.jiwei.space/posts/networking/tcp-ip/03-ip-protocol/</link><pubDate>Thu, 21 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/03-ip-protocol/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇把链路层讲完了：同一子网里，靠 MAC 地址 + 以太网帧 + 交换机 + ARP，两台设备就能把数据搬过去。但 MAC 只管&amp;quot;一跳&amp;quot;，跨网、跨子网、从北京到纽约，链路层就无能为力了。&lt;/p&gt;
&lt;p&gt;升级到网络层，主角登场：&lt;strong&gt;IP&lt;/strong&gt;（Internet Protocol）。这一篇先不谈地址怎么划分、路由怎么走（那是下一篇的事），而是把 IP 协议本身讲透——它的设计哲学、IPv4 数据报头部每一个字段、TTL 怎么防环路、分片怎么和 MTU 较劲，最后带一眼 IPv6 的概貌。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一ip-的设计哲学尽力而为"&gt;&lt;a href="#%e4%b8%80ip-%e7%9a%84%e8%ae%be%e8%ae%a1%e5%93%b2%e5%ad%a6%e5%b0%bd%e5%8a%9b%e8%80%8c%e4%b8%ba" class="header-anchor"&gt;&lt;/a&gt;一、IP 的设计哲学：尽力而为
&lt;/h2&gt;&lt;p&gt;IP 的脾性可以用三个词概括：&lt;strong&gt;无连接、不可靠、尽力而为&lt;/strong&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;无连接&lt;/strong&gt;（connectionless）：发包前不先建电路、不先握手，抬手就发；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可靠&lt;/strong&gt;（unreliable）：不保证送达、不保证顺序、不保证不重复；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尽力而为&lt;/strong&gt;（best-effort）：会尽量投递，但出了问题不负责善后——丢了就丢了，乱序就乱序。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这听起来很糟，其实是有意为之。可靠性、顺序、去重这些复杂活，IP 全部甩给上层（一般是 TCP）去干。IP 只负责一件事：&lt;strong&gt;看目的地址，把包往目的方向推一跳&lt;/strong&gt;。这样网络层保持极简、极快，复杂逻辑只在端点上做——这正是互联网能&amp;quot;大而糙地&amp;quot;扩到全球的秘诀。&lt;/p&gt;
&lt;p&gt;顺带一提 &lt;strong&gt;ICMP&lt;/strong&gt;（Internet Control Message Protocol），它是 IP 的&amp;quot;信使&amp;quot;。IP 自己不报告错误，遇到问题（TTL 超时、目的不可达、需要分片但 DF 置位）就回一个 ICMP 报文告诉源端。日常用的 &lt;code&gt;ping&lt;/code&gt;、&lt;code&gt;traceroute&lt;/code&gt; 都靠它。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二ipv4-数据报头部"&gt;&lt;a href="#%e4%ba%8cipv4-%e6%95%b0%e6%8d%ae%e6%8a%a5%e5%a4%b4%e9%83%a8" class="header-anchor"&gt;&lt;/a&gt;二、IPv4 数据报头部
&lt;/h2&gt;&lt;p&gt;IP 层的 PDU 叫&lt;strong&gt;数据报&lt;/strong&gt;（datagram）。一个 IPv4 数据报由头部 + 载荷组成，头部固定部分 20 字节，最长 60 字节（带选项）。RFC 791 定义的头部布局：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 1 2 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 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
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 |Version| IHL | DSCP | ECN | Total Length |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 | Identification |Flags| Fragment Offset |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 8 | TTL | Protocol | Header Checksum |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 12 | Source IP Address |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 16 | Destination IP Address |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 20 | Options (if any) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;逐字段拆解：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;字段&lt;/th&gt;
 &lt;th&gt;位宽&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Version&lt;/td&gt;
 &lt;td&gt;4 位&lt;/td&gt;
 &lt;td&gt;IP 版本，IPv4 这里是 &lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;IHL&lt;/td&gt;
 &lt;td&gt;4 位&lt;/td&gt;
 &lt;td&gt;头长，以 4 字节为单位。最小 &lt;code&gt;5&lt;/code&gt;（=20 字节，无选项），最大 &lt;code&gt;15&lt;/code&gt;（=60 字节）。&lt;strong&gt;它决定了载荷从哪个字节开始&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DSCP&lt;/td&gt;
 &lt;td&gt;6 位&lt;/td&gt;
 &lt;td&gt;差分服务码点，用于 QoS 打标记优先级&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ECN&lt;/td&gt;
 &lt;td&gt;2 位&lt;/td&gt;
 &lt;td&gt;显式拥塞通告，路由器用它给端点&amp;quot;我拥塞了&amp;quot;的信号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Total Length&lt;/td&gt;
 &lt;td&gt;16 位&lt;/td&gt;
 &lt;td&gt;整个数据报（头部 + 载荷）的字节数，理论最大 65535&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Identification&lt;/td&gt;
 &lt;td&gt;16 位&lt;/td&gt;
 &lt;td&gt;标识符，分片重组时用来认&amp;quot;这几片本来是一家&amp;quot;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Flags&lt;/td&gt;
 &lt;td&gt;3 位&lt;/td&gt;
 &lt;td&gt;bit0 保留（置 0）；bit1 &lt;strong&gt;DF&lt;/strong&gt;（Don&amp;rsquo;t Fragment）；bit2 &lt;strong&gt;MF&lt;/strong&gt;（More Fragments）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Fragment Offset&lt;/td&gt;
 &lt;td&gt;13 位&lt;/td&gt;
 &lt;td&gt;本片在原数据报中的位置，&lt;strong&gt;以 8 字节为单位&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TTL&lt;/td&gt;
 &lt;td&gt;8 位&lt;/td&gt;
 &lt;td&gt;生存时间，每经一跳减 1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Protocol&lt;/td&gt;
 &lt;td&gt;8 位&lt;/td&gt;
 &lt;td&gt;上层协议号，告诉对面&amp;quot;载荷交给谁&amp;quot;（见下表）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Header Checksum&lt;/td&gt;
 &lt;td&gt;16 位&lt;/td&gt;
 &lt;td&gt;只校验头部，&lt;strong&gt;不校验载荷&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Source IP&lt;/td&gt;
 &lt;td&gt;32 位&lt;/td&gt;
 &lt;td&gt;源地址&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Destination IP&lt;/td&gt;
 &lt;td&gt;32 位&lt;/td&gt;
 &lt;td&gt;目的地址&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Options&lt;/td&gt;
 &lt;td&gt;可变&lt;/td&gt;
 &lt;td&gt;几乎不用，多数路径会忽略或直接丢&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Protocol 字段&lt;/strong&gt; 常见取值（IANA 维护完整列表）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;协议号&lt;/th&gt;
 &lt;th&gt;协议&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;ICMP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;IGMP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;TCP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;17&lt;/td&gt;
 &lt;td&gt;UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;41&lt;/td&gt;
 &lt;td&gt;IPv6 封装&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;47&lt;/td&gt;
 &lt;td&gt;GRE&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;50&lt;/td&gt;
 &lt;td&gt;ESP（IPsec）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;51&lt;/td&gt;
 &lt;td&gt;AH（IPsec）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;58&lt;/td&gt;
 &lt;td&gt;ICMPv6&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;89&lt;/td&gt;
 &lt;td&gt;OSPF&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;几个容易记错的点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IHL 数的是 4 字节单位，不是字节&lt;/strong&gt;。所以 &lt;code&gt;5&lt;/code&gt; 代表 20 字节、&lt;code&gt;15&lt;/code&gt; 代表 60 字节，别和 Total Length 搞混；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header Checksum 只管头部&lt;/strong&gt;，载荷的完整性交给上层（TCP 有自己的校验和、UDP 在 IPv4 里校验和可选但实际都开）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Options 基本退出历史舞台&lt;/strong&gt;：带选项的包常被路径上的设备丢弃或特殊处理，安全设备尤其敏感。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="三ttl防环路的保险丝"&gt;&lt;a href="#%e4%b8%89ttl%e9%98%b2%e7%8e%af%e8%b7%af%e7%9a%84%e4%bf%9d%e9%99%a9%e4%b8%9d" class="header-anchor"&gt;&lt;/a&gt;三、TTL：防环路的保险丝
&lt;/h2&gt;&lt;p&gt;TTL（Time To Live）名字像&amp;quot;时间&amp;quot;，其实是&lt;strong&gt;跳数&lt;/strong&gt;。规则极简：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;源端给个初值（常见 64、128 或 255，因系统而异）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每经过一台路由器，TTL 减 1&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;减到 0 还没到目的地，路由器丢弃该包，并回一个 &lt;strong&gt;ICMP Time Exceeded&lt;/strong&gt; 给源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它的核心使命是&lt;strong&gt;防止环路包永远转圈&lt;/strong&gt;。要是没有 TTL，一个配错路由的包可能在两台路由器之间无限循环，把链路带宽吃光。&lt;/p&gt;
&lt;p&gt;不同系统的默认 TTL 不一样，常被拿来粗略判断对端系统（不完全可靠，可被改）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;系统&lt;/th&gt;
 &lt;th&gt;常见默认 TTL&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux / macOS&lt;/td&gt;
 &lt;td&gt;64&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Windows&lt;/td&gt;
 &lt;td&gt;128&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;网络设备（路由器等）&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;这正是 &lt;code&gt;traceroute&lt;/code&gt;（Windows 下叫 &lt;code&gt;tracert&lt;/code&gt;）的原理：故意发 TTL = 1、2、3 … 的探测包，每一跳的路由器因为 TTL 超时回 ICMP Time Exceeded，把返回的 ICMP 源地址记下来，就描出了整条路径。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="四分片与-mtu"&gt;&lt;a href="#%e5%9b%9b%e5%88%86%e7%89%87%e4%b8%8e-mtu" class="header-anchor"&gt;&lt;/a&gt;四、分片与 MTU
&lt;/h2&gt;&lt;p&gt;上一篇说过，以太网的 &lt;strong&gt;MTU = 1500 字节&lt;/strong&gt;（载荷上限）。可 IP 数据报最长能到 65535 字节。当 IP 包大于出口链路的 MTU 时，就要&lt;strong&gt;分片&lt;/strong&gt;（fragmentation）：路由器把载荷切成几片，每片都套上一个完整的 IP 头，各自独立转发。&lt;/p&gt;
&lt;p&gt;分片相关三个字段一起用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Identification&lt;/strong&gt;：原数据报被切出来的所有分片，&lt;strong&gt;共用同一个 Identification&lt;/strong&gt;，让目的端能认出&amp;quot;这几片是一家的&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MF&lt;/strong&gt;（More Fragments）：除最后一片外都是 &lt;code&gt;1&lt;/code&gt;，最后一片是 &lt;code&gt;0&lt;/code&gt;——告诉目的端&amp;quot;分片到这就齐了&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragment Offset&lt;/strong&gt;：本片数据在原载荷中的偏移，&lt;strong&gt;以 8 字节为单位&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重组（reassembly）&lt;strong&gt;只在目的端做，中间路由器不重组&lt;/strong&gt;。目的端靠 &lt;code&gt;(源 IP, 目的 IP, Protocol, Identification)&lt;/code&gt; 四元组把同一组的分片归到一起，按 Offset 拼回去；MF = 0 的那片到齐、且中间没有空洞，就算拼完。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么 Offset 以 8 字节为单位&lt;/strong&gt;？因为 13 位的 Offset 域，乘以 8 才能覆盖 16 位的最大长度：&lt;code&gt;2^13 × 8 = 65536&lt;/code&gt;，正好够表示最大 65535 字节的数据报。这也意味着&lt;strong&gt;每片的载荷长度必须是 8 的倍数&lt;/strong&gt;（最后一片除外）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DF 标志与 PMTUD&lt;/strong&gt;：DF = 1 表示&amp;quot;不要分片&amp;quot;。如果路由器发现包超过 MTU 又被置了 DF，它会丢弃该包，并回一个 &lt;strong&gt;ICMP &amp;ldquo;需要分片&amp;rdquo; 报文&lt;/strong&gt;（Fragmentation Needed），把&amp;quot;下游 MTU 是多少&amp;quot;也带上。源端收到后调小包重发——这就是 &lt;strong&gt;PMTUD&lt;/strong&gt;（Path MTU Discovery，路径 MTU 发现）的核心。现代网络强烈推荐用 PMTUD + DF = 1 来避免分片，原因下面说。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;分片为什么能避则避：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一片丢，全丢&lt;/strong&gt;：任一分片在路上丢了，整个数据报就拼不齐，上层（TCP）只能整体重传，放大了丢包代价；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;防火墙难处理&lt;/strong&gt;：分片后第一条之外的片没有 L4 头部，安全策略和 NAT 很难判断，历史上是著名的绕过手法；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重组负担在目的端&lt;/strong&gt;：占用内存、可能被用来打&amp;quot;重组炸弹&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以实践里：应用尽量别发超大包、TCP 配合 PMTUD、UDP 应用自己控制报文大小，能不分片就不分。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五ipv6-概览"&gt;&lt;a href="#%e4%ba%94ipv6-%e6%a6%82%e8%a7%88" class="header-anchor"&gt;&lt;/a&gt;五、IPv6 概览
&lt;/h2&gt;&lt;p&gt;IPv4 地址（32 位）早不够用了，IPv6 把地址扩到 &lt;strong&gt;128 位&lt;/strong&gt;，号称&amp;quot;给每粒沙子都分一个地址&amp;quot;。但 IPv6 不只是&amp;quot;地址变长&amp;quot;，它对头部做了大幅简化。IPv6 基本头部&lt;strong&gt;固定 40 字节&lt;/strong&gt;，没有选项、没有头部校验和、分片相关字段也从基本头里挪走了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 1 2 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 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
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 |Version| Traffic Class | Flow Label |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 | Payload Length | Next Header | Hop Limit |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 8 | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; + +
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 24 | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; + +
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 字节 8~23：Source Address（128 位）；字节 24~39：Destination Address（128 位）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;字段对照 IPv4 看更清楚：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;IPv6 字段&lt;/th&gt;
 &lt;th&gt;位宽&lt;/th&gt;
 &lt;th&gt;对应 IPv4 / 说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Version&lt;/td&gt;
 &lt;td&gt;4 位&lt;/td&gt;
 &lt;td&gt;这里是 &lt;code&gt;6&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Traffic Class&lt;/td&gt;
 &lt;td&gt;8 位&lt;/td&gt;
 &lt;td&gt;对应 IPv4 的 DSCP + ECN&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Flow Label&lt;/td&gt;
 &lt;td&gt;20 位&lt;/td&gt;
 &lt;td&gt;IPv6 新增，标识一条流，便于做 QoS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Payload Length&lt;/td&gt;
 &lt;td&gt;16 位&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;只算载荷，不含 40 字节基本头&lt;/strong&gt;（IPv4 的 Total Length 是含头的）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Next Header&lt;/td&gt;
 &lt;td&gt;8 位&lt;/td&gt;
 &lt;td&gt;对应 IPv4 的 Protocol，但&lt;strong&gt;还能指向扩展头链&lt;/strong&gt;（见下）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hop Limit&lt;/td&gt;
 &lt;td&gt;8 位&lt;/td&gt;
 &lt;td&gt;对应 TTL，名字更准确（每跳减 1）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Source / Dest Address&lt;/td&gt;
 &lt;td&gt;各 128 位&lt;/td&gt;
 &lt;td&gt;16 字节的 IPv6 地址&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;IPv6 相对 IPv4 的几个关键变化：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;头部更简、转发更快&lt;/strong&gt;。固定 40 字节、没有头部校验和（校验交给上层和链路层 FCS），路由器转发时不用每跳重算校验和，转发效率更高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;扩展头链代替 Options&lt;/strong&gt;。IPv6 把可选功能（逐跳选项、路由、分片、IPsec 的 ESP/AH、目的选项）做成一条&lt;strong&gt;扩展头链&lt;/strong&gt;，用 Next Header 一个接一个串起来，最后才接上层协议。基本头永远在前、固定 40 字节，路由器只需看基本头就能转发，绝大多数扩展头只有目的端（或指定节点）才处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;中间路由不再分片&lt;/strong&gt;。IPv6 规定&lt;strong&gt;路由器不负责分片&lt;/strong&gt;，只有源端能分（通过分片扩展头）。如果路由器发现包超过下游 MTU，直接丢弃并回 &lt;strong&gt;ICMPv6 Packet Too Big&lt;/strong&gt;，源端据此调小重发（PMTUD）。IPv6 还规定&lt;strong&gt;每条链路最低 MTU = 1280 字节&lt;/strong&gt;，低于这个值必须做适配。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hop Limit 取代 TTL&lt;/strong&gt;，机制完全一样（每跳减 1、到 0 丢弃），只是换了个更诚实的名字。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NDP 取代 ARP&lt;/strong&gt;。IPv6 没有 ARP 协议，地址解析改由 &lt;strong&gt;NDP&lt;/strong&gt;（Neighbor Discovery Protocol，邻居发现协议，RFC 4861）完成——它基于 ICMPv6，用组播（而非广播）来查询邻居的链路层地址。好处是：&lt;strong&gt;不再有 ARP 那种全链路广播风暴&lt;/strong&gt;，也顺带把 ARP 欺骗的老坑换成了新的安全模型（NDP 有 SEND 等机制可加固）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;IP 是&lt;strong&gt;无连接、不可靠、尽力而为&lt;/strong&gt;的网络层协议，可靠性交给上层（TCP）；&lt;/li&gt;
&lt;li&gt;IPv4 头部固定 20 字节（可扩到 60），关键字段：&lt;strong&gt;Version / IHL（4 字节单位）/ Total Length / TTL / Protocol / 头部校验和 / 源 / 目的 IP&lt;/strong&gt;，分片靠 &lt;strong&gt;Identification + Flags + Fragment Offset&lt;/strong&gt; 三个字段；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTL 每跳减 1&lt;/strong&gt;，到 0 丢弃并回 ICMP Time Exceeded，是防环路的保险丝，也是 &lt;code&gt;traceroute&lt;/code&gt; 的原理；&lt;/li&gt;
&lt;li&gt;当 &lt;strong&gt;IPv4 数据报长度超过出口链路 MTU&lt;/strong&gt; 且没有禁止分片时，源主机或中间路由器可能分片；重组只在目的端进行。现代实践倾向用 &lt;strong&gt;DF = 1 + PMTUD&lt;/strong&gt; 避免分片（一片丢则全丢、安全设备难处理）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPv6 头部固定 40 字节&lt;/strong&gt;、无校验和、扩展头链代替选项、中间路由不分片、最低 MTU 1280、Hop Limit 取代 TTL、&lt;strong&gt;NDP 取代 ARP&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇，我们正式进入 IP 地址的世界——&lt;strong&gt;TCP/IP（四）：IP 地址、子网划分与路由&lt;/strong&gt; ：地址分类、子网掩码与 CIDR、私有地址、路由表怎么选下一跳。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc791" target="_blank" rel="noopener"
 &gt;RFC 791 — Internet Protocol (IPv4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc8200" target="_blank" rel="noopener"
 &gt;RFC 8200 — Internet Protocol, Version 6 (IPv6)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc4861" target="_blank" rel="noopener"
 &gt;RFC 4861 — Neighbor Discovery for IP version 6 (IPv6)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.iana.org/assignments/protocol-numbers" target="_blank" rel="noopener"
 &gt;IANA — Assigned Internet Protocol Numbers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/IPv6_packet" target="_blank" rel="noopener"
 &gt;IPv6 packet — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（二）：以太网与链路层——MAC 地址、帧与 ARP</title><link>https://www.jiwei.space/posts/networking/tcp-ip/02-link-layer/</link><pubDate>Tue, 19 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/02-link-layer/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;上一篇把分层模型讲完了：OSI 七层、TCP/IP 四层、实际用的五层，还有逐层封装。从这一篇开始，我们沿五层模型&lt;strong&gt;自底向上&lt;/strong&gt;一层层展开。&lt;/p&gt;
&lt;p&gt;最底下是物理层（比特、电信号、光纤），它没什么&amp;quot;协议&amp;quot;可讲，略过。再往上就是&lt;strong&gt;数据链路层&lt;/strong&gt;——这一层要解决的问题是：&lt;strong&gt;在相邻两个节点之间，怎么把比特组织成可以识别、可以校验的数据单元传过去&lt;/strong&gt; 。注意是&amp;quot;相邻&amp;quot;——同一根线两头的两台设备，链路层只管这一跳；跨网、跨子网的事留给上面的网络层。&lt;/p&gt;
&lt;p&gt;这一篇聚焦工程上最主流的链路技术&lt;strong&gt;以太网&lt;/strong&gt;（Ethernet），讲四件事：MAC 地址、以太网帧、交换机怎么转发、ARP 怎么把 IP 翻译成 MAC。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一链路层到底管什么"&gt;&lt;a href="#%e4%b8%80%e9%93%be%e8%b7%af%e5%b1%82%e5%88%b0%e5%ba%95%e7%ae%a1%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;一、链路层到底管什么
&lt;/h2&gt;&lt;p&gt;把网络层交下来的 IP 包搬到&amp;quot;对面那台设备&amp;quot;，链路层要做这几件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;成帧&lt;/strong&gt; —— 给 IP 包套上自己的头部和尾部（帧头 + 帧尾），告诉对面&amp;quot;这是一帧、从哪来、到哪去&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物理寻址&lt;/strong&gt; —— 用 MAC 地址在同一链路上定位网卡；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;差错检测&lt;/strong&gt; —— 帧尾加一段校验码（FCS），接收方算一遍，错了就丢，不交给上层；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;介质访问控制&lt;/strong&gt; —— 决定谁能在共享介质上发数据（经典以太网用 CSMA/CD，全双工交换式以太网里基本退场）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要特别强调一点：链路层&lt;strong&gt;大多数时候只做&amp;quot;检错 + 丢弃&amp;quot;，不做重传&lt;/strong&gt;。丢了的帧交给上层（比如 TCP）去补。这也是分层分工的体现——链路层保持简单、快。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一个常被忽略的点：Wi-Fi（IEEE 802.11）和以太网（802.3）是两条不同的链路层，但它们&lt;strong&gt;都承载 IP&lt;/strong&gt;，所以你从 Wi-Fi 切到有线，上层协议毫无感知。这正是分层带来的好处。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二mac-地址网卡的身份证"&gt;&lt;a href="#%e4%ba%8cmac-%e5%9c%b0%e5%9d%80%e7%bd%91%e5%8d%a1%e7%9a%84%e8%ba%ab%e4%bb%bd%e8%af%81" class="header-anchor"&gt;&lt;/a&gt;二、MAC 地址：网卡的身份证
&lt;/h2&gt;&lt;p&gt;MAC（Media Access Control）地址是网卡的硬件地址，几个关键事实：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;长度 48 位&lt;/strong&gt;（6 字节），标准名也叫 &lt;strong&gt;EUI-48&lt;/strong&gt;，习惯写成 &lt;code&gt;AA:BB:CC:DD:EE:FF&lt;/code&gt; 这种六段十六进制。&lt;/li&gt;
&lt;li&gt;传统的 48 位全局 MAC 地址常按 &lt;strong&gt;前 3 字节 OUI&lt;/strong&gt;（Organizationally Unique Identifier，组织唯一标识符）+ &lt;strong&gt;后 3 字节厂商分配部分&lt;/strong&gt;来理解。IEEE 现在还提供 MA-M、MA-S 等更小的地址号段，因此“前三字节一定是厂商 OUI”适合作为经典模型，而不是覆盖所有现代分配方式的绝对规则。&lt;/li&gt;
&lt;li&gt;因此又叫&amp;quot;固化地址&amp;quot;（burned-in address）。但现在的 MAC &lt;strong&gt;未必真的固化&lt;/strong&gt;：虚拟机网卡、容器 veth、手机随机 MAC（隐私保护）、手工 &lt;code&gt;ip link set&lt;/code&gt; 改的 MAC——都是&amp;quot;本地分配&amp;quot;的，不来自 OUI。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键在于第一字节的最低两位，它们决定了这个地址的性质：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;位&lt;/th&gt;
 &lt;th&gt;名称&lt;/th&gt;
 &lt;th&gt;0&lt;/th&gt;
 &lt;th&gt;1&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;bit 0（最低位）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;I/G&lt;/strong&gt; 位（Individual / Group）&lt;/td&gt;
 &lt;td&gt;单播&lt;/td&gt;
 &lt;td&gt;组播 / 广播&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;bit 1（次低位）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;U/L&lt;/strong&gt; 位（Universal / Local）&lt;/td&gt;
 &lt;td&gt;全局唯一（OUI）&lt;/td&gt;
 &lt;td&gt;本地分配&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;记住几条推论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单播 MAC&lt;/strong&gt; 的 I/G 位是 0，目标是某一块网卡；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组播 MAC&lt;/strong&gt; 的 I/G 位是 1，目标是&amp;quot;一组&amp;quot;网卡；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广播地址&lt;/strong&gt; &lt;code&gt;FF:FF:FF:FF:FF:FF&lt;/code&gt; 是组播的特例（全 1），目标是本链路上所有设备；&lt;/li&gt;
&lt;li&gt;IPv4 组播映射到 MAC 时以 &lt;code&gt;01:00:5E:&lt;/code&gt; 开头，IPv6 组播以 &lt;code&gt;33:33:&lt;/code&gt; 开头——你抓包看到这两类前缀，就知道是组播流量。&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;你看到的虚拟机 / 容器 MAC 常以 &lt;code&gt;02:&lt;/code&gt; 开头，正是因为第二位被置 1（U/L = 本地），第一位保持 0（单播）。这是&amp;quot;本地单播&amp;quot;地址的典型长相。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="三以太网帧数据在链路上的信封"&gt;&lt;a href="#%e4%b8%89%e4%bb%a5%e5%a4%aa%e7%bd%91%e5%b8%a7%e6%95%b0%e6%8d%ae%e5%9c%a8%e9%93%be%e8%b7%af%e4%b8%8a%e7%9a%84%e4%bf%a1%e5%b0%81" class="header-anchor"&gt;&lt;/a&gt;三、以太网帧：数据在链路上的信封
&lt;/h2&gt;&lt;p&gt;一个以太网帧长这样（从左到右逐字节发送）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+--------+-----+---------+---------+--------+----------------+-------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|Preamble| SFD | Dst MAC | Src MAC |Type/Len| Payload | FCS |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 7 B | 1 B | 6 B | 6 B | 2 B | 46 ~ 1500 B | 4 B |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+--------+-----+---------+---------+--------+----------------+-------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 前导 + SFD 共 8 B，不计入帧长 从目的 MAC 到 FCS 才算帧：64 ~ 1518 B
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;逐字段说明：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;字段&lt;/th&gt;
 &lt;th&gt;大小&lt;/th&gt;
 &lt;th&gt;作用&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;前导 Preamble&lt;/td&gt;
 &lt;td&gt;7 字节&lt;/td&gt;
 &lt;td&gt;交替的 &lt;code&gt;1010...&lt;/code&gt; 比特，让接收方做时钟同步&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SFD&lt;/td&gt;
 &lt;td&gt;1 字节&lt;/td&gt;
 &lt;td&gt;帧起始定界符，&lt;code&gt;10101011&lt;/code&gt;，标志帧开始&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;目的 MAC&lt;/td&gt;
 &lt;td&gt;6 字节&lt;/td&gt;
 &lt;td&gt;接收方网卡地址&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;源 MAC&lt;/td&gt;
 &lt;td&gt;6 字节&lt;/td&gt;
 &lt;td&gt;发送方网卡地址&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;类型 / 长度&lt;/td&gt;
 &lt;td&gt;2 字节&lt;/td&gt;
 &lt;td&gt;见下文&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;载荷 Payload&lt;/td&gt;
 &lt;td&gt;46 ~ 1500 字节&lt;/td&gt;
 &lt;td&gt;上层来的数据（一般就是 IP 包）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;FCS&lt;/td&gt;
 &lt;td&gt;4 字节&lt;/td&gt;
 &lt;td&gt;CRC32 校验，错了整帧丢弃&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;几个要点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;前导和 SFD 不算帧长&lt;/strong&gt;。对经典、无 VLAN 标签的 Ethernet II 帧，“最小帧 64 字节”指的是从目的 MAC 到 FCS：&lt;code&gt;6+6+2+46+4 = 64&lt;/code&gt;；“最大帧 1518 字节”是 &lt;code&gt;6+6+2+1500+4 = 1518&lt;/code&gt;。802.1Q VLAN 标签会再增加 4 字节，一些网络还支持厂商相关的 Jumbo Frame。最小帧设 64，是历史原因——为了让冲突信号能在 CSMA/CD 里来得及传回发送方。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类型 / 长度的双关&lt;/strong&gt;：这个字段如果 &lt;code&gt;≤ 1500&lt;/code&gt; 表示&amp;quot;载荷长度&amp;quot;（IEEE 802.3 风格），如果 &lt;code&gt;≥ 1536（0x0600）&lt;/code&gt; 表示&amp;quot;上层协议类型&amp;quot;（Ethernet II 风格）。现代 IP 流量几乎都走 Ethernet II，所以你看到的都是类型。常见的 EtherType：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;EtherType&lt;/th&gt;
 &lt;th&gt;协议&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;0x0800&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;IPv4&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;0x0806&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;ARP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;0x86DD&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;IPv6&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;0x8100&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;802.1Q VLAN 标签&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;经典以太网常见的 &lt;strong&gt;MTU = 1500 字节&lt;/strong&gt;，指的是能够承载的网络层数据包上限。这是当前链路给网络层的约束：一个 IP 包（含 IP 头）超过该链路 MTU 就得想办法——下一篇会讲 IP 分片和 PMTUD。PPPoE、隧道或 overlay 会吃掉额外头部空间，Jumbo Frame 则可能提供更大的 MTU，实际值应以路径和接口配置为准。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FCS 只检错不纠错&lt;/strong&gt;。错了直接丢，网卡层面就丢了，上层（TCP）感知到丢包再重传。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四交换机-vs-集线器冲突域与广播域"&gt;&lt;a href="#%e5%9b%9b%e4%ba%a4%e6%8d%a2%e6%9c%ba-vs-%e9%9b%86%e7%ba%bf%e5%99%a8%e5%86%b2%e7%aa%81%e5%9f%9f%e4%b8%8e%e5%b9%bf%e6%92%ad%e5%9f%9f" class="header-anchor"&gt;&lt;/a&gt;四、交换机 vs 集线器：冲突域与广播域
&lt;/h2&gt;&lt;p&gt;这块容易混，先把两个概念钉死：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;冲突域&lt;/strong&gt;（collision domain）：共享同一介质、可能发生冲突的一组设备。集线器所有端口是同一个冲突域；交换机每个端口是独立的冲突域。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广播域&lt;/strong&gt;（broadcast domain）：一个广播帧能扩散到的范围。没划 VLAN 时，一台交换机的所有端口是同一个广播域；路由器能隔断广播域。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;集线器（hub）&lt;/strong&gt; 是物理层设备，本质是&amp;quot;中继器&amp;quot;：一个端口收到电信号，原样复制到所有其他端口。所以它&lt;strong&gt;不认 MAC、不认帧&lt;/strong&gt;，所有端口抢同一根介质，靠 CSMA/CD 避冲突。今天基本被淘汰。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;交换机（switch）&lt;/strong&gt; 是链路层设备，核心是一张 &lt;strong&gt;MAC 地址表&lt;/strong&gt;（也叫 CAM 表），记录&amp;quot;哪个端口号 ↔ 哪个 MAC&amp;quot;。转发逻辑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;收到一帧（端口 P，源 MAC = S，目的 MAC = D）：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 学习：把 (S → P) 记进 MAC 表（已有就刷新老化时间）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 转发：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 若 D 在表中，且对应的端口就是 P → 过滤，不回发（避免环路回弹）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 若 D 在表中，对应端口是 Q（≠ P）→ 只从 Q 发出去（单播转发）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 若 D 不在表中 → 泛洪（flood）：从除 P 外所有端口发出去
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 若 D 是广播 / 组播 → 泛洪（组播在高级交换机里有 IGMP snooping 精细化，先不展开）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以交换机的两个本事：&lt;strong&gt;靠源 MAC 学习，靠目的 MAC 转发&lt;/strong&gt;；查不到就泛洪兜底。这让它比集线器高效得多——大多数流量只走目标端口，不再全网广播。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一句话区分：集线器是&amp;quot;大喇叭&amp;quot;（同一冲突域、同一广播域），交换机是&amp;quot;分机总机&amp;quot;（每端口一个冲突域，全机一个广播域）。要切广播域，得上路由器或 VLAN。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五arpip-怎么翻译成-mac"&gt;&lt;a href="#%e4%ba%94arpip-%e6%80%8e%e4%b9%88%e7%bf%bb%e8%af%91%e6%88%90-mac" class="header-anchor"&gt;&lt;/a&gt;五、ARP：IP 怎么翻译成 MAC
&lt;/h2&gt;&lt;p&gt;现在问题来了：主机 A 要往同子网的主机 B 发一个 IP 包，A 知道 B 的 IP，但帧头要写的是 &lt;strong&gt;B 的 MAC&lt;/strong&gt;，A 一开始并不知道 B 的 MAC 是什么。这就靠 &lt;strong&gt;ARP&lt;/strong&gt;（Address Resolution Protocol，地址解析协议）。&lt;/p&gt;
&lt;p&gt;完整流程：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A（IP=10.0.0.2，MAC=a） ──→ B（IP=10.0.0.5，MAC=b） 同子网
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. A 查本地 ARP 缓存，没有 10.0.0.5 的条目
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. A 发一个 ARP 请求（ARP Request）：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 以太网帧目的 MAC = FF:FF:FF:FF:FF:FF（广播）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 内容：&amp;#34;谁有 10.0.0.5？把 MAC 告诉 10.0.0.2（MAC=a）&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 同子网所有主机都收到这个广播，但只有 B 发现&amp;#34;问的是我的 IP&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. B 回一个 ARP 应答（ARP Reply）：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 以太网帧目的 MAC = a（单播，只给 A）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - 内容：&amp;#34;10.0.0.5 的 MAC 是 b&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. A 把 (10.0.0.5 → b) 存进 ARP 缓存，带老化时间（几分钟到几十分钟不等，各系统不同）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6. 之后 A 直接用 b 当目的 MAC 发 IP 包，不再广播
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;两个细节值得注意：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;请求广播、应答单播&lt;/strong&gt;。请求是喊一嗓子（广播），应答是悄悄回（单播）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;跨子网时 ARP 找的是网关，不是目标&lt;/strong&gt;。如果 B 在另一个子网，A 不会去解析 B 的 MAC——A 会把 IP 包（目的 IP 仍是 B）封装成&lt;strong&gt;发给默认网关（路由器）MAC&lt;/strong&gt; 的帧。路由器收到后拆开 IP 头，再决定下一跳怎么转发，下一跳又是一次新的&amp;quot;ARP 找下一跳 MAC&amp;quot;。&lt;/p&gt;
&lt;p&gt;这正好呼应分层：&lt;strong&gt;IP 地址端到端不变（全程都是 B），MAC 地址逐跳变&lt;/strong&gt;（每一段链路换成当时那一跳的 MAC）。抓包时如果你看到 IP 目的始终是某台远端主机，而以太网源 / 目的 MAC 一段段在变，就是这个原因。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ARP 欺骗&lt;/strong&gt;（ARP spoofing）：因为 ARP 没有认证，谁都能冒充应答。攻击者发一个伪造的 ARP 应答，把&amp;quot;网关 IP&amp;quot;指向自己的 MAC，受害主机的流量就被骗到攻击者那里，形成中间人。防御手段：静态 ARP 绑定、交换机的动态 ARP 检测（DAI）、监控异常 MAC 翻转等。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六既然有了-ip为什么还要-mac"&gt;&lt;a href="#%e5%85%ad%e6%97%a2%e7%84%b6%e6%9c%89%e4%ba%86-ip%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%98%e8%a6%81-mac" class="header-anchor"&gt;&lt;/a&gt;六、既然有了 IP，为什么还要 MAC
&lt;/h2&gt;&lt;p&gt;这是学网络几乎人人会问的问题——既然 IP 也能定位主机，干嘛还弄一个 MAC？&lt;/p&gt;
&lt;p&gt;答案是&lt;strong&gt;分层分工&lt;/strong&gt;，两者解决的问题不在一个层面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MAC 标识一块网卡&lt;/strong&gt;：扁平、硬件级、不可路由，只在同一条链路上有意义。换了链路（比如从有线换无线）MAC 也跟着换。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP 标识主机在网络中的位置&lt;/strong&gt;：有结构、可聚合、可路由，跨链路全程有效。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打个比方：&lt;strong&gt;MAC 像身份证号，唯一但不告诉你人在哪；IP 像邮寄地址，有结构、能路由&lt;/strong&gt; 。寄一封信，信封上写的是地址（IP），但信件在每一站实际交接时，邮递员要看的是&amp;quot;这一段交给谁&amp;quot;（逐跳的 MAC）。&lt;/p&gt;
&lt;p&gt;另一个现实理由是&lt;strong&gt;软硬件解耦&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网卡坏了换一块（MAC 变了），IP 不用动，上层连接的逻辑不受影响；&lt;/li&gt;
&lt;li&gt;机器从这台物理机迁移到那台（虚拟化迁移），IP 可以保留，由新的网卡（新 MAC）承载。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果 IP 和硬件地址是同一个东西，这种灵活性就没了。分层让&amp;quot;网络位置（IP）&amp;ldquo;和&amp;quot;硬件身份（MAC）&amp;ldquo;独立变化，这正是分层架构的回报。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;链路层在&lt;strong&gt;相邻两节点之间&lt;/strong&gt;成帧、寻址、检错；不做可靠重传，交给上层；&lt;/li&gt;
&lt;li&gt;常见 &lt;strong&gt;MAC 地址为 48 位&lt;/strong&gt;，传统 MA-L 分配可理解为 OUI + 厂商分配部分；第一字节的 bit 0 是单播 / 组播位，bit 1 是全局 / 本地位；广播 = &lt;code&gt;FF:FF:FF:FF:FF:FF&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;经典无标签 &lt;strong&gt;Ethernet II 帧&lt;/strong&gt; = 前导 + SFD +（目的 / 源 MAC + 类型 + 载荷 + FCS），载荷 46 ~ 1500 字节，常见 &lt;strong&gt;MTU = 1500&lt;/strong&gt;；VLAN 和 Jumbo Frame 会改变边界；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交换机&lt;/strong&gt;靠源 MAC 学习、靠目的 MAC 转发，查不到就泛洪；每端口一个冲突域，全机一个广播域；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ARP&lt;/strong&gt; 用广播请求 + 单播应答把 IP 翻译成 MAC；跨子网时解析的是网关 MAC；&lt;strong&gt;IP 端到端不变，MAC 逐跳变&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇，我们升一层，进入网络层的核心——&lt;strong&gt;TCP/IP（三）：IP 协议——数据报、头部与分片&lt;/strong&gt; 。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/Ethernet_frame" target="_blank" rel="noopener"
 &gt;Ethernet frame — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/MAC_address" target="_blank" rel="noopener"
 &gt;MAC address — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/IEEE_802.3" target="_blank" rel="noopener"
 &gt;IEEE 802.3 — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc826" target="_blank" rel="noopener"
 &gt;RFC 826 — An Ethernet Address Resolution Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml" target="_blank" rel="noopener"
 &gt;IANA EtherType numbers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item><item><title>TCP/IP（一）：网络分层模型——OSI、TCP/IP 与五层模型</title><link>https://www.jiwei.space/posts/networking/tcp-ip/01-layered-model/</link><pubDate>Sun, 17 May 2026 10:00:00 +0800</pubDate><guid>https://www.jiwei.space/posts/networking/tcp-ip/01-layered-model/</guid><description>&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;网络是后端、云原生、分布式都绕不开的地基，可它偏偏是那种&amp;quot;天天用、却很少系统学&amp;quot;的东西——出问题时抓包看不懂、调内核参数不知道原理、被人问&amp;quot;TCP 为什么是三次握手&amp;quot;答不完整。&lt;/p&gt;
&lt;p&gt;这个系列从分层模型开始，自底向上把 TCP/IP 讲透。第一篇先回答最基础也最重要的问题：&lt;strong&gt;网络为什么要分层&lt;/strong&gt; ，以及 OSI、TCP/IP、平时说的&amp;quot;五层&amp;quot;这三种模型到底什么关系、怎么对应。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一为什么需要分层"&gt;&lt;a href="#%e4%b8%80%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81%e5%88%86%e5%b1%82" class="header-anchor"&gt;&lt;/a&gt;一、为什么需要分层
&lt;/h2&gt;&lt;p&gt;先想象不分层的网络是什么样：一个大程序要同时管电缆上的电信号、怎么给数据编址、怎么保证可靠、要不要加密、网页是什么格式……只要换一个环节（比如从有线换成无线），整个程序都得重写。&lt;/p&gt;
&lt;p&gt;分层把这套极复杂的问题&lt;strong&gt;切成互不干扰的几层&lt;/strong&gt;，每层只解决一件事，对上提供接口、对下使用下层服务。这样换来四个好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解耦&lt;/strong&gt; —— 换网卡、换链路（有线 / 无线）不影响上面的 HTTP；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复用&lt;/strong&gt; —— TCP 这一套可靠性机制，HTTP、SSH、数据库协议都能直接用；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标准化&lt;/strong&gt; —— 不同厂商的设备能互通，因为大家都按同一套分层与接口来实现；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可排查&lt;/strong&gt; —— 出问题时能按层定位（是物理断了？链路冲突？路由不通？端口被占？还是应用 bug？）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话：&lt;strong&gt;分层把&amp;quot;两台机器上的程序互相通信&amp;quot;这件极复杂的事，拆成若干个可独立设计、可替换的简单问题&lt;/strong&gt; 。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二osi-七层模型"&gt;&lt;a href="#%e4%ba%8cosi-%e4%b8%83%e5%b1%82%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;二、OSI 七层模型
&lt;/h2&gt;&lt;p&gt;OSI（Open Systems Interconnection）是 ISO 在 1984 年提出的&lt;strong&gt;参考模型&lt;/strong&gt;，把网络通信分成七层。它是&amp;quot;教科书标准&amp;quot;，概念清晰，但现实里的协议很少严格按七层实现。&lt;/p&gt;
&lt;p&gt;自底向上：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;层&lt;/th&gt;
 &lt;th&gt;名称&lt;/th&gt;
 &lt;th&gt;负责什么&lt;/th&gt;
 &lt;th&gt;典型协议 / 设备&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;应用层 Application&lt;/td&gt;
 &lt;td&gt;为用户程序提供网络服务接口&lt;/td&gt;
 &lt;td&gt;HTTP、DNS、SMTP、SSH&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;表示层 Presentation&lt;/td&gt;
 &lt;td&gt;数据格式转换、编码、加解密、压缩&lt;/td&gt;
 &lt;td&gt;TLS、JPEG、ASCII&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;会话层 Session&lt;/td&gt;
 &lt;td&gt;建立 / 管理 / 断开会话&lt;/td&gt;
 &lt;td&gt;（少有独立的会话层协议）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;传输层 Transport&lt;/td&gt;
 &lt;td&gt;端到端通信、可靠性与分段&lt;/td&gt;
 &lt;td&gt;TCP、UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;网络层 Network&lt;/td&gt;
 &lt;td&gt;跨网络寻址与路由&lt;/td&gt;
 &lt;td&gt;IP、ICMP、路由器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;数据链路层 Data Link&lt;/td&gt;
 &lt;td&gt;相邻节点间成帧、差错检测&lt;/td&gt;
 &lt;td&gt;以太网、Wi-Fi、交换机&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;物理层 Physical&lt;/td&gt;
 &lt;td&gt;比特在介质上的传输&lt;/td&gt;
 &lt;td&gt;电缆、光纤、集线器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;记忆顺序（自底向上）：物理、链路、网络、传输、会话、表示、应用。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一个常见疑问：会话层、表示层为什么&amp;quot;没什么存在感&amp;quot;？因为在工程实践中，它们的功能往往被应用层或 TLS 顺手做掉了，没有单独的协议实体。这也是 OSI 在工程上不如 TCP/IP 贴合实际的原因之一。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="三tcpip-四层模型"&gt;&lt;a href="#%e4%b8%89tcpip-%e5%9b%9b%e5%b1%82%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;三、TCP/IP 四层模型
&lt;/h2&gt;&lt;p&gt;TCP/IP 模型是伴随互联网（ARPANET）一起成长起来的&lt;strong&gt;工程模型&lt;/strong&gt;（也叫 DoD 模型），RFC 1122 把它划成四层。它&lt;strong&gt;先有协议、再归纳模型&lt;/strong&gt;，所以和实际协议贴得很紧——这一点和 OSI 正好相反。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;TCP/IP 层&lt;/th&gt;
 &lt;th&gt;对应 OSI&lt;/th&gt;
 &lt;th&gt;负责什么&lt;/th&gt;
 &lt;th&gt;典型协议&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;应用层 Application&lt;/td&gt;
 &lt;td&gt;5 / 6 / 7&lt;/td&gt;
 &lt;td&gt;应用逻辑 + 表示 + 会话全部合并&lt;/td&gt;
 &lt;td&gt;HTTP、DNS、SMTP、TLS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;传输层 Transport&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;端到端通信（端口、可靠性）&lt;/td&gt;
 &lt;td&gt;TCP、UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;网际层 Internet&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;跨网寻址与路由&lt;/td&gt;
 &lt;td&gt;IP、ICMP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;网络接口层 Network Access（Link）&lt;/td&gt;
 &lt;td&gt;1 / 2&lt;/td&gt;
 &lt;td&gt;物理传输 + 成帧&lt;/td&gt;
 &lt;td&gt;以太网、Wi-Fi、PPP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最容易混的是命名：TCP/IP 的&lt;strong&gt;网际层（Internet layer）对应 OSI 的网络层&lt;/strong&gt;，别和&amp;quot;网络接口层&amp;quot;搞混——前者管 IP 路由，后者管链路成帧。最底下那层在不同教材里叫法不一（Network Access / Link / Network Interface），指的都是同一层。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四实际用的五层模型"&gt;&lt;a href="#%e5%9b%9b%e5%ae%9e%e9%99%85%e7%94%a8%e7%9a%84%e4%ba%94%e5%b1%82%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;四、实际用的五层模型
&lt;/h2&gt;&lt;p&gt;OSI 太细（会话 / 表示层实际很少单独存在），TCP/IP 太粗（把物理和链路合在一起，不好分开讲）。教学和工程里常用一个折中的&lt;strong&gt;五层模型&lt;/strong&gt;，既好理解，又能一一对应到真实协议：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;五层&lt;/th&gt;
 &lt;th&gt;PDU 名称&lt;/th&gt;
 &lt;th&gt;典型协议 / 设备&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;应用层&lt;/td&gt;
 &lt;td&gt;报文 Message / Data&lt;/td&gt;
 &lt;td&gt;HTTP、DNS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;传输层&lt;/td&gt;
 &lt;td&gt;段 Segment（TCP）/ 数据报 Datagram（UDP）&lt;/td&gt;
 &lt;td&gt;TCP、UDP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;网络层&lt;/td&gt;
 &lt;td&gt;包 Packet（IP 数据报）&lt;/td&gt;
 &lt;td&gt;IP、ICMP、路由器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;数据链路层&lt;/td&gt;
 &lt;td&gt;帧 Frame&lt;/td&gt;
 &lt;td&gt;以太网、Wi-Fi、交换机&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;物理层&lt;/td&gt;
 &lt;td&gt;比特 Bit&lt;/td&gt;
 &lt;td&gt;电缆、光纤、集线器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;本系列后面的篇幅就按五层来展开（链路 → 网络 → 传输 → 应用），既保留 TCP/IP 的工程视角，又不丢 OSI 的概念清晰度。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;三者怎么对应？一句话：&lt;strong&gt;TCP/IP 把 OSI 的 5/6/7 合成应用层、把 1/2 合成网络接口层；五层模型再把 TCP/IP 的网络接口层拆回物理和链路&lt;/strong&gt; 。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="五封装与解封装数据是怎么一层层穿上的"&gt;&lt;a href="#%e4%ba%94%e5%b0%81%e8%a3%85%e4%b8%8e%e8%a7%a3%e5%b0%81%e8%a3%85%e6%95%b0%e6%8d%ae%e6%98%af%e6%80%8e%e4%b9%88%e4%b8%80%e5%b1%82%e5%b1%82%e7%a9%bf%e4%b8%8a%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;五、封装与解封装：数据是怎么一层层&amp;quot;穿上&amp;quot;的
&lt;/h2&gt;&lt;p&gt;分层不是&amp;quot;各干各的就完了&amp;quot;——数据发送时，每一层都会&lt;strong&gt;在上层数据外面套一个自己的头部&lt;/strong&gt;（链路层还会加尾部），这叫封装（encapsulation）；接收端再逐层拆掉，叫解封装。&lt;/p&gt;
&lt;p&gt;一次 HTTP 请求从你的机器发出去，要经历：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用层： 生成 HTTP 报文
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ + TCP 头部（源端口 / 目的端口 / 序号 …）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;传输层： TCP 段（Segment）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ + IP 头部（源 IP / 目的 IP / TTL …）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;网络层： IP 包（Packet）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ + 帧头（源 MAC / 目的 MAC）+ 帧尾（FCS）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;链路层： 以太网帧（Frame）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 转成比特流
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;物理层： 比特（Bit）经网线 / 光纤发出
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每一层只认自己的头部，不关心（也看不懂）上层的载荷——这就是&amp;quot;解耦&amp;quot;在数据层面的体现。比如路由器只看到网络层的 IP 头就能决定往哪转发，完全不必读懂 TCP 或 HTTP。&lt;/p&gt;
&lt;p&gt;这里出现的 PDU（Protocol Data Unit，协议数据单元）就是&lt;strong&gt;每一层处理后产生的数据单元&lt;/strong&gt;：应用层叫报文、传输层叫段 / 数据报、网络层叫包、链路层叫帧、物理层叫比特。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六每层靠什么认人地址"&gt;&lt;a href="#%e5%85%ad%e6%af%8f%e5%b1%82%e9%9d%a0%e4%bb%80%e4%b9%88%e8%ae%a4%e4%ba%ba%e5%9c%b0%e5%9d%80" class="header-anchor"&gt;&lt;/a&gt;六、每层靠什么&amp;quot;认人&amp;quot;：地址
&lt;/h2&gt;&lt;p&gt;各层有各自的寻址方式，互不替代：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;层&lt;/th&gt;
 &lt;th&gt;地址&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;传输层&lt;/td&gt;
 &lt;td&gt;端口号&lt;/td&gt;
 &lt;td&gt;区分同一台机器上的不同进程（如 80 = HTTP）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;网络层&lt;/td&gt;
 &lt;td&gt;IP 地址&lt;/td&gt;
 &lt;td&gt;跨网络定位主机，可路由&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;链路层&lt;/td&gt;
 &lt;td&gt;MAC 地址&lt;/td&gt;
 &lt;td&gt;同一链路上定位网卡，出厂固化&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;后面几篇会分别展开：IP 地址怎么划分、路由怎么走（网络层），TCP 怎么用端口建立连接（传输层），ARP 怎么把 IP 翻译成 MAC（链路层）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;分层是为了&lt;strong&gt;解耦、复用、标准化、可排查&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OSI 七层&lt;/strong&gt;是概念参考模型，&lt;strong&gt;TCP/IP 四层&lt;/strong&gt;贴合实际协议，&lt;strong&gt;五层模型&lt;/strong&gt;是教学折中；&lt;/li&gt;
&lt;li&gt;数据发送时&lt;strong&gt;逐层封装&lt;/strong&gt;（加头 / 尾），接收时&lt;strong&gt;逐层解封装&lt;/strong&gt;；每层都有自己的 PDU 名称和地址体系。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一篇：&lt;strong&gt;TCP/IP（二）：以太网与链路层&lt;/strong&gt; —— MAC 地址、帧结构、交换机怎么转发、ARP 怎么把 IP 变成 MAC。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/OSI_model" target="_blank" rel="noopener"
 &gt;OSI model — Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.rfc-editor.org/rfc/rfc1122" target="_blank" rel="noopener"
 &gt;Requirements for Internet Hosts (RFC 1122)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.fortinet.com/resources/cyberglossary/tcp-ip-model-vs-osi-model" target="_blank" rel="noopener"
 &gt;TCP/IP Model vs. OSI Model — Fortinet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;/blockquote&gt;</description></item></channel></rss>