<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>架构设计 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Tue, 08 Aug 2023 10:40:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>《Unix 编程艺术》笔记：无为而治的操作系统版——不写多余的代码</title>
      <link>https://ftmao.com/blog/bookstore/art-of-unix-programming-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/art-of-unix-programming-notes/</guid>
      <description>&lt;h1 id=&#34;unix-编程艺术笔记无为而治的操作系统版不写多余的代码&#34;&gt;《Unix 编程艺术》笔记：无为而治的操作系统版——不写多余的代码&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写过一两年代码、开始困惑&amp;quot;为什么有的系统越改越烂&amp;quot;的程序员。&lt;br&gt;
参考书籍：Eric S. Raymond《The Art of Unix Programming》（TAOUP，2003）&lt;br&gt;
阅读目标：把 Unix 哲学从&amp;quot;背口诀&amp;quot;变成&amp;quot;做决策的直觉&amp;quot;，知道什么时候&lt;strong&gt;不写代码&lt;/strong&gt;才是正确答案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面这本书其实在讲无为&#34;&gt;0. 写在前面：这本书其实在讲&amp;quot;无为&amp;quot;&lt;/h2&gt;
&lt;p&gt;老子的&amp;quot;无为而治&amp;quot;常被误解成&amp;quot;什么都不做&amp;quot;。它的真实意思是：&lt;strong&gt;不妄为、不多为、让事物按自己的规律运转，治理者只搭最小的框架&lt;/strong&gt;。这篇博客的姊妹篇（道家经典笔记）里聊过这个，但当时我没想到，技术世界里有一套哲学和它是镜像关系——Unix 哲学。&lt;/p&gt;
&lt;p&gt;《Unix 编程艺术》这本书表面上是讲操作系统历史、讲工具、讲惯例，但通读下来，它的主线只有一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;好的设计，是让每样东西只做一件事、把它做好，然后靠组合解决大问题——你写的代码越少，系统越长寿。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这就是&amp;quot;无为&amp;quot;的操作系统版：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无为 ≠ 不写代码，而是&lt;strong&gt;不写多余的代码&lt;/strong&gt;（吝啬原则、经济原则）；&lt;/li&gt;
&lt;li&gt;无为而治 ≠ 没有规则，而是&lt;strong&gt;规则极少且稳定，万物在规则下自由组合&lt;/strong&gt;（组合原则、文本流接口）；&lt;/li&gt;
&lt;li&gt;为而不恃 ≠ 没有接口，而是&lt;strong&gt;接口低调透明，不强迫用户感知它&lt;/strong&gt;（透明原则、缄默原则）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后面所有的原则、公案、争论，都是这一条主线的展开。先记住主线，再看细节，就不会把 17 条原则背成口诀表。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-一句话理解-unix-哲学&#34;&gt;1. 一句话理解 Unix 哲学&lt;/h2&gt;
&lt;p&gt;关于 Unix 哲学有很多版本的一句话总结，流传最广的是 Doug McIlroy（管道的发明人）在 1978 年《Bell System Technical Journal》里写的那段：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;让每个程序只做好一件事。要做新功能，就写个新程序，而不是给旧程序加&amp;quot;特性&amp;quot;把旧程序搞复杂。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;再加两句配套的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;让每个程序的输出，都能成为另一个（还没写出来的）程序的输入。不要因为无关信息把输出搞得花里胡哨。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以及后世程序员最爱引用的简版：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do one thing and do it well.（做一件事，把它做好。）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注意 McIlroy 原话里最关键、也最容易被忽略的半句：&lt;strong&gt;&amp;ldquo;要做新功能，就写个新程序&amp;rdquo;&lt;/strong&gt;。这不是审美洁癖，而是工程判断——往旧程序里加功能，会把原本干净的工具变成一坨没人敢动的遗留代码。这条在后面 &lt;code&gt;cat -v&lt;/code&gt; 之争里会血淋淋地再出现一次。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-十七原则全景哪些是宪法哪些是实施细则&#34;&gt;2. 十七原则全景：哪些是&amp;quot;宪法&amp;quot;，哪些是&amp;quot;实施细则&amp;quot;&lt;/h2&gt;
&lt;p&gt;TAOUP 第一章把 Unix 哲学整理成了 17 条原则。我不打算逐条复述（那是目录，不是消化），先给一张全景图，再挑出真正管用的五条深讲。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;mindmap
  root((Unix哲学&amp;lt;br/&amp;gt;无为=不写多余代码))
    核心五条
      简单原则
        能简单就别复杂
        KISS
      组合原则
        程序互相连接
        文本流是通用接口
      透明原则
        一眼看穿在干嘛
        可调试可维护
      缄默原则
        没话就别说话
        输出即接口
      经济原则
        程序员时间最贵
        宁写一次脚本不重复劳动
    设计姿态
      模块原则
        清晰接口的简洁部件
      分离原则
        策略与机制分离
        接口与引擎分离
      吝啬原则
        小即是美
        除非必要别写大程序
    工程纪律
      清晰原则
        清晰胜过机巧
      稳健原则
        稳健源于透明加简单
      补救原则
        出错要吵要死得早
      优化原则
        先求跑通再求跑快
    生态智慧
      表示原则
        把知识叠进数据
      通俗原则
        最小惊讶原则
      生成原则
        能生成就不手写
      多样原则
        拒绝唯一正确方式
      扩展原则
        为将来留余地&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;21-哪些是宪法&#34;&gt;2.1 哪些是宪法？&lt;/h3&gt;
&lt;p&gt;17 条不是平权的。我自己的判断（也是 Raymond 书里的隐含排序）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;宪法级&lt;/strong&gt;：简单、组合、分离。这三条决定系统的基本骨架，违反了就救不回来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日常纪律级&lt;/strong&gt;：透明、缄默、吝啬、经济、清晰、补救。这些决定你每天写代码的手感。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生态智慧级&lt;/strong&gt;：表示、生成、多样、扩展、优化、通俗、稳健。这些决定系统活十年之后的样子。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面只深讲五条最核心、也最容易被违反的。&lt;/p&gt;
&lt;h3 id=&#34;22-简单原则simplicity能简单就别复杂&#34;&gt;2.2 简单原则（Simplicity）：能简单就别复杂&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design for simplicity; add complexity only where you must.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是所有原则的根。复杂软件的问题不是&amp;quot;难写&amp;quot;，而是&lt;strong&gt;难改、难测、难理解&lt;/strong&gt;——复杂度是利息，每次改动都在还息。&lt;/p&gt;
&lt;p&gt;程序员最常见的自欺是：&amp;ldquo;这个功能以后肯定用得上，先设计成通用的。&amp;ldquo;结果通用框架写好了三年，那个&amp;quot;以后&amp;quot;从没来过，而每个新人都得先学懂这个没人用的框架才能干活。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑指南&lt;/strong&gt;：当你设计一个系统需要超过一页纸才能向新同事解释清楚时，先怀疑设计，而不是怀疑新同事。&lt;/p&gt;
&lt;h3 id=&#34;23-组合原则composition程序要能被接起来&#34;&gt;2.3 组合原则（Composition）：程序要能被&amp;quot;接&amp;quot;起来&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design programs to be connected with other programs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条是 Unix 真正的独创，也是它和同时代其他操作系统最大的区别。别的系统里，程序是一个个孤岛，各干各的；Unix 里的程序天生设计成&lt;strong&gt;可以被串起来&lt;/strong&gt;——输入是文本流，输出也是文本流，于是任何两个程序都能被一根管道接起来，哪怕写第一个程序的人根本不知道第二个程序的存在。&lt;/p&gt;
&lt;p&gt;这条原则的威力在第 4 节的 McIlroy 公案里会展现得淋漓尽致。先记住一个推论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个程序如果不能被组合，它的能力上限就是它自己的代码量；一个能被组合的程序，能力上限是整个系统里所有程序的能力之积。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;24-透明原则transparency一眼看穿系统在干嘛&#34;&gt;2.4 透明原则（Transparency）：一眼看穿系统在干嘛&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Design for visibility to make inspection and debugging easier.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;透明性的标准是：&lt;strong&gt;系统的行为能不能被一个普通工程师&amp;quot;看出来&amp;rdquo;，而不是&amp;quot;猜出来&amp;rdquo;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反面教材：配置写在一个神秘的二进制数据库里、错误码只有厂商文档能查、出了问题只能重启看运气。正面教材：配置文件是文本、日志是人话、&lt;code&gt;strace&lt;/code&gt; 一看就知道进程在干什么。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：现在面试常考的&amp;quot;可观测性&amp;quot;（observability）——metrics、tracing、structured logging——本质上就是透明原则在云原生时代的重新发明。50 年前 Unix 用文本和管道做的事，今天我们用 OpenTelemetry 再做一遍。&lt;/p&gt;
&lt;h3 id=&#34;25-缄默原则silence没话就闭嘴&#34;&gt;2.5 缄默原则（Silence）：没话就闭嘴&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：When a program has nothing surprising to say, it should say nothing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条最反直觉，新手程序员最爱犯：程序跑成功了，打印一行&amp;quot;操作成功完成！&amp;quot;；循环处理一万条数据，每条打一行进度。&lt;/p&gt;
&lt;p&gt;为什么这是错的？因为 Unix 程序的&lt;strong&gt;输出是接口&lt;/strong&gt;。&lt;code&gt;ls | wc -l&lt;/code&gt; 能数出目录里有几个文件，前提是 &lt;code&gt;ls&lt;/code&gt; 不会在末尾加一行&amp;quot;共 42 个文件，查询成功！&amp;quot;。一句多余的人话，对人是客气，对下游程序是脏数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这条在今天的对应物是日志纪律——INFO 级别不该打印&amp;quot;一切正常&amp;quot;的心跳废话，告警不该在无事发生时刷屏。告警疲劳（alert fatigue）就是缄默原则被违反的现代版本：狼来了喊一千次，真的狼来了没人理。&lt;/p&gt;
&lt;h3 id=&#34;26-经济原则economy程序员的时间比机器的时间贵&#34;&gt;2.6 经济原则（Economy）：程序员的时间比机器的时间贵&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原文：Programmer time is expensive; conserve it in preference to machine time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;1970 年代机器慢、人便宜，这条原则在当时是革命性的；今天机器快得离谱、工程师年薪以十万计，这条原则更加正确。推论是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;能用 shell 脚本十分钟糊出来的临时工具，就别开 IDE 建工程写三天；&lt;/li&gt;
&lt;li&gt;能用高一级的语言（当年是 C vs 汇编，今天是 Python vs C++）表达，就别用低一级语言省那点机器时间；&lt;/li&gt;
&lt;li&gt;机器闲着的每一秒都不值钱，你加班的每一小时都很值钱。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这条原则是&amp;quot;无为&amp;quot;最直接的落地：&lt;strong&gt;能不做就不做，能少做就少做，把省下来的时间花在那件真正值得做的事上。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-管道与过滤器unix-哲学的物理化身&#34;&gt;3. 管道与过滤器：Unix 哲学的物理化身&lt;/h2&gt;
&lt;h3 id=&#34;31-一夜诞生的管道&#34;&gt;3.1 一夜诞生的管道&lt;/h3&gt;
&lt;p&gt;管道（&lt;code&gt;|&lt;/code&gt;）的故事是软件史上最著名的&amp;quot;一夜奇迹&amp;quot;之一，值得完整讲一遍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1964 年&lt;/strong&gt;，Doug McIlroy 就在贝尔实验室的内部备忘录里写下了类似的想法：&amp;ldquo;我们应该有某种方法，能把程序像花园水管一样接起来——把一段数据拧进另一段。&amp;quot;（&amp;ldquo;We should have some ways of coupling programs like garden hose.&amp;quot;）&lt;/li&gt;
&lt;li&gt;想法提了快十年，一直没人实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1973 年的一天&lt;/strong&gt;，McIlroy 又一次向 Ken Thompson 提起这个想法。Thompson 当晚回家，&lt;strong&gt;一夜之间修改了 Unix 内核和 shell&lt;/strong&gt;，把管道做了出来，顺手改了一批工具让它们能配合管道工作。&lt;/li&gt;
&lt;li&gt;第二天早上，管道就成了 Unix 的永久居民。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;McIlroy 后来回忆，那是 Unix 历史上最重要的时刻之一——一个想了十年的概念，被一个理解它的人用一晚上变成了现实。这个故事同时也是经济原则的最佳注脚：&lt;strong&gt;想清楚了再动手，动手就是一夜的事；没想清楚的团队，开会开十年。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;32-文本流最简陋也最长寿的通用接口&#34;&gt;3.2 文本流：最简陋也最长寿的通用接口&lt;/h3&gt;
&lt;p&gt;管道为什么能串起一切？因为所有程序约定了同一个数据格式：&lt;strong&gt;字节流，惯例上是按行组织的文本。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个接口&amp;quot;简陋&amp;quot;到可笑——没有 schema、没有类型检查、没有版本号。但正因如此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1970 年写的程序，输出可以被 2023 年写的程序消费，中间隔了半个世纪、几代程序员、无数硬件更迭；&lt;/li&gt;
&lt;li&gt;任何新程序只要读 stdin、写 stdout，立刻自动获得与系统里&lt;strong&gt;所有&lt;/strong&gt;工具组合的能力，不用跟谁谈判、不用签接口协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对比一下同时代的其他方案：IBM 的作业控制语言 JCL 要你先定义数据集格式，各种 RPC 框架要你先对齐接口定义。它们都更&amp;quot;严谨&amp;rdquo;，也都更快地死掉了。文本流活了 50 多年，至今是你每天 &lt;code&gt;kubectl get pods | grep xxx | awk &#39;{print $1}&#39;&lt;/code&gt; 时正在用的东西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：文本流是接口设计里的&amp;quot;中庸之道&amp;rdquo;——结构刚好够工具处理（按行、按空格分列），又松散到谁都能产生、谁都能消费。&lt;/p&gt;
&lt;h3 id=&#34;33-过滤器模式把程序写成水管中间的一截&#34;&gt;3.3 过滤器模式：把程序写成水管中间的一截&lt;/h3&gt;
&lt;p&gt;管道催生了一种程序形态：&lt;strong&gt;过滤器（filter）&lt;/strong&gt;——从 stdin 读，变换一下，往 stdout 写。&lt;code&gt;grep&lt;/code&gt;、&lt;code&gt;sort&lt;/code&gt;、&lt;code&gt;uniq&lt;/code&gt;、&lt;code&gt;awk&lt;/code&gt;、&lt;code&gt;sed&lt;/code&gt;、&lt;code&gt;tr&lt;/code&gt; 全是过滤器。&lt;/p&gt;
&lt;p&gt;过滤器的可怕之处在于&lt;strong&gt;乘法效应&lt;/strong&gt;：系统里有 N 个过滤器，理论上就有 N! 种串法（当然实际有意义的少得多，但数量级是对的）。每新增一个过滤器，系统的总能力不是 +1，而是 ×N。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;输入: 任意文本&amp;lt;br/&amp;gt;(文件/网络/进程输出)&amp;#34;] --&amp;gt; B[&amp;#34;过滤器1&amp;lt;br/&amp;gt;grep/sed/tr&amp;#34;]
    B --&amp;gt; C[&amp;#34;过滤器2&amp;lt;br/&amp;gt;sort/awk&amp;#34;]
    C --&amp;gt; D[&amp;#34;过滤器3&amp;lt;br/&amp;gt;uniq/wc/head&amp;#34;]
    D --&amp;gt; E[&amp;#34;输出: 依然是文本&amp;lt;br/&amp;gt;可继续被消费&amp;#34;]
    B -. &amp;#34;每个程序只做一件事&amp;#34; .-&amp;gt; B
    C -. &amp;#34;接口统一为文本流&amp;#34; .-&amp;gt; C
    D -. &amp;#34;组合出无限可能&amp;#34; .-&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这个模式的后代到处都是——MapReduce 的 map/reduce 是大规模版过滤器，Kafka 的 stream processing 是分布式版管道，React 的数据流、shell 出身的 Unix 老兵们设计的 CI/CD pipeline，全是同一张脸。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-经典公案六行-shell-挑战-knuth&#34;&gt;4. 经典公案：六行 shell 挑战 Knuth&lt;/h2&gt;
&lt;p&gt;这是软件史上我最喜欢的公案，没有之一，必须完整讲。&lt;/p&gt;
&lt;h3 id=&#34;41-背景bentley-出题knuth-接招&#34;&gt;4.1 背景：Bentley 出题，Knuth 接招&lt;/h3&gt;
&lt;p&gt;1986 年，Jon Bentley 在《Communications of the ACM》的&amp;quot;Programming Pearls&amp;quot;专栏出了一道题：&lt;strong&gt;给定一个文本文件和一个整数 N，输出出现频率最高的前 N 个单词及其次数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;出题的目的不是真缺这个功能，而是想展示当时最时髦的编程方法论。Bentley 请来了 Donald Knuth——图灵奖得主、《计算机程序设计艺术》作者——用他发明的**文学编程（literate programming）**来解答。&lt;/p&gt;
&lt;p&gt;Knuth 交了一份大约&lt;strong&gt;十页&lt;/strong&gt;的 WEB 程序（Pascal 代码与散文交织），精心设计了哈希表、用 trie 变体做词频统计，数据结构精巧、行文优雅，是文学编程的样板戏。&lt;/p&gt;
&lt;h3 id=&#34;42-mcilroy-的评论六行管道&#34;&gt;4.2 McIlroy 的评论：六行管道&lt;/h3&gt;
&lt;p&gt;Bentley 随后请 Doug McIlroy 写评论。McIlroy 先客气地称赞了 Knuth 的程序，然后平静地指出：这个任务，用 Unix 工具六行就能做完，而且更好——因为每一行都是久经考验的现成工具，没有一行是新写的、可能有 bug 的代码：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tr -cs A-Za-z &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tr A-Z a-z |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sort |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;uniq -c |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sort -rn |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed &lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;1&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;逐行翻译：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;tr -cs A-Za-z &#39;\n&#39;&lt;/code&gt;：把所有非字母字符换成换行——文本变成&amp;quot;每行一个词&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tr A-Z a-z&lt;/code&gt;：全部转小写——&amp;ldquo;The&amp;quot;和&amp;quot;the&amp;quot;算同一个词；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort&lt;/code&gt;：排序——相同的词现在挨在一起；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uniq -c&lt;/code&gt;：合并相邻重复行并计数——每行变成&amp;quot;次数 单词&amp;rdquo;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sort -rn&lt;/code&gt;：按数字逆序排——高频词浮到顶部；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sed ${1}q&lt;/code&gt;：只保留前 N 行（N 从命令行参数来）。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    T[&amp;#34;原始文本&amp;lt;br/&amp;gt;The quick brown fox&amp;lt;br/&amp;gt;the lazy dog&amp;#34;] --&amp;gt; S1[&amp;#34;tr: 拆成单词&amp;lt;br/&amp;gt;每行一个词&amp;#34;]
    S1 --&amp;gt; S2[&amp;#34;tr: 统一小写&amp;lt;br/&amp;gt;消除大小写干扰&amp;#34;]
    S2 --&amp;gt; S3[&amp;#34;sort: 排序&amp;lt;br/&amp;gt;相同单词聚到一起&amp;#34;]
    S3 --&amp;gt; S4[&amp;#34;uniq -c: 计数&amp;lt;br/&amp;gt;2 the&amp;lt;br/&amp;gt;1 brown&amp;lt;br/&amp;gt;1 dog&amp;#34;]
    S4 --&amp;gt; S5[&amp;#34;sort -rn: 按频次降序&amp;#34;]
    S5 --&amp;gt; S6[&amp;#34;sed Nq: 取前N名&amp;#34;]
    style S1 fill:#e8f4e8
    style S2 fill:#e8f4e8
    style S3 fill:#e8f4e8
    style S4 fill:#e8f4e8
    style S5 fill:#e8f4e8
    style S6 fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;43-这场公案到底说明了什么&#34;&gt;4.3 这场公案到底说明了什么&lt;/h3&gt;
&lt;p&gt;Knuth 本人输了吗？没有。他的哈希 trie 在大规模数据上确实更快，而且十页程序的初衷是展示文学编程，不是交付生产工具。这场公案真正的价值在于 McIlroy 的评论里那几句话的杀伤力：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Knuth 的程序里，每个数据结构都是新的、都需要正确性证明；shell 管道里没有一个数据结构是新的，sort 和 uniq 已经被全世界的人每天用了十几年。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这就是&amp;quot;无为&amp;quot;的极致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不写代码，就是最高的代码质量&lt;/strong&gt;——没有新写的数据结构，就没有新的 off-by-one 错误；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合现成工具，胜过手工打造精品&lt;/strong&gt;——文学编程追求的&amp;quot;像文学一样优雅的程序&amp;quot;，败给了&amp;quot;根本不用写程序&amp;quot;；&lt;/li&gt;
&lt;li&gt;顺便还赢了可读性：Knuth 的程序要读十页，McIlroy 的读十秒。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每次我看到团队为一个&amp;quot;一次性数据统计需求&amp;quot;立项、排期、写微服务，我都会想起这六行 shell。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-机制与策略分离x-window-的世纪教训&#34;&gt;5. 机制与策略分离：X Window 的世纪教训&lt;/h2&gt;
&lt;p&gt;分离原则（Separation）说：&lt;strong&gt;把策略（policy）和机制（mechanism）分开；把接口和引擎分开。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;机制&lt;/strong&gt;：能做什么——提供能力，比如&amp;quot;能在屏幕上画窗口、能接收鼠标点击&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略&lt;/strong&gt;：该怎么做——决定行为，比如&amp;quot;窗口标题栏长什么样、点击关闭按钮是杀进程还是最小化&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;51-x-window把不表态写进架构&#34;&gt;5.1 X Window：把&amp;quot;不表态&amp;quot;写进架构&lt;/h3&gt;
&lt;p&gt;Unix 世界对这个原则最彻底的实践是 &lt;strong&gt;X Window System（X11）&lt;/strong&gt;，1984 年起源于 MIT。X 的设计者把&amp;quot;机制，而非策略&amp;quot;（mechanism, not policy）直接写成了项目格言。&lt;/p&gt;
&lt;p&gt;具体怎么分的：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 策略层[&amp;#34;策略层(可由用户替换)&amp;#34;]
        WM[&amp;#34;窗口管理器&amp;lt;br/&amp;gt;决定标题栏样式/焦点行为&amp;#34;]
        TK[&amp;#34;控件工具包&amp;lt;br/&amp;gt;决定按钮长什么样&amp;#34;]
    end
    subgraph 机制层[&amp;#34;机制层(X服务器)&amp;#34;]
        XS[&amp;#34;X Server&amp;lt;br/&amp;gt;只管画像素/收输入事件&amp;lt;br/&amp;gt;不关心&amp;#39;窗口&amp;#39;概念&amp;#34;]
    end
    APP[&amp;#34;应用程序&amp;#34;] --&amp;gt; TK
    WM --&amp;gt; XS
    TK --&amp;gt; XS
    XS -. &amp;#34;协议(X protocol)&amp;#34; .- APP&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;X 服务器&lt;strong&gt;故意不知道&lt;/strong&gt;窗口应该长什么样、标题栏该有几个按钮。它只提供画矩形、画字、传鼠标键盘事件的机制。所有&amp;quot;外观和行为&amp;quot;都推给了可替换的窗口管理器和工具包。&lt;/p&gt;
&lt;p&gt;结果是惊人的长寿和多样性：同一套 X 协议跑了近四十年，上面先后长出过 Motif、GTK、Qt 等无数风格迥异的桌面环境，X 服务器本身几乎不用变。机制稳定，策略自由演化——这就是&amp;quot;无为而治&amp;quot;的架构版：底层定最少的规矩，上层爱怎么长就怎么长。&lt;/p&gt;
&lt;h3 id=&#34;52-反面教材策略焊死在机制里&#34;&gt;5.2 反面教材：策略焊死在机制里&lt;/h3&gt;
&lt;p&gt;对比之下，很多 GUI 系统把&amp;quot;按钮必须长这样、窗口必须这样关闭&amp;quot;直接焊死在系统底层。短期看开箱即用，长期看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想换一种交互风格？改不动底层，只能外挂 hack；&lt;/li&gt;
&lt;li&gt;底层想升级？牵一发而动全身，一动就破坏所有应用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：这个教训在今天依然天天上演——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes 的控制循环是机制，你的 CRD/Operator 是策略；&lt;/li&gt;
&lt;li&gt;HTTP 是机制，REST 风格是策略；&lt;/li&gt;
&lt;li&gt;编译器前端后端分离（LLVM 的 IR 是机制，各语言前端是策略）；&lt;/li&gt;
&lt;li&gt;你自己写的框架如果把&amp;quot;业务规则&amp;quot;和&amp;quot;执行引擎&amp;quot;揉在一个类里，就是在给三年后的自己埋雷。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：机制回答&amp;quot;能不能&amp;quot;，策略回答&amp;quot;该不该&amp;quot;。把&amp;quot;该不该&amp;quot;的决定权留给离用户最近的那层。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-cat--v-之争小即是美的保卫战&#34;&gt;6. cat -v 之争：小即是美的保卫战&lt;/h2&gt;
&lt;h3 id=&#34;61-一只猫引发的血案&#34;&gt;6.1 一只猫引发的血案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;cat&lt;/code&gt; 是 Unix 里最单纯的程序：把文件内容原样输出。它的名字来自 concatenate（拼接），&lt;code&gt;cat a b c &amp;gt; all&lt;/code&gt; 把三个文件拼成一个。就这么点功能，干净漂亮。&lt;/p&gt;
&lt;p&gt;后来 BSD（伯克利版 Unix）的开发者给 &lt;code&gt;cat&lt;/code&gt; 加了选项，比如 &lt;code&gt;-v&lt;/code&gt; 可以把不可见字符显示出来。听起来人畜无害，对吧？&lt;/p&gt;
&lt;p&gt;1983 年，Rob Pike 和 Brian Kernighan 在 USENIX 大会上发了一篇演讲，标题就很凶：&lt;strong&gt;《Program Design in the UNIX Environment》&lt;/strong&gt;，其中一节叫 &amp;ldquo;cat -v considered harmful&amp;rdquo;（化用 Dijkstra 的名篇）。核心论点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cat&lt;/code&gt; 的职责是&lt;strong&gt;拼接字节流&lt;/strong&gt;，&amp;ldquo;可视化不可见字符&amp;quot;是&lt;strong&gt;另一件事&lt;/strong&gt;，属于另一个程序；&lt;/li&gt;
&lt;li&gt;一旦 &lt;code&gt;cat&lt;/code&gt; 开始长选项，它就会无限长下去——今天是 &lt;code&gt;-v&lt;/code&gt;，明天是显示行号、压缩空行……Unix 后来的 cat 果然长出了 &lt;code&gt;-n&lt;/code&gt;、&lt;code&gt;-s&lt;/code&gt;、&lt;code&gt;-E&lt;/code&gt;、&lt;code&gt;-T&lt;/code&gt; 一长串；&lt;/li&gt;
&lt;li&gt;正确做法是写一个专门的小程序做可视化（Pike 他们后来写了 &lt;code&gt;vis&lt;/code&gt;），让 &lt;code&gt;cat&lt;/code&gt; 保持单纯。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;62-这不是洁癖是防熵增&#34;&gt;6.2 这不是洁癖，是防熵增&lt;/h3&gt;
&lt;p&gt;回头看，Pike 他们争的从来不是 &lt;code&gt;cat&lt;/code&gt; 这一个程序，而是一个普适规律：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;软件的自然演化方向是功能蔓延（feature creep），而每一次&amp;quot;顺手加个选项&amp;quot;都在加速它。今天不加护栏，三年后它就会变成没人敢动的屎山。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;工程连接&lt;/strong&gt;：你身边一定有那个&amp;quot;本来是 200 行的小工具，三年后变成 8000 行、40 个命令行参数、没人知道所有参数组合行为&amp;quot;的内部程序。它就是一场输掉了的 cat -v 战争。McIlroy 那句话在这里闭环了：&lt;strong&gt;&amp;ldquo;要做新功能，就写个新程序，而不是给旧程序加特性。&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;63-小即是美small-is-beautiful&#34;&gt;6.3 小即是美（Small is Beautiful）&lt;/h3&gt;
&lt;p&gt;cat -v 之争背后的正面主张，后来被 Mike Gancarz 总结成一本书的名字：《Linux and the Unix Philosophy》里的核心口号——&lt;strong&gt;Small is Beautiful（小即是美）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小程序容易写、容易测、容易懂、容易替换；&lt;/li&gt;
&lt;li&gt;大程序每一处都是沉没成本，坏了一个角就全盘皆输；&lt;/li&gt;
&lt;li&gt;一个 200 行的小工具，用不顺手可以扔掉重写；一个 8000 行的怪兽，你只能供着它。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-worse-is-betterunix-为什么能赢&#34;&gt;7. Worse is Better：Unix 为什么能赢&lt;/h2&gt;
&lt;h3 id=&#34;71-gabriel-的著名论断&#34;&gt;7.1 Gabriel 的著名论断&lt;/h3&gt;
&lt;p&gt;1989 年前后，Lisp 黑客 Richard Gabriel 写了一篇著名文章（后收录于《Lisp: Good News, Bad News, How to Win Big》，1991），提出了一个让理想主义者抓狂的概念：&lt;strong&gt;Worse is Better（更糟的就是更好的）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;他对比了两种设计哲学：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MIT/Stanford 路线（&amp;ldquo;正确的事&amp;rdquo;，The Right Thing）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设计必须完整、正确、一致、优雅；&lt;/li&gt;
&lt;li&gt;接口的简洁性没有实现的正确性重要；&lt;/li&gt;
&lt;li&gt;代表：Lisp 机器、Scheme 的理想主义一脉。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;新泽西路线（Worse is Better，新泽西 = 贝尔实验室所在地）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;实现的简单性优先于一切&lt;/strong&gt;，接口简单、正确性、一致性都要为它让路；&lt;/li&gt;
&lt;li&gt;功能做 50% 能跑，好过做 90% 但没人写完；&lt;/li&gt;
&lt;li&gt;代表：C 和 Unix。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gabriel 的观察是：那个&amp;quot;更糟&amp;quot;的新泽西方案会赢，因为它&lt;strong&gt;像病毒一样可移植&lt;/strong&gt;——简单的东西容易实现、容易移植到任何机器上，先占领世界，再靠用户社区慢慢把它补齐。而&amp;quot;正确的事&amp;quot;因为太重、太贵、太难实现，永远停留在少数精英的实验室里。&lt;/p&gt;
&lt;h3 id=&#34;72-历史判卷gabriel-说对了&#34;&gt;7.2 历史判卷：Gabriel 说对了&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;C 语言有一堆&amp;quot;糟糕&amp;quot;的设计（缓冲区溢出、未定义行为），但它简单到一个人能写个编译器，于是 C 编译器铺满了地球上每一台机器；&lt;/li&gt;
&lt;li&gt;Unix 的系统调用接口远不如 Lisp 机器优雅，但 Unix 能跑在从 PDP-11 到超算的一切东西上；&lt;/li&gt;
&lt;li&gt;反观 Lisp 机器、各种&amp;quot;设计完美&amp;quot;的操作系统，坟头草已经三米高了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-但要小心这不是给烂代码发通行证&#34;&gt;7.3 但要小心：这不是给烂代码发通行证&lt;/h3&gt;
&lt;p&gt;Gabriel 本人对这篇文章的态度其实很矛盾（他甚至用化名写过反驳自己的文章）。Worse is better 描述的是&lt;strong&gt;演化的规律&lt;/strong&gt;，不是&lt;strong&gt;你写烂代码的借口&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它说的是&amp;quot;实现简单、先跑起来、让世界帮你完善&amp;quot;会赢；&lt;/li&gt;
&lt;li&gt;它&lt;strong&gt;不是&lt;/strong&gt;说&amp;quot;不测试、不处理错误、随便糊&amp;quot;会赢。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：先交付一个简单到不可能失败的 60 分方案，让它在真实世界里进化；不要在象牙塔里憋一个永远交付不了的 100 分方案。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-无用之用shell-脚本的临时救赎&#34;&gt;8. 无用之用：shell 脚本的临时救赎&lt;/h2&gt;
&lt;p&gt;道家讲&amp;quot;无用之用，方为大用&amp;rdquo;——看起来没用的东西，往往有大用。Shell 脚本就是编程世界里的&amp;quot;无用之用&amp;quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它&amp;quot;不入流&amp;quot;：没有类型系统、没有 IDE 补全、面试不考、简历上不好意思写；&lt;/li&gt;
&lt;li&gt;但它解决的是&lt;strong&gt;真实世界里最高频的那类问题&lt;/strong&gt;：一次性的、临时的、不值得立项的脏活。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举几个我自己（和每个老 Unix 用户）每周都在做的事：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 找出日志里报错最多的 10 个接口&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep ERROR app.log | awk &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{print $5}&amp;#39;&lt;/span&gt; | sort | uniq -c | sort -rn | head
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 把 300 个 .jpeg 文件批量重命名成 .jpg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in *.jpeg; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; mv &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;f%.jpeg&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.jpg&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 监控某个进程，挂了自动拉起（穷人版 supervisor）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; true; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; ./myserver &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; sleep 5; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这些脚本&amp;quot;丑陋&amp;quot;、&amp;ldquo;脆弱&amp;rdquo;、&amp;ldquo;不可维护&amp;rdquo;——都没错，但它们本来就是&lt;strong&gt;一次性用品&lt;/strong&gt;，写完跑完就扔。为它们搞工程化、写单元测试、上代码评审，恰恰违反了经济原则：程序员的时间比机器贵，也比&amp;quot;代码的体面&amp;quot;贵。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键判断力&lt;/strong&gt;在于分清楚两类问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;临时问题&lt;/strong&gt; → shell 糊一个，跑完即弃，这是&amp;quot;无为&amp;quot;的胜利；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反复出现的问题&lt;/strong&gt; → 那就不是临时问题了，老老实实写成正经工具，别拿&amp;quot;反正临时用用&amp;quot;当借口让脚本混进生产系统核心路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;区分错了方向都很惨：给临时问题过度工程化是浪费生命，让临时脚本变成永久依赖是埋雷（无数公司的核心发布流程，起点都是某人&amp;quot;临时写一下&amp;quot;的 500 行 bash）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-与无为而治的正式呼应&#34;&gt;9. 与&amp;quot;无为而治&amp;quot;的正式呼应&lt;/h2&gt;
&lt;p&gt;呼应一下开头，把这条镜像关系钉死：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;道家&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;Unix 哲学&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;无为 = 不妄为&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;不写多余的代码（吝啬/经济原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;治大国若烹小鲜&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;大系统由小工具组合而成（组合原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;道法自然&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;顺应文本流/管道这个&amp;quot;自然接口&amp;quot;，不发明私有协议&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;上善若水，水善利万物而不争&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;过滤器程序低调透明，只过数据不刷存在感（缄默原则）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;圣人无常心，以百姓心为心&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;机制不预设策略，把选择权留给用户（X Window）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;无用之用&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;shell 一次性脚本、简单的文本接口反而最长寿&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;但注意：&lt;strong&gt;这本书的主体是工程，不是玄学。&lt;/strong&gt; 道家给的是一个好用的直觉框架，真正支撑 Unix 哲学的是 50 年的工程证据——管道的胜利、文本流的长寿、cat -v 的沦陷、Lisp 机器的坟墓。直觉负责让你快速做出方向正确的决定，证据负责在你被质疑时撑住你。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;10-带走的清单&#34;&gt;10. 带走的清单&lt;/h2&gt;
&lt;p&gt;写代码或做设计之前，过一遍这十条（按 17 原则提炼后的实战版）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;能不写就不写&lt;/strong&gt;：这个问题用现有工具组合能解决吗？（McIlroy 公案）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一次只做一件事&lt;/strong&gt;：这个程序/类/函数，能用一句话说清职责吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出即接口&lt;/strong&gt;：这个程序的输出，能被另一个还没写出来的程序直接消费吗？（缄默原则）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用文本说事&lt;/strong&gt;：这个配置/日志/接口，是人能直接读的文本吗？（透明原则）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机制和策略分开了吗&lt;/strong&gt;：我写的这层，是提供能力，还是替用户做决定？（X Window 教训）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先跑起来再说&lt;/strong&gt;：现在的版本是不是简单到不可能失败？（Worse is better）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这是临时问题吗&lt;/strong&gt;：是 → 脚本糊；不是 → 正经写。别搞反。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我在加功能还是加复杂度&lt;/strong&gt;：这个&amp;quot;顺手加的小功能&amp;quot;，是不是下一个 cat -v？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂度在哪一层&lt;/strong&gt;：不得不复杂时，复杂度被隔离在最小范围了吗？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删掉一半会怎样&lt;/strong&gt;：如果砍掉一半功能，核心用户还在吗？在 → 砍。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;11-避坑指南对-unix-哲学最常见的四种误用&#34;&gt;11. 避坑指南：对 Unix 哲学最常见的四种误用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;坑一：把 KISS 当懒惰的挡箭牌。&lt;/strong&gt; &amp;ldquo;简单&amp;quot;指的是设计和实现的简单，不是&amp;quot;不做需求、不处理边界情况&amp;rdquo;。Knuth 的词频程序虽然输给了六行 shell，但它处理的性能边界是 shell 版处理不了的——该复杂的地方必须复杂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑二：把 worse is better 当烂代码许可证。&lt;/strong&gt; 它描述的是&amp;quot;简单实现先活下来再进化&amp;quot;的演化规律，不是&amp;quot;别写测试、别管错误处理&amp;quot;。60 分方案的前提是&lt;strong&gt;边界清晰、失败模式明确&lt;/strong&gt;，不是糊。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑三：把&amp;quot;小即是美&amp;quot;理解成微服务越多越好。&lt;/strong&gt; 微服务拆分如果只分了部署、没分清楚接口，得到的是分布式屎山。组合原则的前提是&lt;strong&gt;每个部件接口干净&lt;/strong&gt;，否则 N 个服务只有 N 倍的运维痛苦，没有组合红利。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坑四：用 shell 糊生产核心。&lt;/strong&gt; 一次性脚本的正确归宿是跑完即弃（或转正为正经工具）。让 500 行没人看懂的 bash 长在发布流程的心脏上，是把&amp;quot;无用之用&amp;quot;用成了&amp;quot;无用之害&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;12-结语&#34;&gt;12. 结语&lt;/h2&gt;
&lt;p&gt;《Unix 编程艺术》出版于 2003 年，讲的是 1969 年开始的故事，但它预言对了此后二十年几乎所有的技术走向：文本协议（HTTP/JSON）打败二进制协议，组合式工具（容器、K8s、CI/CD pipeline）打败单体巨兽，&amp;ldquo;简单实现先跑起来&amp;rdquo;（Python、Go 的设计取向）打败&amp;quot;正确的事&amp;quot;。&lt;/p&gt;
&lt;p&gt;回到那句主线：&lt;strong&gt;无为 = 不写多余的代码。&lt;/strong&gt; 最高明的程序员不是写得最快的人，而是最清楚哪行代码不该存在的人。就像老子说的，为学日益，为道日损——学技术是不断加法，懂设计是不断减法。&lt;/p&gt;
&lt;p&gt;这本书值得每五年重读一遍，因为你对&amp;quot;哪行代码不该存在&amp;quot;的判断力，会随踩坑数量逐年升级。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>基于 UDP 通信的洋葱网络设计：服务器、Android 客户端与 P2P 控制总结</title>
      <link>https://ftmao.com/blog/posts/udp-onion-network-and-p2p-design/</link>
      <pubDate>Tue, 19 Dec 2017 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/udp-onion-network-and-p2p-design/</guid>
      <description>&lt;h1 id=&#34;基于-udp-通信的洋葱网络设计服务器android-客户端与-p2p-控制总结&#34;&gt;基于 UDP 通信的洋葱网络设计：服务器、Android 客户端与 P2P 控制总结&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;最近要实现端到端的加密通信，现在把设计方案记录下来。目标读者：刚接触洋葱网络、P2P 或 UDP 信令设计，想了解「怎么用 UDP 搭一个匿名转发网络，再基于它做 P2P」的同学。&lt;br&gt;
阅读目标：建立整体架构图、理解关键模块职责、记住常见踩坑点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面为什么用-udp-做洋葱网络--p2p-信令&#34;&gt;0. 写在前面：为什么用 UDP 做洋葱网络 + P2P 信令？&lt;/h2&gt;
&lt;p&gt;洋葱网络（Onion Network）的核心思想是：&lt;strong&gt;数据像剥洋葱一样，一层一层加密转发&lt;/strong&gt;。每一跳节点只知道上一跳和下一跳，不知道完整路径，从而保护通信双方的隐私。&lt;/p&gt;
&lt;p&gt;传统洋葱路由（如 Tor）多跑在 TCP 上，因为 TCP 可靠、有序。但在移动场景或 P2P 场景里，UDP 有几个独特优势：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;开销小&lt;/strong&gt;：UDP 头只有 8 字节，没有连接建立/拆除开销，适合移动弱网。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NAT 友好&lt;/strong&gt;：UDP 是 NAT 打洞（Hole Punching）最常用的传输层协议，STUN/TURN/ICE 都优先用 UDP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适合信令&lt;/strong&gt;：P2P 建立连接前的「打招呼」消息很短，UDP 足够用；真正的大数据再切到 P2P 直连或中继。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;抗审查更灵活&lt;/strong&gt;：无连接特性让流量更像普通 DNS/QUIC/游戏报文，便于混淆。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但 UDP 也带来问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不保证可靠、不保证顺序，需要自己在应用层做重传、去重、拥塞控制。&lt;/li&gt;
&lt;li&gt;报文可能被分片、被防火墙限制、被运营商 QoS。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这篇文章会带你从「一个 UDP 报文怎么进洋葱网络」开始，逐步拆解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;洋葱网络的层次结构与加密过程。&lt;/li&gt;
&lt;li&gt;服务器（目录服务 + 中继 + 信令服务）应该怎么设计。&lt;/li&gt;
&lt;li&gt;Android 客户端的结构与关键代码思路。&lt;/li&gt;
&lt;li&gt;怎么用这套 UDP 信令网络做 P2P 控制（NAT 穿透、打洞、直连、中继回退）。&lt;/li&gt;
&lt;li&gt;新人最容易踩的坑与规避方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-洋葱网络核心概念先建立知识地图&#34;&gt;1. 洋葱网络核心概念：先建立「知识地图」&lt;/h2&gt;
&lt;h3 id=&#34;11-洋葱路由-vs-普通代理的区别&#34;&gt;1.1 洋葱路由 vs 普通代理的区别&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;对比项&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;普通代理&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;洋葱网络&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;路径&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;单跳&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;多跳（通常 3 跳）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;加密&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;端到端一次加密&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;每跳一层加密&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;节点知情范围&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;代理知道你是谁、你访问哪&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;每跳只知道上一跳和下一跳&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;代表&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;HTTP/SOCKS 代理&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;Tor、I2P、自定义洋葱网络&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;12-一次洋葱转发的过程&#34;&gt;1.2 一次洋葱转发的过程&lt;/h3&gt;
&lt;p&gt;假设 Alice 要给 Bob 发消息，路径为：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[Alice] --&amp;gt; E[&amp;#34;入口节点 (Entry)&amp;#34;] --&amp;gt; M[&amp;#34;中间节点 (Middle)&amp;#34;] --&amp;gt; X[&amp;#34;出口节点 (Exit)&amp;#34;] --&amp;gt; B[Bob]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;Alice 会先把报文&lt;strong&gt;从后往前&lt;/strong&gt;逐层加密：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 Exit 的公钥加密「Exit → Bob 的真实数据」，得到 &lt;code&gt;C3&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用 Middle 的公钥加密「下一站是 Exit + C3」，得到 &lt;code&gt;C2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用 Entry 的公钥加密「下一站是 Middle + C2」，得到 &lt;code&gt;C1&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然后 Alice 把 &lt;code&gt;C1&lt;/code&gt; 发给 Entry。Entry 用自己的私钥解密后，看到「下一站是 Middle」，把 &lt;code&gt;C2&lt;/code&gt; 转发给 Middle。Middle 解密后转发 &lt;code&gt;C3&lt;/code&gt; 给 Exit。Exit 解密后拿到真实数据，发给 Bob。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[Alice] --&amp;gt;|C1| E[Entry&amp;lt;br/&amp;gt;解密: 下一站 Middle] --&amp;gt;|C2| M[Middle&amp;lt;br/&amp;gt;解密: 下一站 Exit] --&amp;gt;|C3| X[Exit&amp;lt;br/&amp;gt;解密: 真实数据] --&amp;gt;|明文| B[Bob]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人记忆点&lt;/strong&gt;：洋葱网络的数据是「加密好的俄罗斯套娃」，每到一个节点就剥掉一层外衣。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;13-udp-洋葱报文的基本结构&#34;&gt;1.3 UDP 洋葱报文的基本结构&lt;/h3&gt;
&lt;p&gt;一个 UDP 洋葱报文可以简单设计为：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+----------------+----------------+----------------+----------------+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|  版本 (1B)     |  命令 (1B)     |    跳数 (1B)   |   预留 (1B)    |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+----------------+----------------+----------------+----------------+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                        会话 ID / 电路 ID (4B)                       |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+----------------+----------------+----------------+----------------+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                         下一跳洋葱层数据 ...                       |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;版本&lt;/strong&gt;：协议版本，方便后续升级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;命令&lt;/strong&gt;：路由、数据、建立、销毁、心跳等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳数 / TTL&lt;/strong&gt;：防止报文无限循环，每经过一跳减 1。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;会话 ID / 电路 ID&lt;/strong&gt;：标识一条逻辑链路，让节点知道该用哪把密钥解密。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下一跳洋葱层数据&lt;/strong&gt;：被当前节点私钥解密后，才能得到真正的下一跳地址和载荷。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人容易混淆的点&lt;/strong&gt;：洋葱网络里的「加密」不是端到端 TLS，而是&lt;strong&gt;逐跳加密&lt;/strong&gt;。每一跳都能解密自己最外层，但看不到内层。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-系统整体架构&#34;&gt;2. 系统整体架构&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 公网服务层
        DS[Directory Server&amp;lt;br/&amp;gt;目录服务：发布可信节点]
        SS[Signaling Server&amp;lt;br/&amp;gt;信令服务：NAT 信息交换]
        RS[Relay Server&amp;lt;br/&amp;gt;中继服务：UDP 洋葱转发]
    end

    subgraph Android 客户端
        AC[App 业务层]
        CM[Connection Manager&amp;lt;br/&amp;gt;管理 Circuits / Peers]
        OC[Onion Core&amp;lt;br/&amp;gt;分层加密 / 解密]
        NC[Network Core&amp;lt;br/&amp;gt;UDP Socket / NAT / ICE]
    end

    subgraph 对端
        P2P[Peer B&amp;lt;br/&amp;gt;Android / Server / IoT]
    end

    AC --&amp;gt; CM
    CM --&amp;gt; OC
    CM --&amp;gt; NC
    OC --&amp;gt; NC
    NC --&amp;gt;|UDP| RS
    NC --&amp;gt;|UDP| SS
    NC -.-&amp;gt;|查询节点列表| DS
    NC -.-&amp;gt;|P2P 直连或中继| P2P&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;21-三类服务器职责&#34;&gt;2.1 三类服务器职责&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;服务&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;职责&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;协议&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Directory Server（目录服务）&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;维护可信中继节点列表、公钥、负载、版本&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;HTTPS / UDP 查询&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Signaling Server（信令服务）&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;帮助两个 Peer 交换公网 IP:Port、NAT 类型、候选地址&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;UDP + 可靠信令协议&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Relay Server（中继服务）&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;当 P2P 打洞失败时，转发 UDP 洋葱流量&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;UDP 洋葱路由&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;实际部署中，目录服务可以用 HTTPS 更稳；信令和中继为了速度和 NAT 友好，优先用 UDP。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-服务器端设计&#34;&gt;3. 服务器端设计&lt;/h2&gt;
&lt;h3 id=&#34;31-目录服务directory-server&#34;&gt;3.1 目录服务（Directory Server）&lt;/h3&gt;
&lt;p&gt;目录服务就像「黄页」，让客户端知道哪些中继节点在线、可信、负载低。&lt;/p&gt;
&lt;h4 id=&#34;核心数据模型&#34;&gt;核心数据模型&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Node {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    node_id:        节点唯一 ID
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    public_key:     Ed25519/X25519 公钥
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    endpoint:       公网 IP:UDP 端口
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bandwidth:      声明带宽（Mbps）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    load:           当前负载（连接数 / 带宽占用）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    version:        协议版本
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    flags:          入口 / 中间 / 出口 / 中继 标志位
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    last_seen:      最后一次心跳时间
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    signature:      节点用私钥对以上字段签名
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h4 id=&#34;客户端获取节点列表的流程&#34;&gt;客户端获取节点列表的流程&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant C as Android Client
    participant D as Directory Server
    participant N1 as Node Entry
    participant N2 as Node Middle
    participant N3 as Node Exit

    C-&amp;gt;&amp;gt;D: HTTPS GET /nodes?count=3&amp;amp;flags=entry|middle|exit
    D--&amp;gt;&amp;gt;C: 返回节点列表 + 签名
    C-&amp;gt;&amp;gt;C: 验证签名、选择路径
    C-&amp;gt;&amp;gt;N1: UDP 建立 Circuit 请求
    N1--&amp;gt;&amp;gt;C: 返回 Circuit ID
    C-&amp;gt;&amp;gt;N1: 经过 N1 转发到 N2 建立
    N2--&amp;gt;&amp;gt;C: 返回确认
    C-&amp;gt;&amp;gt;N2: 经过 N1→N2 转发到 N3 建立
    N3--&amp;gt;&amp;gt;C: 返回确认&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人避坑&lt;/strong&gt;：目录服务返回的节点列表必须带签名，客户端要校验，否则会被恶意节点钓鱼。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;32-中继服务relay-server&#34;&gt;3.2 中继服务（Relay Server）&lt;/h3&gt;
&lt;p&gt;中继服务是洋葱网络里的「快递员」：收到一个 UDP 报文，解密最外层，转发给下一跳。&lt;/p&gt;
&lt;h4 id=&#34;核心状态&#34;&gt;核心状态&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Circuit {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    circuit_id:     本节点分配的 32 位 ID
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    prev_node:      上一跳地址（IP:Port）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    next_node:      下一跳地址（IP:Port）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    shared_key:     与创建者协商的共享密钥
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    created_at:     创建时间
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    last_active:    最后活跃时间
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bytes_forward:  转发字节数
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h4 id=&#34;转发流程&#34;&gt;转发流程&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[收到 UDP 报文] --&amp;gt; B{报文格式合法?}
    B --&amp;gt;|否| C[直接丢弃]
    B --&amp;gt;|是| D[查找 circuit_id]
    D --&amp;gt; E{存在?}
    E --&amp;gt;|否| C
    E --&amp;gt;|是| F[用 shared_key 解密洋葱层]
    F --&amp;gt; G{解密成功?}
    G --&amp;gt;|否| C
    G --&amp;gt;|是| H[读取下一跳地址 + 内层数据]
    H --&amp;gt; I[TTL - 1]
    I --&amp;gt; J{TTL &amp;gt; 0?}
    J --&amp;gt;|否| K[发送销毁通知]
    J --&amp;gt;|是| L[转发给下一跳]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 id=&#34;关键设计点&#34;&gt;关键设计点&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;限速与防滥用&lt;/strong&gt;：单 Circuit 限制带宽，防止被当免费代理滥用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTL 与环检测&lt;/strong&gt;：每跳 TTL 减 1，到 0 丢弃，防止恶意构造环路。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密钥派生&lt;/strong&gt;：每个 Circuit 用 X25519 ECDH 协商出共享密钥，再用 HKDF 派出发送/接收两个方向的对称密钥。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无日志策略&lt;/strong&gt;：中继节点不记录明文内容、不记录完整路径（实际上也看不到）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;33-信令服务signaling-server&#34;&gt;3.3 信令服务（Signaling Server）&lt;/h3&gt;
&lt;p&gt;信令服务是 P2P 的「媒人」：两个 Peer 无法直接找到对方公网地址时，通过信令服务交换信息。&lt;/p&gt;
&lt;h4 id=&#34;数据模型&#34;&gt;数据模型&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PeerInfo {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    peer_id:        用户/设备唯一 ID
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    public_key:     用于加密信令的公钥
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    nat_type:       全锥型 / 限制型 / 端口限制型 / 对称型
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    local_candidates: [局域网 IP:Port, ...]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    srflx_candidates: [STUN 映射后的公网 IP:Port, ...]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    relay_candidates: [TURN 中继地址, ...]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    timestamp:      更新时间
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    signature:      防止伪造
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h4 id=&#34;典型信令流程&#34;&gt;典型信令流程&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as Peer A
    participant S as Signaling Server
    participant B as Peer B

    A-&amp;gt;&amp;gt;S: 注册/上报 PeerInfo（UDP 心跳包）
    B-&amp;gt;&amp;gt;S: 注册/上报 PeerInfo

    A-&amp;gt;&amp;gt;S: 请求连接 peer_B
    S-&amp;gt;&amp;gt;S: 检查 A、B 是否在线
    S--&amp;gt;&amp;gt;A: 返回 B 的候选地址列表
    S--&amp;gt;&amp;gt;B: 转发 A 的连接请求 + A 的候选地址

    A-&amp;gt;&amp;gt;A: 按优先级尝试直连 / 打洞
    B-&amp;gt;&amp;gt;B: 同时尝试直连 / 打洞
    A--&amp;gt;&amp;gt;B: UDP 打洞成功，建立 P2P
    B--&amp;gt;&amp;gt;A: 业务数据互通

    alt 打洞失败
        A-&amp;gt;&amp;gt;S: 请求中继
        S--&amp;gt;&amp;gt;A: 分配 Relay 地址
        S--&amp;gt;&amp;gt;B: 通知 B 通过 Relay 通信
        A-&amp;gt;&amp;gt;R: 经中继与 B 通信
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人避坑&lt;/strong&gt;：信令服务本身不传输业务数据，只传「怎么找到对方」。不要把它当成聊天服务器用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-android-客户端设计&#34;&gt;4. Android 客户端设计&lt;/h2&gt;
&lt;p&gt;Android 客户端需要同时处理：业务逻辑、洋葱路由、UDP 网络、NAT 穿透、后台保活。建议按层拆分。&lt;/p&gt;
&lt;h3 id=&#34;41-客户端分层架构&#34;&gt;4.1 客户端分层架构&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 应用层
        UI[UI / ViewModel]
        API[业务 API]
    end

    subgraph 网络中间层
        CM[ConnectionManager&amp;lt;br/&amp;gt;管理多 Circuit / 多 Peer]
        P2P[P2PController&amp;lt;br/&amp;gt;打洞 / 直连 / 中继决策]
        SM[SignalingClient&amp;lt;br/&amp;gt;与信令服务通信]
    end

    subgraph 核心引擎层
        OC[OnionCore&amp;lt;br/&amp;gt;加密 / 解密 / 报文封装]
        NC[NetworkCore&amp;lt;br/&amp;gt;UDP Socket / ICE / STUN]
        CR[CircuitStore&amp;lt;br/&amp;gt;持久化 Circuit 状态]
    end

    UI --&amp;gt; API
    API --&amp;gt; CM
    CM --&amp;gt; P2P
    CM --&amp;gt; SM
    CM --&amp;gt; OC
    P2P --&amp;gt; OC
    P2P --&amp;gt; NC
    SM --&amp;gt; NC
    OC --&amp;gt; NC
    CM --&amp;gt; CR&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-关键模块说明&#34;&gt;4.2 关键模块说明&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;模块&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;职责&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;技术要点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;ConnectionManager&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;管理所有活跃 Circuit 和 Peer 连接&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;连接池、复用、超时清理&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;P2PController&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;决定是否直连、打洞、还是走中继&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;ICE 候选排序、NAT 类型判断&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;SignalingClient&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;与信令服务器通信&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;UDP 心跳、请求/响应匹配、重传&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;OnionCore&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;分层加密/解密、报文打包&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;X25519、ChaCha20-Poly1305、HKDF&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;NetworkCore&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;UDP Socket、STUN/ICE、NAT 类型探测&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;DatagramChannel、线程模型、电池优化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;CircuitStore&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;本地缓存 Circuit 信息&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;SQLite / MMKV、加密存储&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;43-networkcore-线程模型&#34;&gt;4.3 NetworkCore 线程模型&lt;/h3&gt;
&lt;p&gt;UDP 网络层要避免在主线程做 IO，也避免一个线程处理所有事导致卡顿。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph 网络线程
        RT[ReceiveThread&amp;lt;br/&amp;gt;阻塞 recvFrom]
        RT --&amp;gt;|Raw Packet| DP[DecoderPipeline&amp;lt;br/&amp;gt;STUN / 洋葱 / 业务]
        DP --&amp;gt;|业务消息| MQ[MessageQueue]
        DP --&amp;gt;|信令响应| SR[SignalingResponseMap]
    end

    subgraph 发送线程
        SQ[SendQueue&amp;lt;br/&amp;gt;优先级队列]
        ST[SendThread&amp;lt;br/&amp;gt;定时 flush]
        SQ --&amp;gt; ST --&amp;gt;|sendTo| Socket
    end

    subgraph 业务线程
        BH[BusinessHandler]
        MQ --&amp;gt; BH
    end

    Socket --&amp;gt; RT&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;44-android-端-nat-类型探测&#34;&gt;4.4 Android 端 NAT 类型探测&lt;/h3&gt;
&lt;p&gt;客户端启动时，先用 STUN 服务器探测自己的 NAT 类型：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1. 向 STUN Server A 发 Binding Request，得到映射地址 M1。
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2. 向 STUN Server B 发 Binding Request，得到映射地址 M2。
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3. 比较 M1 和 M2：
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   - 相同 → 可能全锥型 / 限制型
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   - 不同 → 对称型 NAT（打洞困难）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4. 再测试其他 IP/Port 是否能把包送进本机，判断是限制型还是端口限制型。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[开始探测] --&amp;gt; B[STUN A 请求]
    B --&amp;gt; C[得到 M1]
    C --&amp;gt; D[STUN B 请求]
    D --&amp;gt; E[得到 M2]
    E --&amp;gt; F{M1 == M2?}
    F --&amp;gt;|是| G[全锥 / 限制 / 端口限制]
    F --&amp;gt;|否| H[对称型 NAT]
    G --&amp;gt; I[进一步测试过滤规则]
    H --&amp;gt; J[P2P 大概率失败&amp;lt;br/&amp;gt;优先准备 Relay]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;45-android-后台保活与省电&#34;&gt;4.5 Android 后台保活与省电&lt;/h3&gt;
&lt;p&gt;P2P 需要客户端能被找到，但 Android 对后台限制越来越严。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;方案&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;适用场景&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;注意事项&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;Foreground Service&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;需要长期在线&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;必须显示通知，用户可能反感&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;WorkManager 周期性心跳&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;非实时场景&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;最小间隔 15 分钟，无法保证即时&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;FCM / 厂商推送&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;唤醒 App 建立连接&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;国内厂商通道需要适配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;保活 socket + 心跳&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;与信令服务维持 NAT 映射&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;心跳间隔建议 15-30s， balancing 电量&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人避坑&lt;/strong&gt;：不要试图用「双进程守护」「1 像素 Activity」等旧保活手段，Android 高版本会被杀，且应用商店可能下架。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-通过-udp-信令网络实现-p2p-控制&#34;&gt;5. 通过 UDP 信令网络实现 P2P 控制&lt;/h2&gt;
&lt;h3 id=&#34;51-p2p-连接的三种结果&#34;&gt;5.1 P2P 连接的三种结果&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[Peer A 想连 Peer B] --&amp;gt; B[通过信令服务获取 B 的候选地址]
    B --&amp;gt; C{尝试直连}
    C --&amp;gt;|成功| D[UDP P2P 直连]
    C --&amp;gt;|失败| E{尝试 NAT 打洞}
    E --&amp;gt;|成功| D
    E --&amp;gt;|失败| F[通过 Relay 中继]
    D --&amp;gt; G[业务通信]
    F --&amp;gt; G&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;52-ice-like-候选地址优先级&#34;&gt;5.2 ICE-like 候选地址优先级&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[直连候选&amp;lt;br/&amp;gt;局域网 IP] --&amp;gt;|优先级最高| B[srflx 候选&amp;lt;br/&amp;gt;STUN 映射公网地址]
    B --&amp;gt;|次高| C[prflx 候选&amp;lt;br/&amp;gt;对端看到的地址]
    C --&amp;gt;|再次| D[relay 候选&amp;lt;br/&amp;gt;TURN/中继服务器]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;优先级规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;host 候选&lt;/strong&gt;：同一局域网直接连，延迟最低。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;server reflexive（srflx）&lt;/strong&gt;：STUN 映射后的公网地址，适合普通 NAT。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;peer reflexive（prflx）&lt;/strong&gt;：对端实际看到的地址，可能在打洞过程中发现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;relay 候选&lt;/strong&gt;：最后兜底，走服务器转发。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;53-nat-打洞详细流程&#34;&gt;5.3 NAT 打洞详细流程&lt;/h3&gt;
&lt;p&gt;假设 A 和 B 都在普通家用路由器后，NAT 类型为「端口限制锥型」。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as Peer A
    participant SA as STUN/Signaling Server
    participant B as Peer B

    A-&amp;gt;&amp;gt;SA: 注册：我的 srflx 是 1.1.1.1:10000
    B-&amp;gt;&amp;gt;SA: 注册：我的 srflx 是 2.2.2.2:20000

    A-&amp;gt;&amp;gt;SA: 请求连接 B
    SA--&amp;gt;&amp;gt;A: 返回 B 的地址 2.2.2.2:20000
    SA--&amp;gt;&amp;gt;B: 转发 A 的地址 1.1.1.1:10000

    A-&amp;gt;&amp;gt;B: 发送 UDP 打洞包（可能到达或被过滤）
    B-&amp;gt;&amp;gt;A: 发送 UDP 打洞包
    Note over A,B: 双方都先发包，让各自 NAT 建立映射

    A-&amp;gt;&amp;gt;B: 收到 B 的包，回确认
    B-&amp;gt;&amp;gt;A: 收到 A 的包，回确认
    Note over A,B: 打洞成功，后续直接通信&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人记忆点&lt;/strong&gt;：NAT 打洞的本质是&lt;strong&gt;让双方路由器先记录一条「允许对方包进来」的映射&lt;/strong&gt;。所以必须双方同时主动发一个包到对方公网地址。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;54-基于洋葱网络的-p2p-控制通道&#34;&gt;5.4 基于洋葱网络的 P2P 控制通道&lt;/h3&gt;
&lt;p&gt;除了直接 P2P，还可以把「P2P 控制信令」本身也套在洋葱网络里，增强隐私：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[Alice] --&amp;gt;|洋葱隧道| X[出口节点] --&amp;gt; S[信令服务器]
    B[Bob] --&amp;gt;|洋葱隧道| X&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;控制消息包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;请求连接、交换候选地址。&lt;/li&gt;
&lt;li&gt;协商加密密钥（用于 P2P 直连后的端到端加密）。&lt;/li&gt;
&lt;li&gt;心跳、断开通知。&lt;/li&gt;
&lt;li&gt;中继切换通知。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样即使信令服务器被监控，攻击者也只能看到「某个洋葱出口在发信令」，不知道 Alice 和 Bob 的真实身份。&lt;/p&gt;
&lt;h3 id=&#34;55-p2p-直连后的加密&#34;&gt;5.5 P2P 直连后的加密&lt;/h3&gt;
&lt;p&gt;P2P 直连本身不保证安全，还需要端到端加密：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as Peer A
    participant S as Signaling Server（洋葱保护下）
    participant B as Peer B

    A-&amp;gt;&amp;gt;S: 发送 X25519 公钥 PA
    S-&amp;gt;&amp;gt;B: 转发 PA
    B-&amp;gt;&amp;gt;S: 发送 X25519 公钥 PB
    S-&amp;gt;&amp;gt;A: 转发 PB

    A-&amp;gt;&amp;gt;A: ECDH 计算共享密钥 K
    B-&amp;gt;&amp;gt;B: ECDH 计算共享密钥 K

    A-&amp;gt;&amp;gt;B: 用 K 加密业务数据，UDP P2P 直连发送
    B-&amp;gt;&amp;gt;A: 用 K 加密业务数据，UDP P2P 直连发送&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人避坑&lt;/strong&gt;：洋葱网络的逐跳加密 ≠ P2P 端到端加密。如果数据离开出口节点后是明文，出口节点仍然能看到内容。真正保密需要在 P2P 直连后再做一次端到端加密。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;6-协议报文设计示例&#34;&gt;6. 协议报文设计示例&lt;/h2&gt;
&lt;h3 id=&#34;61-洋葱路由报文&#34;&gt;6.1 洋葱路由报文&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 0                   1                   2                   3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| Version       | Command       | TTL           | Reserved        |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                        Circuit ID                             |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                                                               |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+              Encrypted Onion Payload (variable)               +
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                                                               |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;Command 定义示例：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;值&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;命令&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x01&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;CREATE&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;建立 Circuit&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x02&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;CREATED&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;建立成功响应&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x03&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;RELAY&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;中继数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x04&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;DESTROY&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;销毁 Circuit&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x05&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;HEARTBEAT&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;保活心跳&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;62-信令报文&#34;&gt;6.2 信令报文&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 0                   1                   2                   3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| Magic (4B)    | Version (1B)  | Command (1B)  | Flags (2B)    |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                         Sequence Number                       |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                         Timestamp                             |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                                                               |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+                    Encrypted Payload                          +
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|                                                               |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;信令 Command 示例：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;值&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;命令&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x10&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;REGISTER&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;客户端注册&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x11&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;OFFER&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;发起连接请求&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x12&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;ANSWER&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;响应连接请求&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x13&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;CANDIDATE&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;交换候选地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x14&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;HEARTBEAT&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;保活&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;0x15&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;ERROR&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;错误通知&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新人避坑&lt;/strong&gt;：UDP 报文要加 Magic 数和版本号，防止收到错乱的广播/扫描包时解析出错。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-关键算法与数据结构&#34;&gt;7. 关键算法与数据结构&lt;/h2&gt;
&lt;h3 id=&#34;71-circuit-选择算法&#34;&gt;7.1 Circuit 选择算法&lt;/h3&gt;
&lt;p&gt;客户端从目录服务拿到节点列表后，选择一条路径：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;choose_path&lt;/span&gt;(nodes, desired_hops&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 1. 过滤在线、版本兼容、flags 符合要求的节点&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    valid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [n &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; nodes &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; n&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;is_online &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; n&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;version &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; PROTO_VERSION]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 2. 按负载和带宽评分，负载越低、带宽越高分越高&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scored &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [(n, score(n)) &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; valid]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scored&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sort(key&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;lambda&lt;/span&gt; x: x[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], reverse&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 3. 随机选择前 K 个中的节点，避免总是选同一批&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    candidates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; scored[:max(len(scored)&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, desired_hops &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# 4. 确保 Entry、Middle、Exit 来自不同运营商 / 网段，增加路径多样性&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    path &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; role &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;entry&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;middle&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;exit&amp;#39;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        chosen &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pick_diverse(path, candidates, role)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(chosen)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; path&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;72-消息可靠性保证&#34;&gt;7.2 消息可靠性保证&lt;/h3&gt;
&lt;p&gt;UDP 不保证可靠，应用层需要自己实现：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[发送消息] --&amp;gt; B[分配 Seq]
    B --&amp;gt; C[加入重传队列]
    C --&amp;gt; D[UDP 发送]
    D --&amp;gt; E{收到 ACK?}
    E --&amp;gt;|是| F[从队列移除]
    E --&amp;gt;|否| G{超时?}
    G --&amp;gt;|否| E
    G --&amp;gt;|是| H[重传 N 次]
    H --&amp;gt; I{达到最大重试?}
    I --&amp;gt;|否| C
    I --&amp;gt;|是| J[通知上层失败]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;重传策略建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始超时：200ms（RTT 估计）。&lt;/li&gt;
&lt;li&gt;退避：指数退避，最多 5 次。&lt;/li&gt;
&lt;li&gt;ACK 去重：用滑动窗口或位图记录已确认 Seq。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-连接状态机&#34;&gt;7.3 连接状态机&lt;/h3&gt;
&lt;p&gt;P2P 连接可以建模为状态机：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;stateDiagram-v2
    [*] --&amp;gt; Idle: 用户发起连接
    Idle --&amp;gt; Gathering: 收集 ICE 候选
    Gathering --&amp;gt; Connecting: 开始打洞/直连
    Connecting --&amp;gt; Connected: 收到对端包
    Connecting --&amp;gt; Relaying: 打洞失败，切中继
    Relaying --&amp;gt; Connected: 中继链路建立
    Connected --&amp;gt; Disconnected: 超时未收到心跳
    Disconnected --&amp;gt; [*]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;8-新人避坑技巧大全&#34;&gt;8. 新人避坑技巧大全&lt;/h2&gt;
&lt;h3 id=&#34;81-udp-层面的坑&#34;&gt;8.1 UDP 层面的坑&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;坑&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;现象&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;规避方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;UDP 报文丢失&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;部分消息收不到&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;应用层 ACK + 重传；关键信令走可靠通道&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;UDP 乱序&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;消息顺序错乱&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;按 Seq 排序或丢弃过期包；业务层幂等&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;UDP 分片&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;大报文丢失率陡增&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;控制报文大小 &amp;lt; 1200 字节；必要时分片重组&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;运营商 QoS&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;高峰丢包严重&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;使用常见端口（如 443/8080）或混淆成 QUIC/DNS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;防火墙拦截&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;某些网络 UDP 不通&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;准备 TCP 回退或 Relay 兜底&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;82-洋葱网络层面的坑&#34;&gt;8.2 洋葱网络层面的坑&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;坑&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;现象&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;规避方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Circuit 被重用&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;旧连接和新连接混用&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;每个 Circuit 带唯一 ID，过期销毁&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;时序攻击&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;入口和出口流量时间关联&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;引入填充（padding）和混合延迟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;恶意出口节点&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;出口监听或篡改明文&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;P2P 必须端到端加密，不依赖出口&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;节点列表被污染&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;连上恶意节点&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;目录服务签名 + 节点身份固定 + 信誉系统&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;密钥管理混乱&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;加密失败或性能差&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;一个 Circuit 一对密钥，用 HKDF 派生&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;83-android-层面的坑&#34;&gt;8.3 Android 层面的坑&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;坑&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;现象&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;规避方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;主线程网络&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;ANR / 卡顿&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;所有 UDP IO 放子线程或用协程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Doze 模式心跳中断&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;后台连接断开&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;使用 JobScheduler / WorkManager 补心跳&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;IPv4/IPv6 双栈&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;一端 v4 一端 v6 连不上&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;同时收集 v4/v6 候选地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;端口复用失败&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;code&gt;BindException&lt;/code&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;Socket 设置 &lt;code&gt;SO_REUSEADDR&lt;/code&gt; / &lt;code&gt;SO_REUSEPORT&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;电量消耗高&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;用户卸载&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;动态心跳间隔，网络空闲时延长&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;84-p2p--nat-层面的坑&#34;&gt;8.4 P2P / NAT 层面的坑&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;坑&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;现象&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;规避方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;对称型 NAT 打洞失败&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;两边都收不到包&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;提前探测 NAT 类型，准备 Relay&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;** hairpin NAT 不支持**&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;同一局域网却走公网&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;优先尝试 host 候选&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;NAT 映射超时&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;长时间没通信后连不上&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;定期发送心跳保活&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;双方不同时打洞&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;一方先发可能被防火墙拦&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;设计让 A、B 同时主动发包&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;信令与数据通道混淆&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;安全边界不清&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;信令用短期密钥，数据用长期会话密钥&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;85-安全与隐私层面的坑&#34;&gt;8.5 安全与隐私层面的坑&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;坑&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;现象&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;规避方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;只做了洋葱加密，没做端到端加密&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;出口节点可读明文&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;P2P 直连后再做一次 ECDH&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;时间戳不校验&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;重放攻击&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;每个报文带时间戳 + 滑动窗口&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;随机数可预测&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;Circuit ID 被猜中&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;用 SecureRandom / crypto.random&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;元数据泄露&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;包大小暴露内容类型&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;固定包长或随机填充&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;证书/公钥不校验&lt;/strong&gt;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;中间人攻击&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;目录服务签名 + 证书锁定&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-一个最小可运行流程串起来&#34;&gt;9. 一个最小可运行流程串起来&lt;/h2&gt;
&lt;p&gt;为了让新人有整体感，下面把一次「Alice 通过 UDP 洋葱网络控制 P2P 连接 Bob」的完整流程串起来。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as Android Alice
    participant D as Directory Server
    participant S as Signaling Server
    participant R as Relay Server
    participant B as Android Bob

    %% 阶段 1：准备节点
    A-&amp;gt;&amp;gt;D: HTTPS 获取节点列表
    D--&amp;gt;&amp;gt;A: 返回 Entry/Middle/Exit + 签名
    A-&amp;gt;&amp;gt;A: 校验签名，选择路径
    A-&amp;gt;&amp;gt;Entry: UDP CREATE Circuit
    Entry--&amp;gt;&amp;gt;A: CREATED
    A-&amp;gt;&amp;gt;Middle: 经 Entry 转发 CREATE
    Middle--&amp;gt;&amp;gt;A: CREATED
    A-&amp;gt;&amp;gt;Exit: 经 Entry→Middle 转发 CREATE
    Exit--&amp;gt;&amp;gt;A: CREATED

    %% 阶段 2：注册信令
    A-&amp;gt;&amp;gt;S: 经洋葱隧道 REGISTER Alice
    B-&amp;gt;&amp;gt;S: 经洋葱隧道 REGISTER Bob

    %% 阶段 3：发起 P2P
    A-&amp;gt;&amp;gt;S: OFFER（请求连接 Bob）
    S-&amp;gt;&amp;gt;B: 转发 OFFER + Alice 候选地址
    B-&amp;gt;&amp;gt;S: ANSWER + Bob 候选地址
    S-&amp;gt;&amp;gt;A: 转发 ANSWER

    %% 阶段 4：打洞
    A-&amp;gt;&amp;gt;B: UDP 打洞包
    B-&amp;gt;&amp;gt;A: UDP 打洞包
    A--&amp;gt;&amp;gt;B: 直连成功

    %% 阶段 5：业务通信
    A-&amp;gt;&amp;gt;B: 端到端加密业务数据
    B-&amp;gt;&amp;gt;A: 端到端加密业务数据

    %% 阶段 6：失败兜底
    alt 打洞失败
        A-&amp;gt;&amp;gt;S: 请求 Relay
        S--&amp;gt;&amp;gt;A: 分配 Relay 地址
        S--&amp;gt;&amp;gt;B: 通知走 Relay
        A-&amp;gt;&amp;gt;R: 经洋葱→Relay→Bob
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;10-总结一张图记住全文&#34;&gt;10. 总结：一张图记住全文&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    A[UDP 洋葱网络 + P2P] --&amp;gt; B[目录服务&amp;lt;br/&amp;gt;给节点]
    A --&amp;gt; C[信令服务&amp;lt;br/&amp;gt;撮合 Peer]
    A --&amp;gt; D[中继服务&amp;lt;br/&amp;gt;兜底转发]
    A --&amp;gt; E[Android 客户端&amp;lt;br/&amp;gt;洋葱 + NAT + P2P]
    E --&amp;gt; F[OnionCore&amp;lt;br/&amp;gt;逐跳加密]
    E --&amp;gt; G[NetworkCore&amp;lt;br/&amp;gt;UDP / STUN / ICE]
    E --&amp;gt; H[P2PController&amp;lt;br/&amp;gt;直连 / 打洞 / 中继]
    F --&amp;gt; I[隐私保护]
    G --&amp;gt; J[NAT 穿透]
    H --&amp;gt; K[去中心化通信]
    A --&amp;gt; L[避坑：可靠传输 / 端到端加密 / 后台保活]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;核心要点回顾：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;洋葱网络用 UDP 是为了轻量、NAT 友好、适合信令&lt;/strong&gt;；但可靠性和顺序要自己补。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务器分三类&lt;/strong&gt;：目录服务（给节点）、信令服务（撮合 P2P）、中继服务（兜底转发）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android 客户端分层&lt;/strong&gt;：业务层、连接管理层、核心引擎层、网络层，不要混在一处。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;P2P 控制靠 UDP 信令交换地址&lt;/strong&gt;，再用 ICE 候选优先级尝试直连、打洞、中继。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隐私不等于安全&lt;/strong&gt;：洋葱网络保护路径隐私，P2P 业务数据还要端到端加密。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新人最容易踩的坑&lt;/strong&gt;：UDP 当 TCP 用、忽略 NAT 类型、不做端到端加密、Android 主线程 IO。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考与延伸阅读&#34;&gt;参考与延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tor Project: &lt;a href=&#34;https://www.torproject.org/&#34;&gt;https://www.torproject.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ICE / STUN / TURN RFCs: RFC 5245, RFC 5389, RFC 5766&lt;/li&gt;
&lt;li&gt;WebRTC NAT Traversal 实践&lt;/li&gt;
&lt;li&gt;Android &lt;code&gt;DatagramSocket&lt;/code&gt;、&lt;code&gt;WorkManager&lt;/code&gt;、&lt;code&gt;Foreground Service&lt;/code&gt; 官方文档&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你正在动手实现，建议先做一个「最小闭环」：一个目录服务 + 一个中继节点 + 两个 Android 客户端，先跑通「Alice 经洋葱网络发一条消息给 Bob」，再逐步加入 P2P 直连和 NAT 打洞。先跑通，再优化。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>
