<?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/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 17 Aug 2020 09:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>编译原理学习总结：新人也能看懂的编译器流水线与避坑指南</title>
      <link>https://ftmao.com/blog/posts/compiler-principles-study-notes/</link>
      <pubDate>Mon, 17 Aug 2020 09:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/compiler-principles-study-notes/</guid>
      <description>&lt;h1 id=&#34;编译原理学习总结新人也能看懂的编译器流水线与避坑指南&#34;&gt;编译原理学习总结：新人也能看懂的编译器流水线与避坑指南&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考教材：王生原、董渊、张素琴、吕映芝、蒋维杜《编译原理（第 3 版）》，清华大学出版社。
目标读者：刚开编译原理课、被「NFA/DFA/LL(1)/LR」一堆缩写吓到的同学；或者工作中想手写个 DSL、规则引擎、配置解析器，发现需要补编译基础的工程师。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;编译原理这门课的劝退点不在「难」，而在&lt;strong&gt;概念链条太长&lt;/strong&gt;：正则表达式→NFA→DFA→最小化，文法→FIRST→FOLLOW→分析表，每一环都建立在前一环上，掉队一步后面全懵。这篇总结帮你做三件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;建立主干认知：编译器就是一条「翻译流水线」，六个阶段各干一件事；&lt;/li&gt;
&lt;li&gt;给出最小可运行的实现：词法分析器、递归下降 Parser、一个 60 行的计算器；&lt;/li&gt;
&lt;li&gt;把新人必踩的坑（左递归、二义性、优先级结合性、FIRST/FOLLOW 算错）提前标出来。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一先建立心智模型编译器--翻译流水线&#34;&gt;一、先建立心智模型：编译器 = 翻译流水线&lt;/h2&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 LR
    SRC[&amp;#34;源程序&amp;lt;br/&amp;gt;a = b + 2 * c&amp;#34;] --&amp;gt; LEX[&amp;#34;词法分析&amp;lt;br/&amp;gt;切成单词&amp;#34;]
    LEX --&amp;gt; SYN[&amp;#34;语法分析&amp;lt;br/&amp;gt;搭出句子结构树&amp;#34;]
    SYN --&amp;gt; SEM[&amp;#34;语义分析&amp;lt;br/&amp;gt;检查含义是否合理&amp;#34;]
    SEM --&amp;gt; ICG[&amp;#34;中间代码生成&amp;lt;br/&amp;gt;三地址码&amp;#34;]
    ICG --&amp;gt; OPT[&amp;#34;代码优化&amp;#34;]
    OPT --&amp;gt; CG[&amp;#34;目标代码生成&amp;lt;br/&amp;gt;汇编/机器码&amp;#34;]

    SYM([&amp;#34;符号表&amp;lt;br/&amp;gt;记录每个名字是什么&amp;#34;]) -.贯穿.- LEX
    SYM -.贯穿.- SYN
    SYM -.贯穿.- SEM
    ERR([&amp;#34;错误处理&amp;lt;br/&amp;gt;报错并恢复继续&amp;#34;]) -.贯穿.- LEX
    ERR -.贯穿.- SYN
    ERR -.贯穿.- SEM&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;记三个要点，后面全部内容都挂在这上面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;符号表和错误处理不是独立阶段，而是贯穿全程的两条辅助线&lt;/strong&gt;。符号表回答「这个名字是什么（类型、作用域、地址）」；错误处理保证发现一个错不崩溃，继续分析把后面的错也找出来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前三个阶段是「理解原文」（分析/前端），后三个阶段是「重新表达」（综合/后端）&lt;/strong&gt;。中间代码是分界线——这也是为什么 GCC/LLVM 能「N 种语言 × M 种CPU」：每种语言写个前端，每种 CPU 写个后端，中间用统一的 IR 对接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个阶段的输出是下一个阶段的输入&lt;/strong&gt;，所以学的时候务必搞清每个阶段的「输入是什么、输出是什么」，概念就不容易混。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;日常最常见的对照例子：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;阶段&lt;/th&gt;
					&lt;th&gt;干的事&lt;/th&gt;
					&lt;th&gt;日常类比&lt;/th&gt;
					&lt;th&gt;典型报错&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;词法分析&lt;/td&gt;
					&lt;td&gt;字符流 → Token 流&lt;/td&gt;
					&lt;td&gt;认出单词&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;invalid character&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;语法分析&lt;/td&gt;
					&lt;td&gt;Token 流 → 语法树&lt;/td&gt;
					&lt;td&gt;分析句子结构&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;unexpected token&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;语义分析&lt;/td&gt;
					&lt;td&gt;检查类型、声明&lt;/td&gt;
					&lt;td&gt;理解句子含义&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;undefined variable&lt;/code&gt; / 类型不匹配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;中间代码&lt;/td&gt;
					&lt;td&gt;语法树 → 三地址码&lt;/td&gt;
					&lt;td&gt;列翻译提纲&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;优化&lt;/td&gt;
					&lt;td&gt;等价变换让代码更快&lt;/td&gt;
					&lt;td&gt;润色&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;目标代码&lt;/td&gt;
					&lt;td&gt;IR → 汇编/机器码&lt;/td&gt;
					&lt;td&gt;正式成文&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二词法分析把字符流切成单词&#34;&gt;二、词法分析：把字符流切成单词&lt;/h2&gt;
&lt;h3 id=&#34;21-任务与产出&#34;&gt;2.1 任务与产出&lt;/h3&gt;
&lt;p&gt;输入 &lt;code&gt;a = b + 2 * c&lt;/code&gt;，输出 Token 序列：&lt;code&gt;ID(a) ASSIGN PLUS...&lt;/code&gt;。每个 Token 带「种别 + 属性值」。&lt;/p&gt;
&lt;h3 id=&#34;22-理论主线正则表达式--nfa--dfa--最小化&#34;&gt;2.2 理论主线：正则表达式 → NFA → DFA → 最小化&lt;/h3&gt;
&lt;p&gt;这是教材第 3 章的核心链条，也是新人最容易断掉的地方。一张图记住四个概念的关系：&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
    RE[&amp;#34;正则表达式&amp;lt;br/&amp;gt;letter(letter|digit)*&amp;#34;] --&amp;gt;|Thompson 构造| NFA[&amp;#34;NFA&amp;lt;br/&amp;gt;一个字符可走多条路&amp;#34;]
    NFA --&amp;gt;|子集构造法| DFA[&amp;#34;DFA&amp;lt;br/&amp;gt;每个状态+字符&amp;lt;br/&amp;gt;只有唯一去处&amp;#34;]
    DFA --&amp;gt;|状态划分| MINDFA[&amp;#34;最小化 DFA&amp;lt;br/&amp;gt;合并等价状态&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个构造算法各自一句话记住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Thompson 构造&lt;/strong&gt;：把正则的每种运算（连接、&lt;code&gt;|&lt;/code&gt;、&lt;code&gt;*&lt;/code&gt;）各对应一个固定的 NFA 拼图，像搭积木一样拼出整图。因为允许 ε 边（不读字符就能跳的边），所以拼起来毫无难度——这就是引入 NFA 的原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子集构造法&lt;/strong&gt;：DFA 的每个状态 = NFA 的「一组状态的集合」（从起点出发、经过任意条 ε 边能到达的所有状态，即 ε-闭包）。NFA 的不确定性被「打包进集合」消掉了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最小化&lt;/strong&gt;：不断把「行为一样」（对每个字符都跳到同一组）的状态合并。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：ε-闭包是子集构造里最容易漏算的一步——每读一个字符到达新状态后，&lt;strong&gt;必须再沿 ε 边补一轮闭包&lt;/strong&gt;，漏了就会丢边，识别结果出错。&lt;/p&gt;
&lt;h3 id=&#34;23-例识别标识符与整数的-dfa&#34;&gt;2.3 例：识别标识符与整数的 DFA&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;stateDiagram-v2
    [*] --&amp;gt; S0
    S0 --&amp;gt; S1 : 字母
    S1 --&amp;gt; S1 : 字母或数字
    S0 --&amp;gt; S2 : 数字
    S2 --&amp;gt; S2 : 数字
    S1 --&amp;gt; [*] : 其他字符，接受 ID
    S2 --&amp;gt; [*] : 其他字符，接受 NUM&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;24-手写实现实践中没人先画-nfa&#34;&gt;2.4 手写实现：实践中没人先画 NFA&lt;/h3&gt;
&lt;p&gt;工程上写词法分析器，直接用「&lt;strong&gt;最长匹配 + 逐个分派&lt;/strong&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:#f92672&#34;&gt;import&lt;/span&gt; re
&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;TOKEN_SPEC &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:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;,   &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\d+&amp;#34;&lt;/span&gt;),            &lt;span style=&#34;color:#75715e&#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:#e6db74&#34;&gt;&amp;#34;ID&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[A-Za-z_]\w*&amp;#34;&lt;/span&gt;),   &lt;span style=&#34;color:#75715e&#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:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,    &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[+\-*/=]&amp;#34;&lt;/span&gt;),       &lt;span style=&#34;color:#75715e&#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:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&lt;/span&gt;,  &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[ \t]+&amp;#34;&lt;/span&gt;),         &lt;span style=&#34;color:#75715e&#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:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&lt;/span&gt;,   &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;),              &lt;span style=&#34;color:#75715e&#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;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;tokenize&lt;/span&gt;(code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(?P&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;v&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;)&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; k, v &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; TOKEN_SPEC)
&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; m &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;finditer(pattern, code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        kind, val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup, m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group()
&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;if&lt;/span&gt; kind &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&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;continue&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;if&lt;/span&gt; kind &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&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;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&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;val&lt;span style=&#34;color:#e6db74&#34;&gt;!r}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)   &lt;span style=&#34;color:#75715e&#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;yield&lt;/span&gt; (kind, val)
&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;print(list(tokenize(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a = b + 2 * c&amp;#34;&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;# [(&amp;#39;ID&amp;#39;,&amp;#39;a&amp;#39;), (&amp;#39;OP&amp;#39;,&amp;#39;=&amp;#39;), (&amp;#39;ID&amp;#39;,&amp;#39;b&amp;#39;), (&amp;#39;OP&amp;#39;,&amp;#39;+&amp;#39;), (&amp;#39;NUM&amp;#39;,&amp;#39;2&amp;#39;), (&amp;#39;OP&amp;#39;,&amp;#39;*&amp;#39;), (&amp;#39;ID&amp;#39;,&amp;#39;c&amp;#39;)]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：正则的排列顺序就是优先级（&lt;code&gt;=&lt;/code&gt; 要排在 &lt;code&gt;==&lt;/code&gt; 后面之类），以及永远留一个兜底规则报「非法字符」，否则遇到乱字符直接静默跳过，后面语法分析报错位置会莫名其妙。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三语法分析把单词流搭成结构树&#34;&gt;三、语法分析：把单词流搭成结构树&lt;/h2&gt;
&lt;h3 id=&#34;31-上下文无关文法cfg与推导&#34;&gt;3.1 上下文无关文法（CFG）与推导&lt;/h3&gt;
&lt;p&gt;语法规则用 CFG 描述，例如表达式文法：&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;E → E + T | T
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T → T * F | F
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;F → ( E ) | id | num&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;2 + 3 * 4&lt;/code&gt; 的语法树（注意 &lt;code&gt;*&lt;/code&gt; 比 &lt;code&gt;+&lt;/code&gt; 离根更远——&lt;strong&gt;离根越远越先算，优先级就藏在这里&lt;/strong&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 TD
    E1[&amp;#34;E&amp;#34;] --&amp;gt; E2[&amp;#34;E&amp;#34;] &amp;amp; PLUS[&amp;#34;+&amp;#34;] &amp;amp; T1[&amp;#34;T&amp;#34;]
    E2 --&amp;gt; T2[&amp;#34;T&amp;#34;] --&amp;gt; F1[&amp;#34;F&amp;#34;] --&amp;gt; N2[&amp;#34;num 2&amp;#34;]
    T1 --&amp;gt; T3[&amp;#34;T&amp;#34;] &amp;amp; MUL[&amp;#34;*&amp;#34;] &amp;amp; F2[&amp;#34;F&amp;#34;]
    T3 --&amp;gt; F3[&amp;#34;F&amp;#34;] --&amp;gt; N3[&amp;#34;num 3&amp;#34;]
    F2 --&amp;gt; N4[&amp;#34;num 4&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;32-两大流派自顶向下-vs-自底向上&#34;&gt;3.2 两大流派：自顶向下 vs 自底向上&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 TD[&amp;#34;自顶向下（LL 家族）&amp;#34;]
        direction LR
        A1[&amp;#34;从根开始&amp;lt;br/&amp;gt;预测该展开哪条规则&amp;#34;] --&amp;gt; A2[&amp;#34;代表：递归下降&amp;lt;br/&amp;gt;LL(1) 表驱动&amp;#34;]
        A2 --&amp;gt; A3[&amp;#34;优点：好写好懂&amp;lt;br/&amp;gt;缺点：怕左递归/回溯&amp;#34;]
    end
    subgraph BU[&amp;#34;自底向上（LR 家族）&amp;#34;]
        direction LR
        B1[&amp;#34;从叶子开始&amp;lt;br/&amp;gt;移进-归约&amp;#34;] --&amp;gt; B2[&amp;#34;代表：LR(0)/SLR/&amp;lt;br/&amp;gt;LALR(1)/LR(1)&amp;#34;]
        B2 --&amp;gt; B3[&amp;#34;优点：能力强&amp;lt;br/&amp;gt;缺点：表难手写&amp;lt;br/&amp;gt;用 Yacc/Bison 生成&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;手写 Parser 用递归下降&lt;/strong&gt;（每个非终结符一个函数），工具生成用 &lt;strong&gt;Yacc/Bison（LALR）&lt;/strong&gt;。工业界主流语言（GCC 的 C++、Go、Rust 前端）大量采用手写递归下降——错误信息友好、调试容易。&lt;/li&gt;
&lt;li&gt;LR 家族记一个不等式：&lt;strong&gt;LR(1) ⊃ LALR(1) ⊃ SLR ⊃ LR(0)&lt;/strong&gt;，能力依次减弱、表依次变小。Yacc 生成的是 LALR(1)——能力和表大小的折中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;33-ll1-的三道手续左递归左公因子firstfollow&#34;&gt;3.3 LL(1) 的三道手续：左递归、左公因子、FIRST/FOLLOW&lt;/h3&gt;
&lt;p&gt;把文法改造成 LL(1) 可用，是教材第 4 章的主线，也是作业/考试重灾区：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坑 1：左递归会让递归下降死循环。&lt;/strong&gt; &lt;code&gt;E → E + T&lt;/code&gt; 翻译成函数就是「解析 E 先调自己」，无限递归。&lt;strong&gt;必须改写成右递归/迭代形式&lt;/strong&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-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;E  → T E&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;E&amp;#39; → + T E&amp;#39; | ε      （原来是左递归 E → E + T | T）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T  → F T&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T&amp;#39; → * F T&amp;#39; | ε
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;F  → ( E ) | id | num&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;坑 2：左公因子导致「选哪条规则」看不准。&lt;/strong&gt; &lt;code&gt;S → if E then S | if E then S else S&lt;/code&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-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;S  → if E then S S&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;S&amp;#39; → else S | ε&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;坑 3：FIRST/FOLLOW 算错，分析表全错。&lt;/strong&gt; 记住用途就不容易错：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FIRST(A)：A 推出的串&lt;strong&gt;开头&lt;/strong&gt;可能是什么终结符——决定「看到某个 token 该选哪条产生式」；&lt;/li&gt;
&lt;li&gt;FOLLOW(A)：A &lt;strong&gt;后面&lt;/strong&gt;可能跟什么终结符——只在「考虑是否选 ε 产生式」时用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;算 FOLLOW 的三条规则（教材原文浓缩）：&lt;code&gt;$&lt;/code&gt; 进 FOLLOW(S)；&lt;code&gt;A → αBβ&lt;/code&gt; 则 FIRST(β)−ε 进 FOLLOW(B)；&lt;code&gt;A → αB&lt;/code&gt; 或 β 可空时 FOLLOW(A) 进 FOLLOW(B)。&lt;strong&gt;最常犯的错就是漏掉第三条&lt;/strong&gt;，考试拿分点也在这里。&lt;/p&gt;
&lt;h3 id=&#34;34-手写实现递归下降表达式解析器&#34;&gt;3.4 手写实现：递归下降表达式解析器&lt;/h3&gt;
&lt;p&gt;改造后的文法直接翻译成人话：&lt;strong&gt;每个非终结符一个函数，看到什么 token 走哪条分支&lt;/strong&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;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Parser&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;__init__&lt;/span&gt;(self, tokens):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokens &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(tokens)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&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:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;peek&lt;/span&gt;(self):
&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; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokens[self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos] &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; len(self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokens) &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;None&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:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;eat&lt;/span&gt;(self, kind):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        k, v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek()
&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;if&lt;/span&gt; k &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; kind:
&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;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&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;kind&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;，实际 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;v&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)  &lt;span style=&#34;color:#75715e&#34;&gt;# 语法错误在这里报&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&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; v
&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;# E → T E&amp;#39;&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_E&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_T()
&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; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); val &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_T()   &lt;span style=&#34;color:#75715e&#34;&gt;# E&amp;#39; → + T E&amp;#39;&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; val
&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;# T → F T&amp;#39;&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_T&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_F()
&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; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); val &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_F()   &lt;span style=&#34;color:#75715e&#34;&gt;# T&amp;#39; → * F T&amp;#39;&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; val
&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;# F → num | ( E )&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_F&lt;/span&gt;(self):
&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;if&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;peek()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&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; int(self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LP&amp;#34;&lt;/span&gt;); val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_E(); self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RP&amp;#34;&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; val&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;三个层次各管一件事：&lt;strong&gt;E 管加减、T 管乘除、F 管括号与原子&lt;/strong&gt;——优先级靠「调用层级」天然保证，结合性靠 &lt;code&gt;while&lt;/code&gt; 循环方向保证（左结合）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四语义分析与中间代码&#34;&gt;四、语义分析与中间代码&lt;/h2&gt;
&lt;h3 id=&#34;41-语义分析语法对了意思对吗&#34;&gt;4.1 语义分析：语法对了，意思对吗&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;int x = &amp;quot;abc&amp;quot;;&lt;/code&gt; 语法完全合法，但语义不通。语义分析干三件事：&lt;/p&gt;
&lt;ol&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;理论上用**属性文法/语法制导翻译（SDD/SDT）**描述：给语法树节点挂「综合属性」（从下往上传，如类型）和「继承属性」（从上往下传，如声明的类型传给变量表项）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：语义错误往往是「前面埋雷后面炸」——声明处没检查，使用处才报。写编译器时把检查尽量前移到「信息刚产生的地方」。&lt;/p&gt;
&lt;h3 id=&#34;42-中间代码三地址码&#34;&gt;4.2 中间代码：三地址码&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;a = b + 2 * c&lt;/code&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-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t1 = 2 * c
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t2 = b + t1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a  = t2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;作用：&lt;strong&gt;把前端和后端解耦&lt;/strong&gt;。LLVM IR、Java 字节码都是这个思想——前端不管什么 CPU，后端不管什么语言。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五优化与目标代码&#34;&gt;五、优化与目标代码&lt;/h2&gt;
&lt;h3 id=&#34;51-优化等价变换不改结果改速度&#34;&gt;5.1 优化：等价变换，不改结果改速度&lt;/h3&gt;
&lt;p&gt;新人只需记住四类常见优化，看到汇编里的「怪异写法」能认出是哪种：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;优化&lt;/th&gt;
					&lt;th&gt;例子&lt;/th&gt;
					&lt;th&gt;效果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;常量折叠/传播&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;2*3&lt;/code&gt; → &lt;code&gt;6&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;编译期算掉&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;公共子表达式消除&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;a+b&lt;/code&gt; 算两次 → 算一次存临时量&lt;/td&gt;
					&lt;td&gt;少算&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;死代码消除&lt;/td&gt;
					&lt;td&gt;删掉结果没人用的语句&lt;/td&gt;
					&lt;td&gt;代码变小&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;循环优化&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;i*4&lt;/code&gt; → 强度削弱为加法；循环不变量外提&lt;/td&gt;
					&lt;td&gt;热点提速&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;避坑&lt;/strong&gt;：优化的铁律是「&lt;strong&gt;语义等价&lt;/strong&gt;」。浮点运算&lt;strong&gt;不满足结合律&lt;/strong&gt;，&lt;code&gt;(a+b)+c&lt;/code&gt; 和 &lt;code&gt;a+(b+c)&lt;/code&gt; 结果可能不同——所以激进优化可能改变浮点结果（GCC 的 &lt;code&gt;-ffast-math&lt;/code&gt; 就会），数值程序测试对不上时先查这个。&lt;/p&gt;
&lt;h3 id=&#34;52-目标代码核心是寄存器分配&#34;&gt;5.2 目标代码：核心是寄存器分配&lt;/h3&gt;
&lt;p&gt;指令选择、指令调度了解一下即可，真正值得记住的是&lt;strong&gt;寄存器分配&lt;/strong&gt;：寄存器快但数量有限，谁住寄存器谁住内存？经典解法是&lt;strong&gt;图着色&lt;/strong&gt;——变量当顶点、同时活跃的变量连边，寄存器当颜色，给图染色。这个「把分配问题建模成图着色」的思想，比算法细节本身更有迁移价值。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六动手实战60-行写一个四则运算计算器&#34;&gt;六、动手实战：60 行写一个四则运算计算器&lt;/h2&gt;
&lt;p&gt;把第二、三章的东西拼起来：词法用正则，语法用递归下降，语义直接求值（跳过中间代码），支持 &lt;code&gt;+ - * / ( )&lt;/code&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:#f92672&#34;&gt;import&lt;/span&gt; re
&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tokenize&lt;/span&gt;(code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    spec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\d+&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;[+\-*/]&amp;#34;&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:#e6db74&#34;&gt;&amp;#34;LP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\(&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\)&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\s+&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(?P&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;v&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;)&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; k, v &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; spec)
&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; m &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;finditer(pattern, code):
&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;if&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;SKIP&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&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;if&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;BAD&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&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;m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group()&lt;span style=&#34;color:#e6db74&#34;&gt;!r}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&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;yield&lt;/span&gt; (m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lastgroup, m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group())
&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calc&lt;/span&gt;(code):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ts, pos &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; list(tokenize(code)), [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]          &lt;span style=&#34;color:#75715e&#34;&gt;# pos 用列表包一层方便改&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;peek&lt;/span&gt;(): &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ts[pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]] &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; len(ts) &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;None&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;eat&lt;/span&gt;(k):
&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;if&lt;/span&gt; peek()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; k: &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;f&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;k&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;, 得到 &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;peek()&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ts[pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;][&lt;span style=&#34;color:#ae81ff&#34;&gt;1&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;expr&lt;/span&gt;():                                  &lt;span style=&#34;color:#75715e&#34;&gt;# expr → term ((+|-) term)*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; term()
&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; peek() &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; ((&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-&amp;#34;&lt;/span&gt;)):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            op &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; term()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; r &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; op &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;+&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; r
&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; v
&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;term&lt;/span&gt;():                                  &lt;span style=&#34;color:#75715e&#34;&gt;# term → factor ((*|/) factor)*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; factor()
&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; peek() &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; ((&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;,&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            op &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OP&amp;#34;&lt;/span&gt;); r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; factor()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; r &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; op &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; r
&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; v
&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;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;factor&lt;/span&gt;():                                &lt;span style=&#34;color:#75715e&#34;&gt;# factor → NUM | ( expr )&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;if&lt;/span&gt; peek()[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; int(eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;NUM&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;LP&amp;#34;&lt;/span&gt;); v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; expr(); eat(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;RP&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; expr()
&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;if&lt;/span&gt; pos[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; len(ts): &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SyntaxError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;表达式未结束，多了内容&amp;#34;&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; result
&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;print(calc(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2 + 3 * (4 - 1)&amp;#34;&lt;/span&gt;))   &lt;span style=&#34;color:#75715e&#34;&gt;# 11&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;写完这个，你就走完了一个迷你编译器的完整前端：&lt;strong&gt;字符 → Token → 语法树（隐含在调用栈里）→ 结果&lt;/strong&gt;。想再进一步，把 &lt;code&gt;expr/term/factor&lt;/code&gt; 里的「直接求值」换成「返回 AST 节点」，就有了语法树；遍历 AST 输出三地址码，就有了中间代码层。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七典型问题速查新人必踩的坑&#34;&gt;七、典型问题速查（新人必踩的坑）&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;根因&lt;/th&gt;
					&lt;th&gt;解法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;递归下降死循环/栈溢出&lt;/td&gt;
					&lt;td&gt;文法有左递归 &lt;code&gt;E → E + T&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;改写为右递归形式（&lt;code&gt;E → T E&#39;&lt;/code&gt;）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;同一个输入两种语法树&lt;/td&gt;
					&lt;td&gt;文法二义性（如 &lt;code&gt;E → E+E&lt;/code&gt;）&lt;/td&gt;
					&lt;td&gt;分层文法（E/T/F）或显式声明优先级结合性&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;if-else 挂错位置（dangling else）&lt;/td&gt;
					&lt;td&gt;二义性经典案例&lt;/td&gt;
					&lt;td&gt;规定 else 匹配最近的未配对 then（Yacc 默认移进优先即可）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;1+2*3&lt;/code&gt; 算成 9&lt;/td&gt;
					&lt;td&gt;优先级没分层&lt;/td&gt;
					&lt;td&gt;运算符按优先级各分一层非终结符&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;8/4/2&lt;/code&gt; 算成 4&lt;/td&gt;
					&lt;td&gt;结合性错了（写成右结合）&lt;/td&gt;
					&lt;td&gt;左结合用 &lt;code&gt;while&lt;/code&gt; 循环收集，右结合（如 &lt;code&gt;^&lt;/code&gt;、赋值）用递归&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;LL(1) 分析表一格填了两条&lt;/td&gt;
					&lt;td&gt;FIRST 集相交，或 ε 规则的 FIRST∩FOLLOW 非空&lt;/td&gt;
					&lt;td&gt;提左公因子；重算 FIRST/FOLLOW&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;遇到一个错就整个编译中断&lt;/td&gt;
					&lt;td&gt;没做错误恢复&lt;/td&gt;
					&lt;td&gt;恐慌模式：跳过 token 直到同步符号（&lt;code&gt;;&lt;/code&gt;、&lt;code&gt;}&lt;/code&gt;）再继续&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;子集构造结果丢边&lt;/td&gt;
					&lt;td&gt;读完字符后忘了补 ε-闭包&lt;/td&gt;
					&lt;td&gt;每步「先 move 再 closure」写成固定套路&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;优化后浮点结果对不上&lt;/td&gt;
					&lt;td&gt;浮点不满足结合律，优化重排了运算&lt;/td&gt;
					&lt;td&gt;关掉激进浮点优化，或接受精度差异&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八学习路线建议&#34;&gt;八、学习路线建议&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第一周&lt;/strong&gt;：看懂六阶段流水线图，用正则手写一个词法分析器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二周&lt;/strong&gt;：攻形式语言——正则→NFA→DFA→最小化，动手做一遍子集构造的纸面推导（这步走一遍比看十遍有用）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第三周&lt;/strong&gt;：递归下降写表达式 Parser，亲手把左递归文法改写成 LL(1)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第四周&lt;/strong&gt;：跑通本文的计算器，然后加变量赋值（需要符号表）——这一步会让你真正理解符号表为什么贯穿始终。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进阶&lt;/strong&gt;：读 LR 分析表构造（SLR→LALR），用 Yacc/Bison 或 ANTLR 生成一个 Parser 对比手写的差别；再往后可以啃「龙书」的优化章节。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后说一句教材反复强调的心法：&lt;strong&gt;编译器的每个阶段都是「输入一种表示，输出另一种表示」&lt;/strong&gt;。学不懂某个概念时，先问两个问题——它输入什么、输出什么？答案往往就是理解本身。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;参考资料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;王生原、董渊、张素琴、吕映芝、蒋维杜《编译原理（第 3 版）》，清华大学出版社&lt;/li&gt;
&lt;li&gt;Aho 等《编译原理》（龙书, Compilers: Principles, Techniques, and Tools）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.antlr.org/&#34;&gt;ANTLR 官方文档&lt;/a&gt;（工具生成 Parser 的现代选择）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://llvm.org/docs/&#34;&gt;LLVM 官方文档&lt;/a&gt;（中间代码与后端思想的工业界范本）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&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>
