<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>网络 on 吾爱主机</title><link>https://blog.waihost.com/categories/%E7%BD%91%E7%BB%9C/</link><description>Recent content in 网络 on 吾爱主机</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 03 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.waihost.com/categories/%E7%BD%91%E7%BB%9C/index.xml" rel="self" type="application/rss+xml"/><item><title>DNS 递归解析与缓存：TTL、负缓存与 dig 排障</title><link>https://blog.waihost.com/posts/dns-recursive-resolution-cache-troubleshooting/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/dns-recursive-resolution-cache-troubleshooting/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/dns-recursive-resolution-cache-troubleshooting.svg" alt="Featured image of post DNS 递归解析与缓存：TTL、负缓存与 dig 排障" /&gt;&lt;p&gt;浏览器访问一个域名时，应用通常只发出一次 DNS 查询，但这不等于某台服务器“一次查完全部答案”。在常见架构中，本机的存根解析器把问题交给递归解析器；递归解析器先查缓存，未命中时再从根、顶级域到权威服务器逐级追踪委派关系。&lt;/p&gt;
&lt;p&gt;理解这条链路，才能解释许多看似矛盾的现象：记录已经修改，为什么有些用户仍看到旧地址？域名明明存在，为什么查询某种类型却没有答案？为什么 &lt;code&gt;dig +trace&lt;/code&gt; 正常，业务使用的 DNS 却返回 &lt;code&gt;SERVFAIL&lt;/code&gt;？本文从协议机制出发，给出可复现的观察方法和分层排障流程。&lt;/p&gt;
&lt;h2 id="一先分清四个角色"&gt;&lt;a href="#%e4%b8%80%e5%85%88%e5%88%86%e6%b8%85%e5%9b%9b%e4%b8%aa%e8%a7%92%e8%89%b2" 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;存根解析器（stub resolver）&lt;/td&gt;
					&lt;td&gt;接收应用请求并转交给配置的 DNS 服务器&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、运营商 DNS、公共 DNS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;根与 TLD 服务器&lt;/td&gt;
					&lt;td&gt;返回下一层权威服务器的委派信息&lt;/td&gt;
					&lt;td&gt;根区、&lt;code&gt;.com&lt;/code&gt;、&lt;code&gt;.cn&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;域名托管商或自建 DNS&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RFC 1034 区分了递归与迭代：递归服务要么返回最终答案，要么返回错误，不把委派直接丢给普通客户端；迭代查询则允许服务器返回“下一步去问谁”的 referral。递归解析器正是通过多次迭代查询完成客户端委托。&lt;/p&gt;
&lt;p&gt;以查询 &lt;code&gt;www.example.com A&lt;/code&gt; 为例，缓存全部未命中时，路径可抽象为：&lt;/p&gt;
&lt;div class="highlight"&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; -&amp;gt; 存根解析器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 递归解析器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 根服务器：去问 .com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; .com 服务器：去问 example.com 的权威服务器
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; 权威服务器：返回 www.example.com 的 A 记录
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;- 缓存结果并把答案返回应用
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;委派响应通常在 Authority 区给出 NS 记录；必要时，Additional 区还会附带 glue 地址，避免为了寻找某个名字服务器的地址而形成解析循环。若目标是 CNAME，解析器还要继续解析其目标名称，因此别把“查到 CNAME”误认为已经获得可连接的 IP。&lt;/p&gt;
&lt;h2 id="二缓存的核心不是有或没有而是剩余-ttl"&gt;&lt;a href="#%e4%ba%8c%e7%bc%93%e5%ad%98%e7%9a%84%e6%a0%b8%e5%bf%83%e4%b8%8d%e6%98%af%e6%9c%89%e6%88%96%e6%b2%a1%e6%9c%89%e8%80%8c%e6%98%af%e5%89%a9%e4%bd%99-ttl" class="header-anchor"&gt;&lt;/a&gt;二、缓存的核心不是“有或没有”，而是剩余 TTL
&lt;/h2&gt;&lt;p&gt;资源记录中的 TTL 以秒为单位，表示记录可以在缓存中保留多久。递归解析器返回缓存答案时，客户端看到的通常是递减后的剩余 TTL，而不是权威区文件里的初始值。&lt;/p&gt;
&lt;p&gt;可以连续观察：&lt;/p&gt;
&lt;div class="highlight"&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 @1.1.1.1 example.com A +noall +answer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sleep &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig @1.1.1.1 example.com A +noall +answer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果两次命中同一缓存节点，第二次 TTL 通常会减少约 10 秒；若中途刷新、负载均衡到另一节点或记录被预取，数值也可能跳变。TTL 归零后表示该缓存副本不应继续使用，但并不意味着全世界会在同一时刻刷新：不同递归解析器的首次查询时间不同，各自的过期时刻也不同。&lt;/p&gt;
&lt;p&gt;因此，变更记录时应把传播过程理解为“旧缓存逐个过期”，而不是权威服务器主动推送。工程上可以在计划变更前至少一个旧 TTL 周期先降低 TTL，等待旧缓存自然淘汰后再切换地址；完成并稳定运行后再恢复 TTL。临时改低 TTL 无法让已经缓存的高 TTL 记录提前失效。&lt;/p&gt;
&lt;p&gt;缓存还可能存在于多个层次：&lt;/p&gt;
&lt;div class="highlight"&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;应用内部 -&amp;gt; 操作系统 -&amp;gt; 本地缓存服务 -&amp;gt; 企业/运营商递归解析器
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;只清理浏览器缓存不一定有效；反过来，公共递归解析器已更新，也不代表长连接、连接池或应用自己的地址缓存已经更新。排障时必须明确每次查询实际经过哪一层。&lt;/p&gt;
&lt;h2 id="三负缓存不存在的答案也会被记住"&gt;&lt;a href="#%e4%b8%89%e8%b4%9f%e7%bc%93%e5%ad%98%e4%b8%8d%e5%ad%98%e5%9c%a8%e7%9a%84%e7%ad%94%e6%a1%88%e4%b9%9f%e4%bc%9a%e8%a2%ab%e8%ae%b0%e4%bd%8f" class="header-anchor"&gt;&lt;/a&gt;三、负缓存：不存在的答案也会被记住
&lt;/h2&gt;&lt;p&gt;DNS 不只缓存成功答案。RFC 2308 定义了负缓存，并区分两个常被混淆的结果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NXDOMAIN&lt;/strong&gt;：查询的名称不存在；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NODATA&lt;/strong&gt;：名称存在，但没有所查询类型的记录。它不是单独的 DNS RCODE，需要根据响应内容判断。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，某名称有 &lt;code&gt;A&lt;/code&gt; 记录但没有 &lt;code&gt;AAAA&lt;/code&gt; 记录，查询 AAAA 可能得到 NODATA，而不是 NXDOMAIN。把两者混为一谈，会误导监控和故障处理。&lt;/p&gt;
&lt;p&gt;权威服务器返回负答案时，会在 Authority 区携带区域 SOA。按照 RFC 2308，负缓存 TTL 取 SOA 记录自身 TTL 与 SOA.MINIMUM 字段中的较小值，并像普通缓存 TTL 一样递减；到零后该负答案不能继续使用。这解释了一个常见故障：先查询了尚未创建的名称，递归解析器缓存 NXDOMAIN，随后即使权威端补上记录，部分客户端仍要等负缓存过期。&lt;/p&gt;
&lt;p&gt;观察负答案时不要只看 Answer 区：&lt;/p&gt;
&lt;div class="highlight"&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 @1.1.1.1 does-not-exist.example.com A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;重点检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;status&lt;/code&gt; 是 &lt;code&gt;NXDOMAIN&lt;/code&gt;、&lt;code&gt;NOERROR&lt;/code&gt; 还是 &lt;code&gt;SERVFAIL&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;Authority 区是否有 SOA；&lt;/li&gt;
&lt;li&gt;SOA 的剩余 TTL 是否递减；&lt;/li&gt;
&lt;li&gt;同名查询其他类型时结果是否不同。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;SERVFAIL&lt;/code&gt; 与 NXDOMAIN 含义不同。前者表示服务器未能完成处理，可能来自上游超时、DNSSEC 验证失败、委派错误或权威服务器不可达；不要把它解释成“域名不存在”，也不要用创建一条记录来盲目修复。&lt;/p&gt;
&lt;h2 id="四用-dig-分层观察而不是只换一个公共-dns"&gt;&lt;a href="#%e5%9b%9b%e7%94%a8-dig-%e5%88%86%e5%b1%82%e8%a7%82%e5%af%9f%e8%80%8c%e4%b8%8d%e6%98%af%e5%8f%aa%e6%8d%a2%e4%b8%80%e4%b8%aa%e5%85%ac%e5%85%b1-dns" class="header-anchor"&gt;&lt;/a&gt;四、用 dig 分层观察，而不是只换一个公共 DNS
&lt;/h2&gt;&lt;h3 id="1-查询系统实际使用的递归解析器"&gt;&lt;a href="#1-%e6%9f%a5%e8%af%a2%e7%b3%bb%e7%bb%9f%e5%ae%9e%e9%99%85%e4%bd%bf%e7%94%a8%e7%9a%84%e9%80%92%e5%bd%92%e8%a7%a3%e6%9e%90%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;1. 查询系统实际使用的递归解析器
&lt;/h3&gt;&lt;div class="highlight"&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 example.com A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;输出中的 &lt;code&gt;SERVER&lt;/code&gt; 表明请求发给了谁；标志位中的 &lt;code&gt;rd&lt;/code&gt; 表示客户端请求递归，&lt;code&gt;ra&lt;/code&gt; 表示服务器声明可提供递归。注意，&lt;code&gt;ra&lt;/code&gt; 是能力声明，不代表这一次一定完成了完整递归过程，答案也可能直接来自缓存。&lt;/p&gt;
&lt;h3 id="2-对比不同递归解析器"&gt;&lt;a href="#2-%e5%af%b9%e6%af%94%e4%b8%8d%e5%90%8c%e9%80%92%e5%bd%92%e8%a7%a3%e6%9e%90%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;2. 对比不同递归解析器
&lt;/h3&gt;&lt;div class="highlight"&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 @1.1.1.1 example.com A +noall +answer +comments
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig @8.8.8.8 example.com A +noall +answer +comments
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;若答案或剩余 TTL 不同，先考虑缓存时间点、地理调度和 EDNS Client Subnet 等差异，不要立刻断定某个解析器“错误”。&lt;/p&gt;
&lt;h3 id="3-直接询问权威服务器"&gt;&lt;a href="#3-%e7%9b%b4%e6%8e%a5%e8%af%a2%e9%97%ae%e6%9d%83%e5%a8%81%e6%9c%8d%e5%8a%a1%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;3. 直接询问权威服务器
&lt;/h3&gt;&lt;p&gt;先找权威 NS，再向其中一台发非递归查询：&lt;/p&gt;
&lt;div class="highlight"&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 example.com NS +short
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig @a.iana-servers.net example.com A +norecurse
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ISC BIND 的 &lt;code&gt;dig&lt;/code&gt; 文档说明，&lt;code&gt;+norecurse&lt;/code&gt; 会清除查询中的 RD 位。直接比较权威答案和递归答案，可以判断问题在权威配置还是缓存层。若多个权威节点答案不一致，应检查区文件发布、序列号和节点同步，而不是等待递归缓存自行“修好”。&lt;/p&gt;
&lt;h3 id="4-用-trace-重放委派链"&gt;&lt;a href="#4-%e7%94%a8-trace-%e9%87%8d%e6%94%be%e5%a7%94%e6%b4%be%e9%93%be" class="header-anchor"&gt;&lt;/a&gt;4. 用 trace 重放委派链
&lt;/h3&gt;&lt;div class="highlight"&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 +trace example.com A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;+trace&lt;/code&gt; 从根服务器开始按委派向下查询，并自动关闭递归请求。它适合发现缺失 glue、错误 NS、某层委派不一致等问题。但它绕开了业务正在使用的递归解析器，因此“trace 成功”只能说明从当前主机进行迭代追踪大体可行，不能证明企业 DNS 的缓存、转发器或 DNSSEC 验证链没有故障。&lt;/p&gt;
&lt;h3 id="5-必要时强制-tcp"&gt;&lt;a href="#5-%e5%bf%85%e8%a6%81%e6%97%b6%e5%bc%ba%e5%88%b6-tcp" class="header-anchor"&gt;&lt;/a&gt;5. 必要时强制 TCP
&lt;/h3&gt;&lt;div class="highlight"&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 +tcp example.com A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当响应被截断、UDP 路径受限或怀疑防火墙只放行了一种传输时，对比 UDP 与 TCP 很有价值。不要沿用“DNS 永远只用 UDP 53”的排障假设；生产网络应同时验证所需的 UDP/TCP 53 路径。&lt;/p&gt;
&lt;h2 id="五一个可执行的排障顺序"&gt;&lt;a href="#%e4%ba%94%e4%b8%80%e4%b8%aa%e5%8f%af%e6%89%a7%e8%a1%8c%e7%9a%84%e6%8e%92%e9%9a%9c%e9%a1%ba%e5%ba%8f" 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;：保存查询名、类型、时间、客户端网络、&lt;code&gt;SERVER&lt;/code&gt;、状态码、flags 和完整响应；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确认名称与类型&lt;/strong&gt;：A、AAAA、CNAME、TXT 是不同问题，NXDOMAIN 与 NODATA 也不同；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;比较递归与权威&lt;/strong&gt;：权威正确而递归旧，多半是缓存；权威节点之间不同，则先修权威发布；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检查 TTL&lt;/strong&gt;：同时查看正答案和 SOA 驱动的负缓存，不要只看控制台显示的配置值；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;追踪委派&lt;/strong&gt;：用 &lt;code&gt;+trace&lt;/code&gt; 检查根到权威的链路，并核对父区 NS/glue 与子区实际服务；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;比较网络与传输&lt;/strong&gt;：更换客户端网络，分别测试 UDP/TCP，检查防火墙、NAT 和 MTU；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最后再清缓存&lt;/strong&gt;：清理动作要针对真正缓存旧结果的层，且应保留清理前证据。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一个实用的证据包可以这样采集：&lt;/p&gt;
&lt;div class="highlight"&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="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;example.com
&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; date -u
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dig &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dig &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; AAAA
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dig &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; NS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dig +trace &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dig +tcp &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt; &amp;gt; dns-debug.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;生产环境还应把实际递归服务器地址、权威节点查询结果和变更时间线加入证据包。这样即使故障已经因 TTL 到期而消失，也能回溯当时究竟是旧缓存、负缓存、委派还是网络路径问题。&lt;/p&gt;
&lt;h2 id="六工程设计建议"&gt;&lt;a href="#%e5%85%ad%e5%b7%a5%e7%a8%8b%e8%ae%be%e8%ae%a1%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;六、工程设计建议
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TTL 是一致性与查询成本的权衡&lt;/strong&gt;：低 TTL 缩短变更窗口，但会增加权威查询量，并不能替代健康检查与故障转移设计；&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;：既从公共递归解析器查询，也直接检查每个权威节点；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要依赖单一记录类型&lt;/strong&gt;：双栈环境同时监控 A 与 AAAA，防止 IPv4 正常而 IPv6 路径故障；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;区分控制面与数据面&lt;/strong&gt;：托管控制台显示“已保存”只证明配置已提交，不等于所有权威节点、递归缓存和客户端都已收敛。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;DNS 故障的关键不是记住更多刷新缓存命令，而是建立分层模型：存根解析器把请求交给递归解析器，递归解析器通过 referral 逐级找到权威答案，再按 TTL 缓存；不存在的名称或类型也可能通过 SOA 形成负缓存。排障时把权威、递归、客户端缓存和网络传输逐层拆开，并结合 &lt;code&gt;status&lt;/code&gt;、flags、Authority 区与剩余 TTL 取证，通常能比“反复切换 DNS”更快定位根因。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&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://www.rfc-editor.org/rfc/rfc2308" target="_blank" rel="noopener"
 &gt;RFC 2308：Negative Caching of DNS Queries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://bind9.readthedocs.io/en/latest/manpages.html#dig-dns-lookup-utility" target="_blank" rel="noopener"
 &gt;ISC BIND 9 Manual Pages：dig DNS lookup utility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Linux epoll I/O 多路复用：水平触发、边缘触发与工程实践</title><link>https://blog.waihost.com/posts/linux-epoll-io-multiplexing-lt-et-practice/</link><pubDate>Tue, 28 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/linux-epoll-io-multiplexing-lt-et-practice/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/linux-epoll-io-multiplexing-lt-et-practice.svg" alt="Featured image of post Linux epoll I/O 多路复用：水平触发、边缘触发与工程实践" /&gt;&lt;p&gt;高并发服务里最常见的一类卡顿不是“CPU 不够”，而是 &lt;strong&gt;大量连接上只有少量真正可读/可写&lt;/strong&gt;。若对每个连接开线程阻塞 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;，上下文切换与内存开销会迅速失控；若用轮询扫所有 fd，又会把 CPU 烧掉。Linux 提供的 &lt;strong&gt;epoll&lt;/strong&gt; 就是为“只在就绪时被唤醒”设计的 I/O 事件通知机制。&lt;/p&gt;
&lt;p&gt;本文基于 Linux man-pages 的 &lt;code&gt;epoll(7)&lt;/code&gt;、&lt;code&gt;epoll_create(2)&lt;/code&gt;/&lt;code&gt;epoll_create1(2)&lt;/code&gt;、&lt;code&gt;epoll_ctl(2)&lt;/code&gt;、&lt;code&gt;epoll_wait(2)&lt;/code&gt;，并结合 Python &lt;code&gt;selectors&lt;/code&gt; 与 Nginx 连接处理文档，把 &lt;strong&gt;interest/ready 两张表、LT/ET 语义、ONESHOT、非阻塞与 EAGAIN、常见坑与排障&lt;/strong&gt; 讲清楚，方便你在自研事件循环或排查 Nginx/Redis 类服务时按机制定位。&lt;/p&gt;
&lt;h2 id="一问题背景为什么-selectpoll-会长不大"&gt;&lt;a href="#%e4%b8%80%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e4%b8%ba%e4%bb%80%e4%b9%88-selectpoll-%e4%bc%9a%e9%95%bf%e4%b8%8d%e5%a4%a7" class="header-anchor"&gt;&lt;/a&gt;一、问题背景：为什么 select/poll 会“长不大”
&lt;/h2&gt;&lt;p&gt;传统 &lt;code&gt;select(2)&lt;/code&gt; / &lt;code&gt;poll(2)&lt;/code&gt; 也能多路复用，但工程上有两个长期痛点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每次调用都要反复提交关注集合&lt;/strong&gt;。调用者把“我关心哪些 fd、关心读还是写”整包传给内核；内核再线性扫描。连接数上来后，用户态↔内核态的拷贝与扫描成本随 N 增长。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;就绪集合表达方式不友好&lt;/strong&gt;。&lt;code&gt;select&lt;/code&gt; 还有历史性的 fd 数量上限（与 &lt;code&gt;FD_SETSIZE&lt;/code&gt; 相关），大规模长连接场景很难优雅扩展。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;epoll 的定位不同：它在内核里维护一个 &lt;strong&gt;epoll 实例&lt;/strong&gt;（本质是“容器 + 回调”），应用 &lt;strong&gt;只注册一次兴趣&lt;/strong&gt;，之后用 &lt;code&gt;epoll_wait&lt;/code&gt; &lt;strong&gt;拉取当前就绪事件&lt;/strong&gt;。man 页明确指出：epoll 既可作 &lt;strong&gt;水平触发（level-triggered, LT）&lt;/strong&gt; 也可作 &lt;strong&gt;边缘触发（edge-triggered, ET）&lt;/strong&gt;，并且 &lt;strong&gt;能较好扩展到大量被监视的文件描述符&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="二核心机制interest-list-与-ready-list"&gt;&lt;a href="#%e4%ba%8c%e6%a0%b8%e5%bf%83%e6%9c%ba%e5%88%b6interest-list-%e4%b8%8e-ready-list" class="header-anchor"&gt;&lt;/a&gt;二、核心机制：interest list 与 ready list
&lt;/h2&gt;&lt;p&gt;从用户视角看，一个 epoll 实例可理解为两张表（&lt;code&gt;epoll(7)&lt;/code&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;interest list&lt;/strong&gt;（epoll set）&lt;/td&gt;
					&lt;td&gt;进程通过 &lt;code&gt;epoll_ctl&lt;/code&gt; 注册、希望监视的 fd 集合&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ready list&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;interest list 的子集（更准确：其中“就绪”的引用集合），由内核因 I/O 活动动态填充&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&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;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_create&lt;/code&gt; / &lt;code&gt;epoll_create1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;创建 epoll 实例，返回 epoll 自身的 fd&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_ctl&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;对 interest list 做 &lt;code&gt;ADD&lt;/code&gt; / &lt;code&gt;MOD&lt;/code&gt; / &lt;code&gt;DEL&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_wait&lt;/code&gt; / &lt;code&gt;epoll_pwait&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;从 ready list 取回最多 &lt;code&gt;maxevents&lt;/code&gt; 个事件；无事件时可阻塞&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="21-创建实例优先-epoll_create1"&gt;&lt;a href="#21-%e5%88%9b%e5%bb%ba%e5%ae%9e%e4%be%8b%e4%bc%98%e5%85%88-epoll_create1" class="header-anchor"&gt;&lt;/a&gt;2.1 创建实例：优先 &lt;code&gt;epoll_create1&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;epoll_create(size)&lt;/code&gt; 在 Linux 2.6.8 之后 &lt;strong&gt;忽略 size&lt;/strong&gt;（但仍要求 &lt;code&gt;size &amp;gt; 0&lt;/code&gt;）。更推荐 &lt;code&gt;epoll_create1(flags)&lt;/code&gt;：可传 &lt;code&gt;EPOLL_CLOEXEC&lt;/code&gt;，避免 &lt;code&gt;fork&lt;/code&gt;+&lt;code&gt;exec&lt;/code&gt; 时泄漏 epoll fd（与 &lt;code&gt;open(2)&lt;/code&gt; 的 &lt;code&gt;O_CLOEXEC&lt;/code&gt; 同理）。&lt;/p&gt;
&lt;h3 id="22-注册兴趣epoll_ctl-与事件掩码"&gt;&lt;a href="#22-%e6%b3%a8%e5%86%8c%e5%85%b4%e8%b6%a3epoll_ctl-%e4%b8%8e%e4%ba%8b%e4%bb%b6%e6%8e%a9%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;2.2 注册兴趣：&lt;code&gt;epoll_ctl&lt;/code&gt; 与事件掩码
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* EPOLLIN | EPOLLOUT | ... */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;epoll_data_t&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* 用户数据：常存 fd 或指针 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&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="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;常用 &lt;code&gt;op&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EPOLL_CTL_ADD&lt;/code&gt;：加入 interest list&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EPOLL_CTL_MOD&lt;/code&gt;：修改已有项的事件掩码/用户数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EPOLL_CTL_DEL&lt;/code&gt;：从 interest list 删除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见事件位（&lt;code&gt;epoll_ctl(2)&lt;/code&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;code&gt;EPOLLIN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可读（含对端数据、监听 socket 有新连接等）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLOUT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;可写&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLRDHUP&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;流式套接字对端关闭或关闭写半部（2.6.17+，ET 下检测对端关闭很有用）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLPRI&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;带外/异常条件（语义见 &lt;code&gt;poll(2)&lt;/code&gt; 的 &lt;code&gt;POLLPRI&lt;/code&gt;）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLERR&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;错误；&lt;strong&gt;无需&lt;/strong&gt;在 &lt;code&gt;events&lt;/code&gt; 里显式设置，&lt;code&gt;epoll_wait&lt;/code&gt; 总会报告&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLHUP&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;hang up；同样 &lt;strong&gt;总会报告&lt;/strong&gt;，不必手动设置&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLET&lt;/code&gt;&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;code&gt;EPOLLONESHOT&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;通知一次后自动禁用该 fd，需 &lt;code&gt;MOD&lt;/code&gt; 重新武装&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;EPOLLEXCLUSIVE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多 epoll 监听同一 fd 时的互斥唤醒（4.5+）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;工程建议：&lt;strong&gt;监听 socket 与连接 socket 都建议设为非阻塞&lt;/strong&gt;；读写方向若会频繁切换，可一次 &lt;code&gt;ADD&lt;/code&gt; 时就挂 &lt;code&gt;EPOLLIN|EPOLLOUT&lt;/code&gt;，避免来回 &lt;code&gt;MOD&lt;/code&gt; 切换。&lt;/p&gt;
&lt;h3 id="23-等待就绪epoll_wait"&gt;&lt;a href="#23-%e7%ad%89%e5%be%85%e5%b0%b1%e7%bb%aaepoll_wait" class="header-anchor"&gt;&lt;/a&gt;2.3 等待就绪：&lt;code&gt;epoll_wait&lt;/code&gt;
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxevents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;maxevents&lt;/code&gt; 必须 &lt;strong&gt;&amp;gt; 0&lt;/strong&gt;，表示输出数组容量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeout&lt;/code&gt; 单位毫秒：&lt;code&gt;0&lt;/code&gt; 立即返回；&lt;code&gt;-1&lt;/code&gt; 一直阻塞；正数限时等待。时间基准为 &lt;code&gt;CLOCK_MONOTONIC&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;返回值：就绪事件个数；&lt;code&gt;0&lt;/code&gt; 表示超时；&lt;code&gt;-1&lt;/code&gt; 出错（常见 &lt;code&gt;EINTR&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;返回的 &lt;code&gt;events[i].events&lt;/code&gt; 是 &lt;strong&gt;实际发生&lt;/strong&gt; 的事件掩码；&lt;code&gt;data&lt;/code&gt; 原样带回你在 &lt;code&gt;ctl&lt;/code&gt; 时写入的用户数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 的返回策略还会尽量避免 &lt;strong&gt;饥饿&lt;/strong&gt;：若某次只处理“已经知道就绪”的一小撮 fd，而忽略其他已就绪 fd，系统可能长期“看不见”后者。应用层也应做 &lt;strong&gt;就绪队列 + 轮转处理&lt;/strong&gt;，而不是在单次事件上无限 &lt;code&gt;read&lt;/code&gt; 到饿死其它连接（man 页 “Starvation” 小节专门提醒）。&lt;/p&gt;
&lt;h2 id="三水平触发-vs-边缘触发语义决定写法"&gt;&lt;a href="#%e4%b8%89%e6%b0%b4%e5%b9%b3%e8%a7%a6%e5%8f%91-vs-%e8%be%b9%e7%bc%98%e8%a7%a6%e5%8f%91%e8%af%ad%e4%b9%89%e5%86%b3%e5%ae%9a%e5%86%99%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;三、水平触发 vs 边缘触发：语义决定写法
&lt;/h2&gt;&lt;p&gt;这是 epoll 最容易写错的地方。&lt;code&gt;epoll(7)&lt;/code&gt; 用管道例子讲得很直白：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把管道读端 &lt;code&gt;rfd&lt;/code&gt; 加入 epoll；&lt;/li&gt;
&lt;li&gt;写端写入 2KB；&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;epoll_wait&lt;/code&gt; → 报告 &lt;code&gt;rfd&lt;/code&gt; 可读；&lt;/li&gt;
&lt;li&gt;应用只读了 1KB，缓冲区仍剩数据；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;再次&lt;/strong&gt; &lt;code&gt;epoll_wait&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LT（默认）&lt;/strong&gt;：只要缓冲区仍“可读”，就会 &lt;strong&gt;继续&lt;/strong&gt; 报告就绪；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ET（&lt;code&gt;EPOLLET&lt;/code&gt;）&lt;/strong&gt;：只在状态 &lt;strong&gt;发生变化&lt;/strong&gt; 时报告；若你没把数据读到“暂时不可读”，可能 &lt;strong&gt;不再通知&lt;/strong&gt;，下一次 &lt;code&gt;wait&lt;/code&gt; 会“卡住”，尽管缓冲里还有数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="31-lt像-poll好写易吵"&gt;&lt;a href="#31-lt%e5%83%8f-poll%e5%a5%bd%e5%86%99%e6%98%93%e5%90%b5" class="header-anchor"&gt;&lt;/a&gt;3.1 LT：像 poll，好写，易“吵”
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;语义接近 &lt;code&gt;poll&lt;/code&gt;：条件成立就通知。&lt;/li&gt;
&lt;li&gt;适合：业务逻辑简单、可接受重复通知、希望少踩坑。&lt;/li&gt;
&lt;li&gt;风险：若某 fd 长期可读但你处理很慢，它会 &lt;strong&gt;反复&lt;/strong&gt; 出现在 ready list，拖慢整个循环。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="32-et变化才通知必须抽干--非阻塞"&gt;&lt;a href="#32-et%e5%8f%98%e5%8c%96%e6%89%8d%e9%80%9a%e7%9f%a5%e5%bf%85%e9%a1%bb%e6%8a%bd%e5%b9%b2--%e9%9d%9e%e9%98%bb%e5%a1%9e" class="header-anchor"&gt;&lt;/a&gt;3.2 ET：变化才通知，必须“抽干 + 非阻塞”
&lt;/h3&gt;&lt;p&gt;man 页对 ET 的建议可压缩成两条硬规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;fd 必须非阻塞&lt;/strong&gt;。否则一次阻塞 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt; 可能饿死处理其它 fd 的任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只有在 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt; 返回 &lt;code&gt;EAGAIN&lt;/code&gt;（或等价“暂时不可用”）之后，才回去 &lt;code&gt;epoll_wait&lt;/code&gt;&lt;/strong&gt;。也就是说，事件到来后要 &lt;strong&gt;循环读写直到资源暂时耗尽&lt;/strong&gt;，不能“读一次就回 wait”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ET 适合：超高连接数、希望减少重复唤醒、团队能严格遵守非阻塞与 drain 协议的场景。很多高性能网络库默认 ET + 非阻塞。&lt;/p&gt;
&lt;h3 id="33-epolloneshot一次性通知与重武装"&gt;&lt;a href="#33-epolloneshot%e4%b8%80%e6%ac%a1%e6%80%a7%e9%80%9a%e7%9f%a5%e4%b8%8e%e9%87%8d%e6%ad%a6%e8%a3%85" class="header-anchor"&gt;&lt;/a&gt;3.3 &lt;code&gt;EPOLLONESHOT&lt;/code&gt;：一次性通知与重武装
&lt;/h3&gt;&lt;p&gt;设置 &lt;code&gt;EPOLLONESHOT&lt;/code&gt; 后，某个 fd 被 &lt;code&gt;epoll_wait&lt;/code&gt; 报告一次，就会在 interest list 中被 &lt;strong&gt;禁用&lt;/strong&gt;，直到你用 &lt;code&gt;EPOLL_CTL_MOD&lt;/code&gt; 重新启用。它常用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多线程 worker：保证同一连接同一时刻只被一个线程处理；&lt;/li&gt;
&lt;li&gt;防止在处理完成前被其它线程再次取到同一事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价是：处理结束后 &lt;strong&gt;必须记得 rearm&lt;/strong&gt;，否则该连接“静音”。&lt;/p&gt;
&lt;h2 id="四最小可运行实验管道上的-lt-vs-et"&gt;&lt;a href="#%e5%9b%9b%e6%9c%80%e5%b0%8f%e5%8f%af%e8%bf%90%e8%a1%8c%e5%ae%9e%e9%aa%8c%e7%ae%a1%e9%81%93%e4%b8%8a%e7%9a%84-lt-vs-et" class="header-anchor"&gt;&lt;/a&gt;四、最小可运行实验：管道上的 LT vs ET
&lt;/h2&gt;&lt;p&gt;下面用 Python 演示 &lt;strong&gt;同一场景在 LT/ET 下的不同表现&lt;/strong&gt;（Linux 上 &lt;code&gt;selectors.EpollSelector&lt;/code&gt;；其它平台会落到 kqueue/select 等，行为不完全等价）。目标不是抄生产框架，而是把 man 页语义变成可观测实验。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;LT vs ET demo on a pipe (Linux).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;select&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;sys&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_pipe&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# set non-blocking on read end for ET-safe drain&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_blocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_blocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_pipe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epoll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPOLLIN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPOLLET&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;A&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mode:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ET&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;LT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&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;# first wait should fire&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; first poll:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)&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;# only read 1024, leave 1024 in buffer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; partial read:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&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;# second wait: LT should still report; ET often does not&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ev2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; second poll:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev2&lt;/span&gt;&lt;span class="p"&gt;)&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;# drain remaining&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;BlockingIOError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; drained rest:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;---&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;预期现象（内核版本与缓冲细节可能略有差异，但方向稳定）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LT&lt;/strong&gt;：partial read 后第二次 &lt;code&gt;poll&lt;/code&gt; &lt;strong&gt;仍可能&lt;/strong&gt;看到 &lt;code&gt;EPOLLIN&lt;/code&gt;（缓冲仍可读）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ET&lt;/strong&gt;：partial read 后第二次 &lt;code&gt;poll&lt;/code&gt; 往往 &lt;strong&gt;超时为空&lt;/strong&gt;，直到再次有“新数据写入”或你换回 LT/ONESHOT 策略。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这正是生产代码里 “ET 下偶发假死：连接其实有数据，事件循环却不再被唤醒” 的最小复现。&lt;/p&gt;
&lt;h3 id="41-监听-socket-的标准骨架伪代码"&gt;&lt;a href="#41-%e7%9b%91%e5%90%ac-socket-%e7%9a%84%e6%a0%87%e5%87%86%e9%aa%a8%e6%9e%b6%e4%bc%aa%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;4.1 监听 socket 的标准骨架（伪代码）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/* listener, conn 均为 nonblocking */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EPOLLIN&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_et&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;EPOLLET&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&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="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;epoll_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_EVENTS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ET 下要循环 accept 到 EAGAIN */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errno&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;EAGAIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* handle error */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;set_nonblocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;epoll_ctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epfd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EPOLL_CTL_ADD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;epoll_event&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EPOLLIN&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;EPOLLRDHUP&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;use_et&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nl"&gt;EPOLLET&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;do_use_fd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cm"&gt;/* ET: 读写到 EAGAIN；注意 EPOLLERR/HUP */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;do_use_fd&lt;/code&gt; 在 ET 下必须把 &lt;strong&gt;可读数据读尽、可写缓冲区写满到 EAGAIN&lt;/strong&gt;；半包协议要在用户态自己做缓冲拼装。&lt;/p&gt;
&lt;h2 id="五工程映射语言运行时与-nginx"&gt;&lt;a href="#%e4%ba%94%e5%b7%a5%e7%a8%8b%e6%98%a0%e5%b0%84%e8%af%ad%e8%a8%80%e8%bf%90%e8%a1%8c%e6%97%b6%e4%b8%8e-nginx" class="header-anchor"&gt;&lt;/a&gt;五、工程映射：语言运行时与 Nginx
&lt;/h2&gt;&lt;h3 id="51-python-selectors"&gt;&lt;a href="#51-python-selectors" class="header-anchor"&gt;&lt;/a&gt;5.1 Python &lt;code&gt;selectors&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;标准库 &lt;code&gt;selectors&lt;/code&gt; 把 epoll/kqueue/select 统一成高层 API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DefaultSelector&lt;/code&gt;：当前平台最高效实现的别名（Linux 上通常是 &lt;code&gt;EpollSelector&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EVENT_READ&lt;/code&gt; / &lt;code&gt;EVENT_WRITE&lt;/code&gt;：跨平台事件抽象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;register&lt;/code&gt; / &lt;code&gt;unregister&lt;/code&gt; / &lt;code&gt;modify&lt;/code&gt; / &lt;code&gt;select&lt;/code&gt;：对应 ctl + wait 的职责拆分。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自研 asyncio 之外的小工具时，优先用 &lt;code&gt;selectors&lt;/code&gt;，比直接 &lt;code&gt;select.epoll&lt;/code&gt; 更易移植；但若你要精细控制 &lt;code&gt;EPOLLET&lt;/code&gt;/&lt;code&gt;ONESHOT&lt;/code&gt;，仍需回到平台原生接口。&lt;/p&gt;
&lt;h3 id="52-nginx-的连接处理方式"&gt;&lt;a href="#52-nginx-%e7%9a%84%e8%bf%9e%e6%8e%a5%e5%a4%84%e7%90%86%e6%96%b9%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;5.2 Nginx 的连接处理方式
&lt;/h3&gt;&lt;p&gt;Nginx 文档 &lt;em&gt;Connection processing methods&lt;/em&gt; 写明：Linux 2.6+ 使用高效的 &lt;strong&gt;epoll&lt;/strong&gt; 方法，并支持 &lt;code&gt;EPOLLRDHUP&lt;/code&gt;、&lt;code&gt;EPOLLEXCLUSIVE&lt;/code&gt; 等标志。平台支持多种方法时，Nginx 会自动选最高效者，也可用 &lt;code&gt;use&lt;/code&gt; 指令显式指定。这解释了为何“同一套配置在 Linux 与 FreeBSD 上行为细节不同”——底层分别是 epoll 与 kqueue。&lt;/p&gt;
&lt;h2 id="六常见坑与排查清单"&gt;&lt;a href="#%e5%85%ad%e5%b8%b8%e8%a7%81%e5%9d%91%e4%b8%8e%e6%8e%92%e6%9f%a5%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;六、常见坑与排查清单
&lt;/h2&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;ET 下连接“有数据但不再触发”&lt;/td&gt;
					&lt;td&gt;partial read 后未 drain 到 &lt;code&gt;EAGAIN&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;循环读；确认非阻塞；对照 man 管道示例&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单连接阻塞拖死整进程&lt;/td&gt;
					&lt;td&gt;阻塞 fd + 同步 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全部 &lt;code&gt;O_NONBLOCK&lt;/code&gt;；慢业务丢线程池/协程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;某连接饿死其它连接&lt;/td&gt;
					&lt;td&gt;单 fd 上无限处理大流量&lt;/td&gt;
					&lt;td&gt;就绪列表 + 轮转；限制单次读写字节/次数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ONESHOT 后永久静音&lt;/td&gt;
					&lt;td&gt;处理完未 &lt;code&gt;EPOLL_CTL_MOD&lt;/code&gt; rearm&lt;/td&gt;
					&lt;td&gt;统一在处理结束路径 rearm&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;对端已关仍不清理&lt;/td&gt;
					&lt;td&gt;只看 &lt;code&gt;EPOLLIN&lt;/code&gt;，忽略挂断&lt;/td&gt;
					&lt;td&gt;处理 &lt;code&gt;EPOLLRDHUP&lt;/code&gt;/&lt;code&gt;EPOLLHUP&lt;/code&gt;/&lt;code&gt;EPOLLERR&lt;/code&gt;，读到 0 关闭&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_ctl ADD&lt;/code&gt; 报 &lt;code&gt;EEXIST&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;重复 ADD 同一 fd&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;MOD&lt;/code&gt; 改掩码，或先 &lt;code&gt;DEL&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 被信号打断&lt;/td&gt;
					&lt;td&gt;返回 &lt;code&gt;-1&lt;/code&gt; + &lt;code&gt;EINTR&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;循环重试，或使用 &lt;code&gt;epoll_pwait&lt;/code&gt; 管理信号掩码&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多进程抢同一监听 socket&lt;/td&gt;
					&lt;td&gt;惊群/重复唤醒&lt;/td&gt;
					&lt;td&gt;评估 &lt;code&gt;EPOLLEXCLUSIVE&lt;/code&gt;、&lt;code&gt;SO_REUSEPORT&lt;/code&gt; 等策略&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;排障时建议同时抓三份证据：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;fd 是否非阻塞&lt;/strong&gt;（&lt;code&gt;fcntl&lt;/code&gt; 标志）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;interest 掩码&lt;/strong&gt;是否含 &lt;code&gt;EPOLLET&lt;/code&gt;/&lt;code&gt;ONESHOT&lt;/code&gt;/&lt;code&gt;RDHUP&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次事件处理是否读/写到 EAGAIN&lt;/strong&gt;，以及是否处理 hangup。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="七和-selectpoll-的选型建议"&gt;&lt;a href="#%e4%b8%83%e5%92%8c-selectpoll-%e7%9a%84%e9%80%89%e5%9e%8b%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;七、和 select/poll 的选型建议
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;小工具、fd 很少、要极致可移植&lt;/strong&gt;：&lt;code&gt;poll&lt;/code&gt;/&lt;code&gt;select&lt;/code&gt; 或高层封装足够。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux 上万级长连接、自研事件循环&lt;/strong&gt;：epoll（多数生产库默认 ET + 非阻塞）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨 Unix 可移植高性能库&lt;/strong&gt;：用 libuv / netty / Go runtime 等已封装的多路复用层，而不是在业务里手写三套 epoll/kqueue/IOCP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只做业务、不碰事件循环&lt;/strong&gt;：理解 LT/ET 与非阻塞，是为了读懂框架文档和 flame graph，而不是 reinvent。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="八总结"&gt;&lt;a href="#%e5%85%ab%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;八、总结
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;epoll 在内核维护 &lt;strong&gt;interest list + ready list&lt;/strong&gt;，用 &lt;code&gt;create/ctl/wait&lt;/code&gt; 三件套把“注册”和“等待”拆开，从而在大规模 fd 上更省。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认 LT&lt;/strong&gt;：条件成立就通知，写法像 poll；&lt;strong&gt;ET&lt;/strong&gt;：状态变化才通知，必须 &lt;strong&gt;非阻塞 + 读/写到 EAGAIN&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EPOLLONESHOT&lt;/code&gt; 适合多线程串行化处理，但务必 &lt;strong&gt;处理完 rearm&lt;/strong&gt;；&lt;code&gt;EPOLLRDHUP&lt;/code&gt;/&lt;code&gt;HUP&lt;/code&gt;/&lt;code&gt;ERR&lt;/code&gt; 是连接回收的关键信号。&lt;/li&gt;
&lt;li&gt;饥饿、假死、惊群，大多不是“epoll 不灵”，而是 &lt;strong&gt;事件语义与读写协议不匹配&lt;/strong&gt;。先复现 LT/ET 差异，再改生产代码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;掌握这些机制后，再看 Nginx worker、Redis 事件循环、Netty EventLoop 或自研网关，会发现它们都在同一套规则上做工程化封装。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man7/epoll.7.html" target="_blank" rel="noopener"
 &gt;epoll(7)&lt;/a&gt; — interest/ready、LT/ET、ONESHOT、建议用法与陷阱&lt;/li&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man2/epoll_ctl.2.html" target="_blank" rel="noopener"
 &gt;epoll_ctl(2)&lt;/a&gt; — 事件标志、ADD/MOD/DEL、EPOLLET/ONESHOT/EXCLUSIVE&lt;/li&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man2/epoll_wait.2.html" target="_blank" rel="noopener"
 &gt;epoll_wait(2)&lt;/a&gt; — timeout、maxevents、就绪返回语义&lt;/li&gt;
&lt;li&gt;Linux man-pages: &lt;a class="link" href="https://man7.org/linux/man-pages/man2/epoll_create.2.html" target="_blank" rel="noopener"
 &gt;epoll_create(2) / epoll_create1(2)&lt;/a&gt; — 创建实例与 &lt;code&gt;EPOLL_CLOEXEC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Python 3 Docs: &lt;a class="link" href="https://docs.python.org/3/library/selectors.html" target="_blank" rel="noopener"
 &gt;selectors — High-level I/O multiplexing&lt;/a&gt; — &lt;code&gt;EpollSelector&lt;/code&gt; / &lt;code&gt;DefaultSelector&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Nginx Docs: &lt;a class="link" href="https://nginx.org/en/docs/events.html" target="_blank" rel="noopener"
 &gt;Connection processing methods&lt;/a&gt; — Linux 上 epoll 方法及 RDHUP/EXCLUSIVE 支持说明&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Docker 桥接网络与嵌入式 DNS：原理、连通性与工程实践</title><link>https://blog.waihost.com/posts/docker-bridge-network-embedded-dns/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0800</pubDate><guid>https://blog.waihost.com/posts/docker-bridge-network-embedded-dns/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/docker-bridge-network-embedded-dns.svg" alt="Featured image of post Docker 桥接网络与嵌入式 DNS：原理、连通性与工程实践" /&gt;&lt;p&gt;本地 &lt;code&gt;docker run&lt;/code&gt; 能通、Compose 里服务名却解析失败；两个容器“看起来在同一台机器”却互 ping 不通；或者明明没 &lt;code&gt;-p&lt;/code&gt;，却发现同网段容器已经能访问全部端口——这些日常问题，大多不是“Docker 坏了”，而是 &lt;strong&gt;bridge 网络模型 + DNS 解析路径&lt;/strong&gt; 理解不到位。&lt;/p&gt;
&lt;p&gt;本文基于 Docker 官方 &lt;em&gt;Networking overview&lt;/em&gt;、&lt;em&gt;Bridge network driver&lt;/em&gt;、&lt;em&gt;Packet filtering and firewalls&lt;/em&gt;、&lt;em&gt;Networking in Compose&lt;/em&gt; 与 &lt;em&gt;Legacy container links&lt;/em&gt;，把 &lt;strong&gt;默认 bridge 与用户自定义 bridge 的差异、嵌入式 DNS（&lt;code&gt;127.0.0.11&lt;/code&gt;）、端口可达性、防火墙/iptables 协作，以及可复现的连通性实验&lt;/strong&gt; 讲清楚，方便你在排障时按机制定位。&lt;/p&gt;
&lt;h2 id="一问题背景容器眼里只有网卡没有魔法互联"&gt;&lt;a href="#%e4%b8%80%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af%e5%ae%b9%e5%99%a8%e7%9c%bc%e9%87%8c%e5%8f%aa%e6%9c%89%e7%bd%91%e5%8d%a1%e6%b2%a1%e6%9c%89%e9%ad%94%e6%b3%95%e4%ba%92%e8%81%94" class="header-anchor"&gt;&lt;/a&gt;一、问题背景：容器眼里只有网卡，没有“魔法互联”
&lt;/h2&gt;&lt;p&gt;从容器视角看，网络很朴素：一张网卡、一个 IP、默认网关、路由表和 DNS。容器并不知道对端是不是另一个 Docker 容器，也不知道自己挂在 default bridge 还是自定义网络上。&lt;/p&gt;
&lt;p&gt;因此，工程里真正要掌握的是 &lt;strong&gt;Docker 如何在宿主机上拼出这张网&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用哪种 &lt;strong&gt;network driver&lt;/strong&gt; 创建隔离域；&lt;/li&gt;
&lt;li&gt;容器如何获得 IP，以及如何访问外网（masquerading）；&lt;/li&gt;
&lt;li&gt;同网/跨网时，&lt;strong&gt;哪些端口天然可达，哪些必须 publish&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;名字解析走 &lt;strong&gt;宿主 &lt;code&gt;/etc/resolv.conf&lt;/code&gt; 拷贝&lt;/strong&gt;，还是 Docker &lt;strong&gt;嵌入式 DNS&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这四条对齐后，再看 &lt;code&gt;docker network inspect&lt;/code&gt; 和 &lt;code&gt;cat /etc/resolv.conf&lt;/code&gt;，排障会快很多。&lt;/p&gt;
&lt;h2 id="二核心机制bridge-网络在做什么"&gt;&lt;a href="#%e4%ba%8c%e6%a0%b8%e5%bf%83%e6%9c%ba%e5%88%b6bridge-%e7%bd%91%e7%bb%9c%e5%9c%a8%e5%81%9a%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;二、核心机制：bridge 网络在做什么
&lt;/h2&gt;&lt;h3 id="21-软件桥与隔离边界"&gt;&lt;a href="#21-%e8%bd%af%e4%bb%b6%e6%a1%a5%e4%b8%8e%e9%9a%94%e7%a6%bb%e8%be%b9%e7%95%8c" class="header-anchor"&gt;&lt;/a&gt;2.1 软件桥与隔离边界
&lt;/h3&gt;&lt;p&gt;Bridge 驱动在 Linux 上使用软件网桥：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同一 bridge 网络内的容器可互通；&lt;/li&gt;
&lt;li&gt;默认阻止来自其他 bridge 网络、以及 Docker 宿主机之外的直接访问；&lt;/li&gt;
&lt;li&gt;通过 &lt;strong&gt;masquerading（源地址伪装）&lt;/strong&gt; 让容器出网时，外部只看到宿主机 IP；&lt;/li&gt;
&lt;li&gt;支持 &lt;strong&gt;port publishing&lt;/strong&gt;，把容器端口映射到宿主机地址，供外部或其他网络访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官方明确：bridge 面向 &lt;strong&gt;同一 Docker daemon 主机&lt;/strong&gt; 上的容器；跨主机通信需要 OS 级路由或 &lt;strong&gt;overlay&lt;/strong&gt; 等方案。&lt;/p&gt;
&lt;h3 id="22-内置驱动速览选型锚点"&gt;&lt;a href="#22-%e5%86%85%e7%bd%ae%e9%a9%b1%e5%8a%a8%e9%80%9f%e8%a7%88%e9%80%89%e5%9e%8b%e9%94%9a%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;2.2 内置驱动速览（选型锚点）
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Driver&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;bridge&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;默认驱动；同机容器互联（本文重点）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;host&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;去掉网络隔离，直接用宿主机网络栈&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;完全隔离，无网络&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;overlay&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多 daemon / Swarm 跨节点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;macvlan&lt;/code&gt; / &lt;code&gt;ipvlan&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;让容器更像“物理网”上的设备 / VLAN 集成&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;没有特殊需求时，应用栈优先用 &lt;strong&gt;用户自定义 bridge&lt;/strong&gt;，而不是长期堆在 default &lt;code&gt;bridge&lt;/code&gt; 上。&lt;/p&gt;
&lt;h3 id="23-默认-bridge-vs-用户自定义-bridge必须分清"&gt;&lt;a href="#23-%e9%bb%98%e8%ae%a4-bridge-vs-%e7%94%a8%e6%88%b7%e8%87%aa%e5%ae%9a%e4%b9%89-bridge%e5%bf%85%e9%a1%bb%e5%88%86%e6%b8%85" class="header-anchor"&gt;&lt;/a&gt;2.3 默认 bridge vs 用户自定义 bridge（必须分清）
&lt;/h3&gt;&lt;p&gt;Docker 首次启动会自动创建名为 &lt;code&gt;bridge&lt;/code&gt; 的 &lt;strong&gt;default bridge&lt;/strong&gt;。未指定 &lt;code&gt;--network&lt;/code&gt; 的容器会连上它。&lt;/p&gt;
&lt;p&gt;官方对用户自定义 bridge 的结论很直接：&lt;strong&gt;User-defined bridge networks are superior to the default bridge network.&lt;/strong&gt; 关键差异如下。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;default bridge&lt;/th&gt;
					&lt;th&gt;用户自定义 bridge&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;名字解析&lt;/td&gt;
					&lt;td&gt;默认只能靠 IP；&lt;code&gt;--link&lt;/code&gt; 为 legacy&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;自动 DNS&lt;/strong&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;要从 default bridge 换走，通常需停容器重建&lt;/td&gt;
					&lt;td&gt;运行中可用 &lt;code&gt;docker network connect/disconnect&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;配置粒度&lt;/td&gt;
					&lt;td&gt;全局一份（常需改 daemon 并重启）&lt;/td&gt;
					&lt;td&gt;每个网络可独立 &lt;code&gt;docker network create&lt;/code&gt; 配置&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;生产建议&lt;/td&gt;
					&lt;td&gt;官方视为 legacy 细节，&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;p&gt;同一用户自定义 bridge 内的容器，&lt;strong&gt;彼此相当于暴露全部端口&lt;/strong&gt;；若要对其他网络或宿主机外访问，才需要 &lt;code&gt;-p/--publish&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="24-嵌入式-dns1270011"&gt;&lt;a href="#24-%e5%b5%8c%e5%85%a5%e5%bc%8f-dns1270011" class="header-anchor"&gt;&lt;/a&gt;2.4 嵌入式 DNS：&lt;code&gt;127.0.0.11&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;DNS 是 default bridge 与自定义网络最容易踩坑的分界线：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;default bridge&lt;/strong&gt;：容器通常拿到宿主 &lt;code&gt;/etc/resolv.conf&lt;/code&gt; 的&lt;strong&gt;拷贝&lt;/strong&gt;，按宿主配置解析公网名；&lt;strong&gt;默认不能靠容器名互访&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自定义网络（含 Compose 默认项目网络）&lt;/strong&gt;：使用 Docker &lt;strong&gt;embedded DNS&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;地址固定为 &lt;strong&gt;&lt;code&gt;127.0.0.11&lt;/code&gt;&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有 IPv6 等价地址&lt;/strong&gt;，IPv6-only 容器仍用该 IPv4 地址；&lt;/li&gt;
&lt;li&gt;容器名/别名解析在嵌入式 DNS 完成；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部域名查询会转发到宿主上配置的 DNS&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;若应用硬编码了 DNS 服务器地址，官方建议写 &lt;strong&gt;&lt;code&gt;127.0.0.11&lt;/code&gt;&lt;/strong&gt;（在自定义网络场景下），而不是宿主上的 &lt;code&gt;8.8.8.8&lt;/code&gt; 之类——后者会绕过容器名解析。&lt;/p&gt;
&lt;p&gt;可用标志（&lt;code&gt;docker run&lt;/code&gt; / &lt;code&gt;docker create&lt;/code&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;code&gt;--dns&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;指定 DNS 服务器；注意解析发生在&lt;strong&gt;容器网络命名空间&lt;/strong&gt;，&lt;code&gt;--dns=127.0.0.1&lt;/code&gt; 指容器自己的 loopback&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--dns-search&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;搜索域&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--dns-opt&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;resolv.conf&lt;/code&gt; 选项&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--hostname&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;容器主机名（默认多为容器 ID）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;宿主 &lt;code&gt;/etc/hosts&lt;/code&gt; 的自定义条目&lt;strong&gt;不会自动继承&lt;/strong&gt;进容器；额外主机名用 &lt;code&gt;--add-host&lt;/code&gt; / Compose &lt;code&gt;extra_hosts&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="25-端口可达性与防火墙协作"&gt;&lt;a href="#25-%e7%ab%af%e5%8f%a3%e5%8f%af%e8%be%be%e6%80%a7%e4%b8%8e%e9%98%b2%e7%81%ab%e5%a2%99%e5%8d%8f%e4%bd%9c" class="header-anchor"&gt;&lt;/a&gt;2.5 端口可达性与防火墙协作
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;同 bridge 网络：容器端口对&lt;strong&gt;宿主&lt;/strong&gt;及&lt;strong&gt;同网其他容器&lt;/strong&gt;可达；&lt;/li&gt;
&lt;li&gt;默认配置下，&lt;strong&gt;其他 bridge 网络&lt;/strong&gt;或&lt;strong&gt;宿主机外&lt;/strong&gt;访问需要 publish；&lt;/li&gt;
&lt;li&gt;Linux 上 Docker 会创建防火墙规则以实现隔离、端口发布与 NAT；&lt;strong&gt;不要随意改 Docker 生成的规则&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;默认后端多为 &lt;strong&gt;iptables&lt;/strong&gt;，也支持 &lt;strong&gt;nftables&lt;/strong&gt;；bridge 上两者功能等价；&lt;/li&gt;
&lt;li&gt;对 &lt;code&gt;ipvlan&lt;/code&gt; / &lt;code&gt;macvlan&lt;/code&gt; / &lt;code&gt;host&lt;/code&gt;，Docker &lt;strong&gt;不创建&lt;/strong&gt;这套 bridge 规则；&lt;/li&gt;
&lt;li&gt;Docker 常会打开 IP 转发，并可能把转发默认策略设为 drop（防止宿主机无意变成路由器）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker 与 ufw 不兼容的常见根因&lt;/strong&gt;：Docker 在 &lt;code&gt;nat&lt;/code&gt; 表改写流量，可能在 ufw 的 INPUT/OUTPUT 之前分流，导致“ufw 规则像没生效”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常用网络级选项（&lt;code&gt;docker network create --opt&lt;/code&gt;）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Option&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;com.docker.network.bridge.enable_ip_masquerade&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;出网伪装&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;com.docker.network.bridge.enable_icc&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;同网容器互通；可关以强制“只经代理/发布口访问”&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;com.docker.network.bridge.host_binding_ipv4&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全部地址&lt;/td&gt;
					&lt;td&gt;未写主机 IP 时的默认绑定地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;com.docker.network.driver.mtu&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0&lt;/code&gt;（不限制）&lt;/td&gt;
					&lt;td&gt;MTU&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="26-compose-如何落地同一套模型"&gt;&lt;a href="#26-compose-%e5%a6%82%e4%bd%95%e8%90%bd%e5%9c%b0%e5%90%8c%e4%b8%80%e5%a5%97%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;2.6 Compose 如何落地同一套模型
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;docker compose up&lt;/code&gt; 默认会创建名为 &lt;strong&gt;&lt;code&gt;&amp;lt;project-name&amp;gt;_default&lt;/code&gt;&lt;/strong&gt; 的 bridge 网络，服务全部加入，并向内部 DNS 注册&lt;strong&gt;服务名&lt;/strong&gt;。因此 Compose 里写 &lt;code&gt;http://api:8080&lt;/code&gt; 往往能通，而“裸 default bridge 起两个容器却写容器名”会失败——这不是 Compose 魔法，而是 &lt;strong&gt;自定义网络 + 嵌入式 DNS&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;还可声明 &lt;code&gt;internal: true&lt;/code&gt; 的网络（无默认出网网关），把数据库等后端放在“只能被同项目服务访问”的网段上。&lt;/p&gt;
&lt;h2 id="三实践可复现的连通性与-dns-实验"&gt;&lt;a href="#%e4%b8%89%e5%ae%9e%e8%b7%b5%e5%8f%af%e5%a4%8d%e7%8e%b0%e7%9a%84%e8%bf%9e%e9%80%9a%e6%80%a7%e4%b8%8e-dns-%e5%ae%9e%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;三、实践：可复现的连通性与 DNS 实验
&lt;/h2&gt;&lt;p&gt;下面命令假定本机已安装 Docker Engine。若无 &lt;code&gt;alpine&lt;/code&gt; 镜像，会自动拉取。&lt;/p&gt;
&lt;h3 id="31-观察-default-bridge能通-ip不能靠名字"&gt;&lt;a href="#31-%e8%a7%82%e5%af%9f-default-bridge%e8%83%bd%e9%80%9a-ip%e4%b8%8d%e8%83%bd%e9%9d%a0%e5%90%8d%e5%ad%97" class="header-anchor"&gt;&lt;/a&gt;3.1 观察 default bridge：能通 IP，不能靠名字
&lt;/h3&gt;&lt;div class="highlight"&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;# 两个容器都挂在 default bridge（未指定 --network）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name dns-a alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name dns-b alpine sleep &lt;span class="m"&gt;3600&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;# 看 IP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker inspect -f &lt;span class="s1"&gt;&amp;#39;{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}&amp;#39;&lt;/span&gt; dns-a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;IP_A&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;docker inspect -f &lt;span class="s1"&gt;&amp;#39;{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}&amp;#39;&lt;/span&gt; dns-a&lt;span class="k"&gt;)&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;# IP 互通通常成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; dns-b ping -c1 &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$IP_A&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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;# 名字解析：在 default bridge 上通常失败（无嵌入式 DNS 的容器名互访）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; dns-b ping -c1 dns-a &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;name resolve failed as expected on default bridge&amp;#34;&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;# 看 resolv.conf：往往是宿主 DNS 拷贝，而不是 127.0.0.11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; dns-b cat /etc/resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;清理：&lt;/p&gt;
&lt;div class="highlight"&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;docker rm -f dns-a dns-b
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="32-用户自定义-bridge容器名解析--1270011"&gt;&lt;a href="#32-%e7%94%a8%e6%88%b7%e8%87%aa%e5%ae%9a%e4%b9%89-bridge%e5%ae%b9%e5%99%a8%e5%90%8d%e8%a7%a3%e6%9e%90--1270011" class="header-anchor"&gt;&lt;/a&gt;3.2 用户自定义 bridge：容器名解析 + &lt;code&gt;127.0.0.11&lt;/code&gt;
&lt;/h3&gt;&lt;div class="highlight"&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;docker network create demo-net
&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;docker run -d --name web --network demo-net alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name db --network demo-net alpine sleep &lt;span class="m"&gt;3600&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;# 名字应可解析&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; web ping -c1 db
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; db ping -c1 web
&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&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; web cat /etc/resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 期望 nameserver 为 127.0.0.11&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;# 同网端口：无需 -p，另一容器即可访问（这里用 nc 示意）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; -d db sh -c &lt;span class="s1"&gt;&amp;#39;echo hi | nc -l -p 9000&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; web sh -c &lt;span class="s1"&gt;&amp;#39;nc -w 2 db 9000&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;运行中热挂网络：&lt;/p&gt;
&lt;div class="highlight"&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;docker run -d --name side alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt; &lt;span class="c1"&gt;# 先在 default bridge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network connect demo-net side
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; side ping -c1 web
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network disconnect demo-net side
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;清理：&lt;/p&gt;
&lt;div class="highlight"&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;docker rm -f web db side
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network rm demo-net
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="33-前后端分离网络拓扑多网卡容器"&gt;&lt;a href="#33-%e5%89%8d%e5%90%8e%e7%ab%af%e5%88%86%e7%a6%bb%e7%bd%91%e7%bb%9c%e6%8b%93%e6%89%91%e5%a4%9a%e7%bd%91%e5%8d%a1%e5%ae%b9%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;3.3 “前后端分离网络”拓扑（多网卡容器）
&lt;/h3&gt;&lt;div class="highlight"&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;docker network create frontend
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network create --internal backend &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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name api --network frontend alpine sleep &lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network connect backend api
&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;docker run -d --name pg --network backend alpine sleep &lt;span class="m"&gt;3600&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;# api 能访问 pg（同 backend）；pg 默认不应依赖出网&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; api ping -c1 pg
&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;docker network create &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --opt com.docker.network.bridge.host_binding_ipv4&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; local-only
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="34-compose-最小可运行示意"&gt;&lt;a href="#34-compose-%e6%9c%80%e5%b0%8f%e5%8f%af%e8%bf%90%e8%a1%8c%e7%a4%ba%e6%84%8f" class="header-anchor"&gt;&lt;/a&gt;3.4 Compose 最小可运行示意
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# compose.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep 3600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;api]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep 3600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;default, backend]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;alpine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep 3600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;backend]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&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;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose &lt;span class="nb"&gt;exec&lt;/span&gt; web ping -c1 api &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;docker compose &lt;span class="nb"&gt;exec&lt;/span&gt; api ping -c1 db
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker compose down
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="35-关闭-icc强制不能随意横穿"&gt;&lt;a href="#35-%e5%85%b3%e9%97%ad-icc%e5%bc%ba%e5%88%b6%e4%b8%8d%e8%83%bd%e9%9a%8f%e6%84%8f%e6%a8%aa%e7%a9%bf" class="header-anchor"&gt;&lt;/a&gt;3.5 关闭 ICC（强制“不能随意横穿”）
&lt;/h3&gt;&lt;div class="highlight"&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;docker network create &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --opt com.docker.network.bridge.enable_icc&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; no-icc-net
&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;# 同网容器在 ICC 关闭后，默认相互连通会被限制&lt;/span&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;docker network rm no-icc-net
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="四常见坑与排查清单"&gt;&lt;a href="#%e5%9b%9b%e5%b8%b8%e8%a7%81%e5%9d%91%e4%b8%8e%e6%8e%92%e6%9f%a5%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;四、常见坑与排查清单
&lt;/h2&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;容器名 ping 不通&lt;/td&gt;
					&lt;td&gt;是否挂在 &lt;strong&gt;default bridge&lt;/strong&gt;？自定义网络里 &lt;code&gt;cat /etc/resolv.conf&lt;/code&gt; 是否为 &lt;code&gt;127.0.0.11&lt;/code&gt;？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compose 服务名不通&lt;/td&gt;
					&lt;td&gt;是否在同一 project 网络？服务是否显式挂到不同 network 且未共享？&lt;code&gt;docker network inspect &amp;lt;project&amp;gt;_default&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;外网不能访问容器&lt;/td&gt;
					&lt;td&gt;是否 &lt;code&gt;-p/--publish&lt;/code&gt;？安全组/云防火墙？绑定是否被 &lt;code&gt;host_binding_ipv4=127.0.0.1&lt;/code&gt; 限制在本机？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;容器不能出网&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;enable_ip_masquerade&lt;/code&gt;？宿主 IP 转发？DNS 是否异常？&lt;code&gt;iptables=false&lt;/code&gt; 是否误关 Docker 防火墙规则？&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;ufw “放行了仍异常”&lt;/td&gt;
					&lt;td&gt;已知 Docker 与 ufw 规则路径冲突，publish 流量可能绕过 ufw 链&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;--link&lt;/code&gt; 老项目&lt;/td&gt;
					&lt;td&gt;官方标为 &lt;strong&gt;legacy&lt;/strong&gt;，推荐迁移到 user-defined networks；环境变量共享改用 env/volume/Compose&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;单网容器极多不稳定&lt;/td&gt;
					&lt;td&gt;内核限制：单 bridge 约 &lt;strong&gt;1000+&lt;/strong&gt; 容器时可能不稳定（见 moby#44973）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多网卡默认路由怪&lt;/td&gt;
					&lt;td&gt;多网络时默认网关可能变化；可用 &lt;code&gt;gw-priority&lt;/code&gt; 指定优先网关&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;宿主 hosts 改了容器不生效&lt;/td&gt;
					&lt;td&gt;宿主 &lt;code&gt;/etc/hosts&lt;/code&gt; 不继承；用 &lt;code&gt;--add-host&lt;/code&gt; / &lt;code&gt;extra_hosts&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;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;docker network ls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker network inspect &amp;lt;net&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker inspect -f &lt;span class="s1"&gt;&amp;#39;{{json .NetworkSettings.Networks}}&amp;#39;&lt;/span&gt; &amp;lt;ctr&amp;gt; &lt;span class="p"&gt;|&lt;/span&gt; jq .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;ctr&amp;gt; cat /etc/resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;ctr&amp;gt; ip route
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;ctr&amp;gt; getent hosts &amp;lt;peer-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 宿主侧（Linux）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ip link show &lt;span class="nb"&gt;type&lt;/span&gt; bridge
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 注意：不要随手 flush Docker 管理的 iptables 链&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="五工程建议可直接落到规范"&gt;&lt;a href="#%e4%ba%94%e5%b7%a5%e7%a8%8b%e5%bb%ba%e8%ae%ae%e5%8f%af%e7%9b%b4%e6%8e%a5%e8%90%bd%e5%88%b0%e8%a7%84%e8%8c%83" class="header-anchor"&gt;&lt;/a&gt;五、工程建议（可直接落到规范）
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;新项目默认创建用户自定义 bridge&lt;/strong&gt;（或直接用 Compose 项目网络），避免依赖 default bridge 与 &lt;code&gt;--link&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务发现用名字，不写死容器 IP&lt;/strong&gt;；IP 会随重建变化，DNS 是稳定契约。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前后端分层网络&lt;/strong&gt;：对外服务进可出网网络，数据库进 &lt;code&gt;internal&lt;/code&gt; 网络；需要两边都通的 BFF/API 同时 attach。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只 publish 必要端口&lt;/strong&gt;，生产优先绑定具体主机地址，避免 &lt;code&gt;0.0.0.0&lt;/code&gt; 无脑暴露。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要手改 Docker 生成的防火墙规则&lt;/strong&gt;；若必须对接企业防火墙，走官方允许的集成方式，而不是 &lt;code&gt;iptables=false&lt;/code&gt; 一刀切。&lt;/li&gt;
&lt;li&gt;应用若强制配置 DNS，自定义网络场景优先 &lt;strong&gt;&lt;code&gt;127.0.0.11&lt;/code&gt;&lt;/strong&gt;，以免丢掉容器名解析。&lt;/li&gt;
&lt;li&gt;跨主机通信升级到 &lt;strong&gt;overlay / 服务网格 / 云网络&lt;/strong&gt;，不要指望单机 bridge “自动跨机”。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="六总结"&gt;&lt;a href="#%e5%85%ad%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;六、总结
&lt;/h2&gt;&lt;p&gt;Docker 单机互联的主干是 &lt;strong&gt;bridge&lt;/strong&gt;：软件桥提供 L2 邻接与隔离，masquerade 解决出网，publish 解决入站，&lt;strong&gt;嵌入式 DNS（&lt;code&gt;127.0.0.11&lt;/code&gt;）&lt;/strong&gt; 解决自定义网络上的名字发现。default bridge 适合“随手跑个容器”，但缺少容器名 DNS、隔离弱、配置粗，官方更推荐 &lt;strong&gt;user-defined bridge&lt;/strong&gt;；Compose 的服务名互通，本质上就是把这套模型产品化了。&lt;/p&gt;
&lt;p&gt;记住一条排障主线：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;先看容器挂在哪张网 → 再看 &lt;code&gt;/etc/resolv.conf&lt;/code&gt; 是不是 &lt;code&gt;127.0.0.11&lt;/code&gt; → 再判断该不该有 publish → 最后才怀疑防火墙/宿主路由。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;按这条线做，大部分“容器网络玄学”都会变成可验证的配置问题。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/" target="_blank" rel="noopener"
 &gt;Networking overview&lt;/a&gt;（官方；DNS / default bridge / drivers）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/drivers/bridge/" target="_blank" rel="noopener"
 &gt;Bridge network driver&lt;/a&gt;（官方；default vs user-defined、选项表、ICC/masquerade）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/drivers/" target="_blank" rel="noopener"
 &gt;Network drivers&lt;/a&gt;（官方；bridge/host/overlay/macvlan 等选型）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/packet-filtering-firewalls/" target="_blank" rel="noopener"
 &gt;Packet filtering and firewalls&lt;/a&gt;（官方；iptables/nftables、ufw 注意点）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/compose/how-tos/networking/" target="_blank" rel="noopener"
 &gt;Networking in Compose&lt;/a&gt;（官方；项目默认网络与服务名 DNS）&lt;/li&gt;
&lt;li&gt;Docker Docs — &lt;a class="link" href="https://docs.docker.com/engine/network/links/" target="_blank" rel="noopener"
 &gt;Legacy container links&lt;/a&gt;（官方；&lt;code&gt;--link&lt;/code&gt; legacy 与迁移建议）&lt;/li&gt;
&lt;li&gt;源码文档镜像（核验用）：&lt;a class="link" href="https://github.com/docker/docs/tree/main/content/manuals/engine/network" target="_blank" rel="noopener"
 &gt;docker/docs &lt;code&gt;content/manuals/engine/network/&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>iptables/socat转发ip的方式</title><link>https://blog.waihost.com/posts/iptables-socat-ip-forward/</link><pubDate>Fri, 08 Mar 2024 19:49:00 +0800</pubDate><guid>https://blog.waihost.com/posts/iptables-socat-ip-forward/</guid><description>&lt;img src="https://blog.waihost.com/images/covers/iptables-socat-ip-forward.svg" alt="Featured image of post iptables/socat转发ip的方式" /&gt;&lt;h3 id="方法-1使用-iptables推荐"&gt;&lt;a href="#%e6%96%b9%e6%b3%95-1%e4%bd%bf%e7%94%a8-iptables%e6%8e%a8%e8%8d%90" class="header-anchor"&gt;&lt;/a&gt;方法 1：使用 iptables（推荐）
&lt;/h3&gt;&lt;h4 id="步骤"&gt;&lt;a href="#%e6%ad%a5%e9%aa%a4" class="header-anchor"&gt;&lt;/a&gt;步骤：
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在 VPS B 上启用 IP 转发&lt;/strong&gt;&lt;br&gt;
编辑 &lt;code&gt;/etc/sysctl.conf&lt;/code&gt;，取消以下行的注释：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;net.ipv4.ip_forward = 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后应用配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sysctl -p
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置 iptables 转发规则&lt;/strong&gt;&lt;br&gt;
执行以下命令（替换 &lt;code&gt;B_PORT&lt;/code&gt; 和 &lt;code&gt;A_IP:A_PORT&lt;/code&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;iptables -t nat -A PREROUTING -p tcp --dport B_PORT -j DNAT --to-destination A_IP:A_PORT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;iptables -t nat -A POSTROUTING -j MASQUERADE
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;保存 iptables 规则（防止重启丢失）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 对于 Ubuntu/Debian：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt install iptables-persistent -y &amp;amp;&amp;amp; iptables-save &amp;gt; /etc/iptables/rules.v4
&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;# 对于 CentOS/RHEL：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;service iptables save
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开放防火墙端口&lt;/strong&gt;&lt;br&gt;
如果 VPS B 启用了防火墙（如 &lt;code&gt;ufw&lt;/code&gt; 或 &lt;code&gt;firewalld&lt;/code&gt;），确保放行端口：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ufw 示例：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ufw allow B_PORT/tcp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id="方法-2使用-socat快速临时方案"&gt;&lt;a href="#%e6%96%b9%e6%b3%95-2%e4%bd%bf%e7%94%a8-socat%e5%bf%ab%e9%80%9f%e4%b8%b4%e6%97%b6%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;方法 2：使用 socat（快速临时方案）
&lt;/h3&gt;&lt;h4 id="步骤-1"&gt;&lt;a href="#%e6%ad%a5%e9%aa%a4-1" class="header-anchor"&gt;&lt;/a&gt;步骤：
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在 VPS B 上安装 socat&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Ubuntu/Debian：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt install socat -y
&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;# CentOS/RHEL：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yum install socat -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;运行转发命令&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;socat TCP-LISTEN:B_PORT,fork,reuseaddr TCP:A_IP:A_PORT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;若要后台运行，添加 &lt;code&gt;&amp;amp;&lt;/code&gt; 或使用 &lt;code&gt;nohup&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nohup socat TCP-LISTEN:B_PORT,fork,reuseaddr TCP:A_IP:A_PORT &amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id="验证转发是否成功"&gt;&lt;a href="#%e9%aa%8c%e8%af%81%e8%bd%ac%e5%8f%91%e6%98%af%e5%90%a6%e6%88%90%e5%8a%9f" class="header-anchor"&gt;&lt;/a&gt;验证转发是否成功
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在 VPS A 上启动服务&lt;/strong&gt;&lt;br&gt;
确保 A 的端口 &lt;code&gt;A_PORT&lt;/code&gt; 有服务监听（如 &lt;code&gt;nc -l A_PORT&lt;/code&gt; 或运行实际应用）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;从外部测试连接&lt;/strong&gt;&lt;br&gt;
访问 &lt;code&gt;VPS_B_IP:B_PORT&lt;/code&gt;，观察是否能连接到 VPS A 的服务：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;telnet VPS_B_IP B_PORT
&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;curl http://VPS_B_IP:B_PORT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id="注意事项"&gt;&lt;a href="#%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" class="header-anchor"&gt;&lt;/a&gt;注意事项
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;协议支持&lt;/strong&gt;：上述命令默认转发 TCP 流量。如需 UDP，将 &lt;code&gt;-p tcp&lt;/code&gt; 改为 &lt;code&gt;-p udp&lt;/code&gt;（iptables）或使用 &lt;code&gt;UDP-LISTEN&lt;/code&gt;（socat）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持久化&lt;/strong&gt;：使用 &lt;code&gt;iptables&lt;/code&gt; 方法需保存规则，而 &lt;code&gt;socat&lt;/code&gt; 需通过 &lt;code&gt;systemd&lt;/code&gt; 或 &lt;code&gt;supervisor&lt;/code&gt; 设置开机启动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;防火墙&lt;/strong&gt;：确保 VPS B 的云服务商控制台（如 AWS 安全组）也放行了 &lt;code&gt;B_PORT&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果需要更稳定的方案，还可以考虑使用 &lt;strong&gt;WireGuard&lt;/strong&gt; 或 &lt;strong&gt;SSH 隧道&lt;/strong&gt; 建立加密转发。例如 SSH 隧道：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 在 VPS B 上执行：
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -N -L 0.0.0.0:B_PORT:A_IP:A_PORT user@A_IP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item></channel></rss>