<?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/shelves/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Fri, 08 Dec 2023 10:40:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/shelves/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>《计算机程序设计艺术》笔记：Knuth 用半个世纪给算法写的一部《史记》</title>
      <link>https://ftmao.com/blog/bookstore/taocp-book-notes/</link>
      <pubDate>Fri, 08 Dec 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/taocp-book-notes/</guid>
      <description>&lt;h1 id=&#34;计算机程序设计艺术笔记knuth-用半个世纪给算法写的一部史记&#34;&gt;《计算机程序设计艺术》笔记：Knuth 用半个世纪给算法写的一部《史记》&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：被&amp;quot;要不要读 TAOCP&amp;quot;这个问题折磨过的工程师；也想给正在啃这套书的人一张地图和一份劝退/劝进并存的导读。
参考书籍：Donald E. Knuth《计算机程序设计艺术》（The Art of Computer Programming）：卷 1《基本算法》（1968）、卷 2《半数值算法》（1969）、卷 3《排序与查找》（1973）、卷 4A《组合算法（一）》（2011），以及陆续以分册（fascicle）形式发布的 4B、4C。
阅读目标：不假装自己通读了这套书——说实话，没人通读过——而是把它读成一张&amp;quot;算法领域的地形图&amp;quot;：每卷解决什么问题、Knuth 的方法论是什么、哪些内容今天仍是工程师的硬通货、正确的打开姿势是什么。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一定位这不是教材是算法学科的史记&#34;&gt;一、定位：这不是教材，是算法学科的《史记》&lt;/h2&gt;
&lt;p&gt;先破一个流传最广的误读：TAOCP 不是&amp;quot;编程教程&amp;quot;，甚至不是&amp;quot;算法教材&amp;quot;。它是 Knuth 从 1962 年开始写的一部&lt;strong&gt;算法领域的编年史加分析法典&lt;/strong&gt;——每一种重要算法都给出精确的描述、完整的正确性论证、严格的运行时间分析，以及详尽到令人发指的文献考证。Bill Gates 那句著名的话是对这套书最准确的注脚：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;如果你觉得自己是个足够优秀的程序员，就去读 Knuth 的《计算机程序设计艺术》……如果你能读完它，请一定把简历发给我。&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这句话的重点不在&amp;quot;读完&amp;quot;，在&amp;quot;读完的人值得收简历&amp;quot;——它说的是这套书的密度。Knuth 自己的写作史也配得上这份密度：1962 年动笔时他以为自己写的是一本&amp;quot;编译器构造&amp;quot;的书，越写发现底料越多，最终膨胀成七卷规划；卷 1 到卷 3 在 1968-1973 年间出齐，然后卷 4 一写就是近四十年，2011 年才出了 4A，至今仍在以分册形式连载。顺带一个工程师会心一笑的插曲：1977 年卷 2 出第二版时，出版商改用照相排版，Knuth 对数学公式的排版质量忍无可忍，于是&lt;strong&gt;自己花了近十年写了 TeX 排版系统&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
    TAOCP[&amp;#34;TAOCP 七卷规划&amp;lt;br/&amp;gt;1962 年动笔&amp;#34;] --&amp;gt; V1[&amp;#34;卷 1 基本算法 1968&amp;lt;br/&amp;gt;数学基础 + 数据结构&amp;#34;]
    TAOCP --&amp;gt; V2[&amp;#34;卷 2 半数值算法 1969&amp;lt;br/&amp;gt;随机数 + 高精度算术&amp;#34;]
    TAOCP --&amp;gt; V3[&amp;#34;卷 3 排序与查找 1973&amp;lt;br/&amp;gt;内排序 + 外排序 + 查找&amp;#34;]
    TAOCP --&amp;gt; V4[&amp;#34;卷 4 组合算法&amp;lt;br/&amp;gt;4A 2011 已出&amp;lt;br/&amp;gt;4B/4C 分册连载中&amp;#34;]
    TAOCP --&amp;gt; V5[&amp;#34;卷 5-7 规划中&amp;lt;br/&amp;gt;句法算法 / 语言理论等&amp;#34;]
    V1 -.-&amp;gt;|&amp;#34;排版质量事故&amp;#34;| TEX[&amp;#34;副产品: TeX&amp;lt;br/&amp;gt;全世界数学排版标准&amp;#34;]
    style TEX fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;为什么用-mixmmix-汇编而不是高级语言&#34;&gt;为什么用 MIX/MMIX 汇编，而不是高级语言&lt;/h3&gt;
&lt;p&gt;这是理解全书方法论的一把钥匙。书里所有算法都用一台假想计算机 &lt;strong&gt;MIX&lt;/strong&gt;（后来升级为 64 位 RISC 风格的 &lt;strong&gt;MMIX&lt;/strong&gt;）的机器语言写成。Knuth 给的理由，本质上是三条工程判断：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;要算性能账，就得有一台确定的机器&lt;/strong&gt;。算法分析的核心是精确计算执行次数和时间，而&amp;quot;这条语句花多少时间&amp;quot;在高级语言里是说不清的——编译器、运行时、指令集全在中间隔着。MIX 是一台指令集、时序完全定义好的机器，每条指令几个时钟周期白纸黑字，运行时间的分析因此可以&lt;strong&gt;精确到指令级&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机器语言不赶时髦&lt;/strong&gt;。1960 年代的高级语言换得飞快（Algol、Fortran、PL/I……），用任何一种写书都会很快过时；而一台假想的、设计得足够经典的机器，五十年不过时。事实证明他赌对了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逼你看清代价&lt;/strong&gt;。用汇编写，每一次访存、每一次循环、每一条跳转都暴露在纸面上——Knuth 认为程序员必须知道自己写的每一行代码让机器做了什么，这是&amp;quot;程序设计艺术&amp;quot;里&amp;quot;艺术&amp;quot;二字的底线。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代价也是显然的：阅读门槛被这道汇编抬得很高。这也是为什么本文后面会专门谈&amp;quot;怎么读&amp;quot;——这套书的正确打开方式不是从第一页硬啃。&lt;/p&gt;
&lt;p&gt;另一个体现 Knuth 写作态度的细节是&lt;strong&gt;勘误奖励&lt;/strong&gt;：他给每个在书中首次发现错误的人签发一张&amp;quot;十六进制一美元&amp;quot;的支票（0x$1.00 = 2.56 美元）。钱少到可笑，但这张支票在技术圈是荣誉勋章——它宣示的是一种工程文化：&lt;strong&gt;错误不是羞耻，藏着掖着才是；每一版书都比上一版更接近正确。&lt;/strong&gt; 半个世纪几十个印次，这套书可能是人类历史上被校对得最狠的技术著作。&lt;/p&gt;
&lt;p&gt;还有一个容易忽略的风格特征：几乎每一节结尾都有 &lt;strong&gt;&amp;ldquo;History and bibliography&amp;rdquo;（历史与文献）&lt;/strong&gt; 小节，把这个算法从最早的雏形到最新的改进逐年考证，作者、年份、原始论文一个不落。这让 TAOCP 同时是一部&lt;strong&gt;算法史的目录&lt;/strong&gt;——想知道某个思想的最早出处，查这套书通常比查搜索引擎可靠。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二卷-1基本算法算法的定义与数据结构的奠基&#34;&gt;二、卷 1《基本算法》：算法的定义与数据结构的奠基&lt;/h2&gt;
&lt;h3 id=&#34;算法是什么五个特征与欧几里得算法&#34;&gt;算法是什么：五个特征与欧几里得算法&lt;/h3&gt;
&lt;p&gt;卷 1 开篇就干了件&amp;quot;法典&amp;quot;该干的事：给&amp;quot;算法&amp;quot;下定义。Knuth 用**算法 E（欧几里得求最大公约数）**做标本，提炼出算法的五个特征：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有穷性（Finiteness）&lt;/strong&gt;：算法必须在有限步内终止。（Knuth 特别注明：只满足其余四条而不满足有穷性的，可以叫&amp;quot;计算方法&amp;quot;，比如某些永不终止但仍有用处的反应式过程。）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确切性（Definiteness）&lt;/strong&gt;：每一步都必须精确定义、无歧义——自然语言描述做不到这一点，这是书里坚持用形式化记号的原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入（Input）&lt;/strong&gt;：有零个或多个输入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出（Output）&lt;/strong&gt;：有一个或多个输出，且与输入有确定关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能行性（Effectiveness）&lt;/strong&gt;：每一步都必须足够基本，原则上能用纸笔在有限时间内完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个定义的价值在于它立刻产生了区分能力：菜谱通常不满足确切性（&amp;ldquo;适量盐&amp;rdquo;），死循环程序不满足有穷性，而&amp;quot;掷骰子决定下一步&amp;quot;不满足确切性——都不是算法。&lt;strong&gt;定义不是用来背的，是用来划界的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;数学基础与-oωθ-的来历&#34;&gt;数学基础与 O/Ω/Θ 的来历&lt;/h3&gt;
&lt;p&gt;卷 1 第一章的数学部分（归纳法、数、幂与对数、和与积、排列、斐波那契、生成函数）常被读者跳过，但里面埋着全书的方法论核心：&lt;strong&gt;渐近分析（asymptotic analysis）&lt;/strong&gt;。几个史实值得澄清：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大 O 记号不是 Knuth 发明的——它来自数论学家 &lt;strong&gt;Bachmann&lt;/strong&gt;（1894 年），经 Landau 推广；&lt;/li&gt;
&lt;li&gt;Knuth 的贡献是把它引入算法分析，并在 1976 年专门写了一篇短文《Big Omicron and Big Omega and Big Theta》，&lt;strong&gt;推广了大 Ω（下界）和大 Θ（紧界）记号&lt;/strong&gt;，结束了当时文献里&amp;quot;什么都叫 O&amp;quot;的混乱局面。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，今天我们写在面试白板上的 O(n log n) 这套语言，其语法规范是这套书（及配套论文）定下来的。Knuth 的态度始终如一：&lt;strong&gt;&amp;ldquo;大约 n 的平方量级&amp;quot;不是分析，&amp;ldquo;3n² + 5n - 7 次 MIX 指令执行&amp;quot;才是分析，O 记号是对前者的严格速写，不是含糊的挡箭牌。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;数据结构栈队列链表与树&#34;&gt;数据结构：栈、队列、链表与树&lt;/h3&gt;
&lt;p&gt;卷 1 第二章是整部书对后世影响最深的部分之一——今天教科书里的数据结构章节目录，几乎就是这一章的子孙：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线性结构&lt;/strong&gt;：栈、队列、双端队列，以及链表（含循环链表、双向链表）的完整代数化描述——插入、删除、遍历的每一步都用 MIX 实现并计算代价。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;树与二叉树&lt;/strong&gt;：形式化定义、三种遍历（先序/中序/后序）、&lt;strong&gt;线索二叉树（threaded tree）&lt;/strong&gt;——利用空指针存放前驱后继信息，让遍历不再需要栈。这是一个漂亮的&amp;quot;空间复用&amp;quot;思想实验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态存储分配与垃圾回收&lt;/strong&gt;：内存池的伙伴系统（buddy system）、首次适配/最佳适配策略、以及垃圾回收的标记思想——1968 年就成体系地写在书里，比多数编程语言拥有 GC 早了一代人。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：读这一章最大的震撼是&amp;quot;年代错位感&amp;rdquo;——我们以为的&amp;quot;现代概念&amp;rdquo;（垃圾回收、智能指针的引用计数思想、内存分配策略的权衡），全部写在 1968 年的书里，连权衡的框架都没变过。所谓新技术，多数是老思想在新硬件上的重排。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三卷-2半数值算法随机数与算得准的艺术&#34;&gt;三、卷 2《半数值算法》：随机数与&amp;quot;算得准&amp;quot;的艺术&lt;/h2&gt;
&lt;p&gt;卷 2 处理一个独特的中间地带：对象是数，但关心的是算法而非数值分析。两大主线是随机数和算术。&lt;/p&gt;
&lt;p&gt;先解释一下这个奇怪的书名：&lt;strong&gt;&amp;ldquo;半数值&amp;rdquo;（seminumerical）&lt;/strong&gt; 指的是这类算法介于&amp;quot;纯数值计算&amp;quot;（数值分析的地盘，关心连续数学与误差界）和&amp;quot;纯符号处理&amp;quot;（编译、树操作的地盘）之间——输入输出是数，但算法的骨架是离散的组合结构。随机数生成、进制转换、多精度算术都属于这一带。&lt;/p&gt;
&lt;h3 id=&#34;随机数不该用随机的方法生成&#34;&gt;随机数：不该用随机的方法生成&lt;/h3&gt;
&lt;p&gt;卷 2 第三章是全书被引用最多的章节之一，开篇那句名言每个工程师都该见过一次：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Random numbers should not be generated by a method chosen at random.&amp;quot;（随机数不该用一个随机选出来的方法生成。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Knuth 用亲身经历佐证这句话：他年轻时曾设计过一个&amp;quot;超级随机&amp;quot;的生成器——程序里随机地跳转、随机地执行不同操作，自以为复杂到不可能有规律；实际一跑，&lt;strong&gt;序列几乎立刻陷入一个短循环或不动点&lt;/strong&gt;。教训被写成定理级别的警告：复杂性不等于随机性，没有理论分析的&amp;quot;看起来乱&amp;quot;都是自欺欺人。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线性同余法（LCG）&lt;/strong&gt;：x 的下一个值 = (a·x + c) mod m。全书用几十页分析三个参数怎么选——模数 m、乘数 a、增量 c 的选择决定了周期长度和统计质量，每一个坏选择的下场都有具体展示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;统计检验&lt;/strong&gt;：卡方检验、Kolmogorov-Smirnov 检验、以及专门测高维均匀性的&lt;strong&gt;谱检验（spectral test）&lt;/strong&gt;——一个 LCG 可以在一维上完美均匀，在二维平面上却排成几条明显的直线，谱检验就是抓这种&amp;quot;隐藏结构&amp;quot;的。&lt;/li&gt;
&lt;li&gt;这一章的工程翻译到今天依然直接：&lt;strong&gt;不要用 &lt;code&gt;rand()&lt;/code&gt; 做密码学，不要自己发明随机数生成器，用之前先查它的检验记录。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;高精度算术与算得准&#34;&gt;高精度算术与&amp;quot;算得准&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;另一条主线是让计算机突破机器字长地算数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多精度算术&lt;/strong&gt;：加减乘的经典算法之外，重点是&lt;strong&gt;算法 D（多精度除法）&lt;/strong&gt;——除法是四则运算里唯一需要&amp;quot;试商&amp;quot;的，Knuth 给出完整的规范化、试商、校正流程和正确性证明。今天所有大整数库的除法实现，祖师爷都是这一节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;进制转换&lt;/strong&gt;：二进制、十进制之间的转换算法及其代价分析——这个在当年是日常刚需。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有理数与浮点运算&lt;/strong&gt;：浮点数的表示、舍入误差分析——&amp;ldquo;浮点算术不是实数算术&amp;quot;这个每个工程师都会踩的坑，书里有完整的误差传播分析。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多项式算术与幂的快速计算&lt;/strong&gt;：包括快速幂（平方-乘方法）的前身分析，以及乘法复杂度的下潜——Karatsuba 乘法这类&amp;quot;比课本算法快&amp;quot;的分治思路。更进一步是**加法链（addition chain）**问题：计算 x 的 n 次方最少需要几次乘法？&amp;ldquo;反复平方&amp;quot;并不总是最优，而求最优加法链本身至今没有完全解决——又一个&amp;quot;看似简单、实则研究级&amp;quot;的 50 分题栖息地。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：卷 2 是四卷里&amp;quot;最数学&amp;quot;的一卷，但它的工程含义一点不虚：凡是钱、坐标、时间戳参与计算的地方，都在和&amp;quot;算得准&amp;quot;打交道。浮点误差、大数溢出、随机数质量——这三类生产事故的完整理论都在这一卷里，早该被列为后端工程师的体检项目。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四卷-3排序与查找工程师的硬通货&#34;&gt;四、卷 3《排序与查找》：工程师的硬通货&lt;/h2&gt;
&lt;p&gt;四卷里工程密度最高、最值得当工具书常备的一卷。Knuth 把排序和查找这两个&amp;quot;最古老的生产问题&amp;quot;做到了考古学级别的穷尽。&lt;/p&gt;
&lt;h3 id=&#34;内排序一个家族的谱系&#34;&gt;内排序：一个家族的谱系&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
    S[&amp;#34;内排序算法家族&amp;#34;] --&amp;gt; I[&amp;#34;插入类&amp;lt;br/&amp;gt;直接插入 / Shell 排序&amp;#34;]
    S --&amp;gt; E[&amp;#34;交换类&amp;lt;br/&amp;gt;冒泡 / 快速排序 Hoare 1962&amp;#34;]
    S --&amp;gt; SE[&amp;#34;选择类&amp;lt;br/&amp;gt;直接选择 / 堆排序 Williams-Floyd&amp;#34;]
    S --&amp;gt; M[&amp;#34;归并类&amp;lt;br/&amp;gt;二路归并 / 自然归并&amp;#34;]
    S --&amp;gt; D[&amp;#34;分布类&amp;lt;br/&amp;gt;计数 / 基数排序&amp;#34;]
    E -.-&amp;gt;|&amp;#34;平均最优&amp;lt;br/&amp;gt;原地性差&amp;#34;| NOTE[&amp;#34;各有适用场景&amp;lt;br/&amp;gt;没有全能冠军&amp;#34;]
    M -.-&amp;gt;|&amp;#34;稳定 + 可外排&amp;#34;| NOTE
    style NOTE fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;书里对每一类的处理都是&amp;quot;描述 + 实现 + 精确分析&amp;quot;三段式，几个今天仍在生产环境活跃的名字：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;快速排序&lt;/strong&gt;（Hoare，1962）：Knuth 给出了平均比较次数的完整推导，以及最坏 O(n²) 的触发条件——&amp;ldquo;已排序输入配最左基准元&amp;quot;这个教科书反面案例就出自这里的分析。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;堆排序&lt;/strong&gt;（Williams 提出堆，Floyd 改进）：最坏情况也是 O(n log n)，堆这个数据结构本身后来长成了优先级队列的标准实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;归并排序&lt;/strong&gt;：稳定、可预测，并且是通往外排序的桥。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;读这一章时建议始终带着 Knuth 的三个评价维度去看每个算法：&lt;strong&gt;稳定性&lt;/strong&gt;（相等元素的相对次序是否保持）、&lt;strong&gt;原地性&lt;/strong&gt;（额外空间是常数还是随 n 增长）、&lt;strong&gt;适应性&lt;/strong&gt;（输入已接近有序时能否变快）。三个维度没有全能冠军——快排快在常数、堆排赢在最坏情况、归并赢在稳定和可外排——&lt;strong&gt;选型就是在这张三维表里做取舍&lt;/strong&gt;，这也是&amp;quot;没有银弹，只有权衡&amp;quot;在算法层的具体形态。&lt;/p&gt;
&lt;h3 id=&#34;最优排序n-log-n-下界的信息论论证&#34;&gt;最优排序：n log n 下界的信息论论证&lt;/h3&gt;
&lt;p&gt;卷 3 最优雅的一段数学是&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 LR
    A[&amp;#34;n 个元素&amp;lt;br/&amp;gt;共有 n! 种可能排列&amp;#34;] --&amp;gt; B[&amp;#34;比较排序 = 决策树&amp;lt;br/&amp;gt;每个内部节点是一次比较&amp;lt;br/&amp;gt;每片叶子对应一种排列&amp;#34;]
    B --&amp;gt; C[&amp;#34;树至少有 n! 片叶子&amp;#34;]
    C --&amp;gt; D[&amp;#34;高度至少为 log2(n!)&amp;#34;]
    D --&amp;gt; E[&amp;#34;Stirling 公式&amp;lt;br/&amp;gt;log2(n!) 约等于&amp;lt;br/&amp;gt;n log2 n - 1.44n&amp;#34;]
    E --&amp;gt; F[&amp;#34;结论:&amp;lt;br/&amp;gt;任何比较排序的最坏情况&amp;lt;br/&amp;gt;下界为 Ω(n log n)&amp;#34;]
    style F fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这个论证的力量在于它&lt;strong&gt;不针对任何具体算法&lt;/strong&gt;——它限制的是&amp;quot;比较&amp;quot;这个信息获取方式本身：一次比较最多带来 1 比特信息，而区分 n! 种排列需要 log2(n!) 比特。要快过 n log n，唯一的出路是放弃纯比较——这正是基数排序、计数排序这些&amp;quot;分布类&amp;quot;算法存在的理由。这是我在全书中最喜欢的一段：&lt;strong&gt;用信息论给一类算法封顶，一句话堵死一个方向的无效努力。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;外排序与查找&#34;&gt;外排序与查找&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外排序&lt;/strong&gt;：当数据放不进内存，瓶颈从比较次数变成读写趟数。多路归并、**置换选择（replacement selection）**生成初始归并段、多相归并——这一整套是磁带时代的遗产，但思想直接活在今天的 LSM-tree（LevelDB/RocksDB 的多路归并 compaction）里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查找&lt;/strong&gt;：顺序查找与&lt;strong&gt;二分查找&lt;/strong&gt;（Knuth 照例指出：思路人人会，写对的没几个——边界条件的完整分析在书里；他还考据出二分查找的思路 1946 年就见诸文献，而第一个对所有输入规模都正确的完整算法直到 1962 年才发表，一个&amp;quot;显而易见&amp;quot;的算法花了十六年才写对）；&lt;strong&gt;哈希&lt;/strong&gt;：拉链法、开放寻址、线性探测的平均探查次数分析——线性探测的完整数学分析正是 Knuth 本人在 1962 年做出的，他自称正是这个问题让他决定写这套书；&lt;strong&gt;平衡树&lt;/strong&gt;：AVL 树的旋转与再平衡，多路查找树与 &lt;strong&gt;B 树&lt;/strong&gt;——数据库索引的物理形态；&lt;strong&gt;数字查找树（trie）&lt;/strong&gt;：按位/按字符逐级展开的查找结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五卷-4a组合算法生成一切的艺术&#34;&gt;五、卷 4A《组合算法》：生成一切的艺术&lt;/h2&gt;
&lt;p&gt;隔了 38 年，2011 年出的卷 4A 换了一个世界：从&amp;quot;算数&amp;quot;和&amp;quot;排序&amp;quot;进入&lt;strong&gt;组合对象&lt;/strong&gt;——布尔函数、组合、排列、划分——的领域。这卷的气质和前三卷一脉相承：先把对象定义到无歧义，再讨论&amp;quot;如何系统性地、不重不漏地枚举出全部实例&amp;rdquo;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;布尔函数与按位运算&lt;/strong&gt;（7.1 节）：真值表、按位技巧的宝库——掩码、并行按位计数（population count 的 sideways addition）、&amp;ldquo;x 与 x-1 的按位与可以抹掉最低位的 1&amp;quot;这类惯用法——这一节读起来像高级位操作黑魔法集，但每个技巧都有正确性论证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成所有组合对象&lt;/strong&gt;（7.2.1 节）：全 n 元组与 &lt;strong&gt;Gray 码&lt;/strong&gt;（相邻码字只差一位的编码，枚举时每次只翻转一位）、全排列、全组合、整数划分、集合划分、所有树——每类对象都给出&amp;quot;不重不漏、常数均摊时间&amp;quot;的生成算法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回溯思想&lt;/strong&gt;：组合生成的算法骨子里都是&amp;quot;受控的回溯&amp;rdquo;——按字典序推进、到头回退。系统化的回溯框架（Algorithm B）在卷 4B 的 7.2.2 节正式登场；顺带交代一句出版史：4A 出版后 Knuth 继续以分册形式推进，卷 4B 已于 2022 年正式成书，当年那个&amp;quot;卷 4 写了四十年&amp;quot;的段子至此翻篇了一半。&lt;/li&gt;
&lt;/ul&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 TD
    R[&amp;#34;空解&amp;lt;br/&amp;gt;第 0 层&amp;#34;] --&amp;gt; A[&amp;#34;第 1 位选 a&amp;#34;]
    R --&amp;gt; B[&amp;#34;第 1 位选 b&amp;#34;]
    A --&amp;gt; A1[&amp;#34;第 2 位选 a&amp;lt;br/&amp;gt;...继续深入&amp;#34;]
    A --&amp;gt; A2[&amp;#34;第 2 位选 b&amp;#34;]
    A2 --&amp;gt;|&amp;#34;约束不满足&amp;lt;br/&amp;gt;剪枝回退&amp;#34;| X1[&amp;#34;放弃整棵子树&amp;#34;]
    A1 --&amp;gt; A1L[&amp;#34;到达叶子&amp;lt;br/&amp;gt;输出一个完整解&amp;#34;]
    B --&amp;gt; B1[&amp;#34;...同样的展开&amp;#34;]
    X1 -.-&amp;gt;|&amp;#34;回退到最近分叉&amp;lt;br/&amp;gt;试下一个分支&amp;#34;| A
    style X1 fill:#ffcccc
    style A1L fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;核心纪律只有两条：&lt;strong&gt;到底就输出，碰壁就回退，回退后试下一个未试过的分支&lt;/strong&gt;——不重不漏靠的就是这条纪律，而不是事后去重。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：卷 4A 对今天的工程师有一个直接映射：&lt;strong&gt;所有&amp;quot;遍历所有可能配置&amp;quot;的需求&lt;/strong&gt;——测试用例生成、组合优化、穷举校验——都是组合生成问题，而&amp;quot;生成&amp;quot;远比&amp;quot;收集后再去重&amp;quot;高效。不重不漏四个字，说起来像小学要求，做起来是算法设计里最硬的纪律之一。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六怎么读这套书习题分级费马大定理与路线图&#34;&gt;六、怎么读这套书：习题分级、费马大定理与路线图&lt;/h2&gt;
&lt;h3 id=&#34;习题分级体系00-到-50-的登山标尺&#34;&gt;习题分级体系：00 到 50 的登山标尺&lt;/h3&gt;
&lt;p&gt;TAOCP 的习题是半本书——Knuth 给每道题标了难度分，这套编号体系本身就是个值得单独记住的设计：&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;00&lt;/td&gt;
					&lt;td&gt;看一眼就该会&lt;/td&gt;
					&lt;td&gt;几秒钟&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;简单&lt;/td&gt;
					&lt;td&gt;一分钟内&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;20&lt;/td&gt;
					&lt;td&gt;中等&lt;/td&gt;
					&lt;td&gt;一刻钟上下&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;30&lt;/td&gt;
					&lt;td&gt;较难&lt;/td&gt;
					&lt;td&gt;可能需要数小时&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;40&lt;/td&gt;
					&lt;td&gt;学期项目级&lt;/td&gt;
					&lt;td&gt;数周&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;50&lt;/td&gt;
					&lt;td&gt;研究级问题&lt;/td&gt;
					&lt;td&gt;出题时可能无人解决&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;编号前可加 M（数学向）或 HM（高等数学向）。最著名的梗是卷 1 里那道 &lt;strong&gt;[M50]&lt;/strong&gt;：题目是&amp;quot;证明费马大定理&amp;rdquo;——Knuth 的意思是&amp;quot;这道题出题时没人会做&amp;rdquo;。1995 年 Wiles 真的证明了它之后，后续印次里这道题的处境变成了书迷间的段子。&lt;strong&gt;50 分题不是作业，是路标：它告诉你学科边界在哪里。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;我的路线图建议&#34;&gt;我的路线图建议&lt;/h3&gt;
&lt;p&gt;对全职工程师，&amp;ldquo;通读&amp;quot;是个伪目标，务实的姿势是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;卷 3 当工具书常备&lt;/strong&gt;：排序和查找是唯一一卷会在日常代码评审、性能分析、系统设计面试中直接变现的。通读一遍，之后按需回查。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卷 1 读第二章&lt;/strong&gt;：数据结构部分是&amp;quot;为什么链表这样设计、为什么 GC 这样工作&amp;quot;的第一性原理来源；第一章数学基础按需查，不必顺序啃。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卷 2 按需精读&lt;/strong&gt;：写大数、随机、浮点相关代码之前，把对应章节当 pre-mortem 读一遍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卷 4A 当兴趣读物&lt;/strong&gt;：遇到组合枚举需求（生成测试组合、配置穷举）时按目录取用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;习题只做 20 分以下的&lt;/strong&gt;：10-20 分题保证理解，30 分以上当文献指南——题目本身常附带一整段文献史，不打算做题也值得读题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别按顺序读&lt;/strong&gt;。Knuth 自己也没指望人顺序读：每节相对独立，前置依赖写在节首。把这套书当成&amp;quot;算法领域的百科全书&amp;quot;而不是&amp;quot;课程&amp;rdquo;，是心态上最重要的一步。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七落到工程实践这套书改变了什么&#34;&gt;七、落到工程实践：这套书改变了什么&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;复杂度分析从&amp;quot;感觉&amp;quot;变成了&amp;quot;账&amp;quot;。&lt;/strong&gt; 读过 Knuth 的分析后，再说&amp;quot;这个操作是 O(n)&amp;ldquo;会本能地问：常数是什么？最坏情况和平均情况分别是多少？输入分布假设是什么？这套书最宝贵的遗产不是任何单个算法，而是**&amp;ldquo;性能是可以、也必须被精确计算的&amp;quot;这个信念**——profile 之前的估算、capacity planning 里的量级推演，全是它的日常形态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对&amp;quot;下界&amp;quot;的敬畏。&lt;/strong&gt; n log n 的信息论论证训练了一种思维方式：优化之前先问这个问题的理论下界在哪，离下界还有多远。很多&amp;quot;再优化一下&amp;quot;的努力，看一眼下界就知道是徒劳——&lt;strong&gt;知道哪里没有油水，和知道哪里有油水一样值钱。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机与浮点有了红线意识。&lt;/strong&gt; &amp;ldquo;不自己造随机数生成器&amp;quot;&amp;ldquo;钱不用浮点表示&amp;quot;这类团队规约，以前是被当经验口诀背下来的；读完卷 2 它们变成了有证明的结论，执行的腰杆完全不一样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读旧文献的胆子变大了。&lt;/strong&gt; Knuth 的行文示范了一件事：1960 年代的论文不是&amp;quot;过时的古董&amp;rdquo;，而是很多问题唯一的完整答案所在。排障和选型时敢往回翻原始论文，是这套书给的胆量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对&amp;quot;正确性&amp;quot;的标准被永久抬高了。&lt;/strong&gt; 书里每个算法都带正确性证明或严格论证，读多了会形成一种肌肉记忆：写完一段非平凡逻辑，先问&amp;quot;循环不变式是什么&amp;quot;&amp;ldquo;边界情况枚举全了吗&amp;rdquo;。二分查找十六年才写对的考据，是这条标准最好的反面教材。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;本篇小结&#34;&gt;本篇小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TAOCP 是算法学科的编年史加分析法典：七卷规划，卷 1-3 出版于 1968-1973，卷 4A 迟至 2011，副产品是 TeX；Gates 那句&amp;quot;读完发简历&amp;quot;说的是它的密度。&lt;/li&gt;
&lt;li&gt;MIX/MMIX 假想机不是老派怪癖，是方法论：要精确算性能账，就得有一台指令级确定的机器。&lt;/li&gt;
&lt;li&gt;卷 1 立规矩：算法五特征（有穷、确切、输入、输出、能行），数据结构大全，O/Ω/Θ 的语法规范（O 来自 Bachmann，Ω/Θ 由 Knuth 推广）。&lt;/li&gt;
&lt;li&gt;卷 2 管&amp;quot;算得准&amp;rdquo;：随机数不该用随机方法生成（LCG 参数分析、谱检验），多精度算术（算法 D），浮点误差。&lt;/li&gt;
&lt;li&gt;卷 3 是工程师硬通货：排序家族全谱、n log n 下界的信息论论证（比较即信息获取）、外排序思想活在今天的 LSM-tree 里、哈希与 B 树。&lt;/li&gt;
&lt;li&gt;卷 4A 进入组合世界：按位技巧、不重不漏地生成所有组合/排列/划分，回溯的思想雏形。&lt;/li&gt;
&lt;li&gt;读法：卷 3 常备、卷 1 读数据结构、卷 2/4A 按需精读；习题分级 00-50，[M50] 的费马大定理题是学科边界的路标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套书我没读完，大概率也永远不会读完——但这不妨碍它一直在工作：它就立在手边，像一个不说话的考官，每次想偷懒说&amp;quot;差不多 n log n 吧&amp;quot;的时候，抬头就能看见。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 1: &lt;em&gt;Fundamental Algorithms&lt;/em&gt; (3rd ed., Addison-Wesley, 1997; 初版 1968).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 2: &lt;em&gt;Seminumerical Algorithms&lt;/em&gt; (3rd ed., 1997; 初版 1969).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 3: &lt;em&gt;Sorting and Searching&lt;/em&gt; (2nd ed., 1998; 初版 1973).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, Volume 4A: &lt;em&gt;Combinatorial Algorithms, Part 1&lt;/em&gt; (Addison-Wesley, 2011).&lt;/li&gt;
&lt;li&gt;Donald E. Knuth, &amp;ldquo;Big Omicron and Big Omega and Big Theta&amp;rdquo;, &lt;em&gt;ACM SIGACT News&lt;/em&gt;, 1976（O/Ω/Θ 记号规范化的原始短文）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《编码》笔记：Petzold 教你从手电筒开始，亲手搭出一台计算机</title>
      <link>https://ftmao.com/blog/bookstore/code-petzold-book-notes/</link>
      <pubDate>Wed, 08 Nov 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/code-petzold-book-notes/</guid>
      <description>&lt;h1 id=&#34;编码笔记petzold-教你从手电筒开始亲手搭出一台计算机&#34;&gt;《编码》笔记：Petzold 教你从手电筒开始，亲手搭出一台计算机&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写了多年代码、对&amp;quot;计算机底层怎么跑起来的&amp;quot;只有模糊印象的工程师；也适合所有想给 CS 基础补课的非科班同行。
参考书籍：Charles Petzold《编码：隐匿在计算机软硬件背后的语言》（Code: The Hidden Language of Computer Hardware and Software，1999 年初版；2022 年第 2 版对章节做了扩充并配了在线交互演示，主线结构不变）。
阅读目标：不是抄一份计算机组成原理提纲，而是跟着 Petzold 的搭积木路径走一遍——每一层只用上一层的东西，绝不凭空引入新概念——把&amp;quot;CPU 在执行指令&amp;quot;这句话从信仰变成直觉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一全书主线一次不跳步的推导&#34;&gt;一、全书主线：一次不跳步的推导&lt;/h2&gt;
&lt;p&gt;大多数计算机教材的讲法是&amp;quot;切横断面&amp;quot;：第一章讲数制，第二章讲门电路，第三章讲组成原理，每章都是成品快照。Petzold 的讲法是&lt;strong&gt;纵向钻一口井&lt;/strong&gt;：从两个小孩隔着窗户用手电筒闪摩尔斯电码开始，一路向下——电筒换成电线，电线换成电报，电报里拆出继电器，继电器组成逻辑门，逻辑门搭出加法器，加法器加上触发器变成内存，内存配上指令码变成自动计算机。全书二十多章，&lt;strong&gt;每一步的零件都来自上一步，没有一处&amp;quot;此处略去一万字&amp;quot;&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
    A[&amp;#34;手电筒 + 摩尔斯电码&amp;lt;br/&amp;gt;编码即信息表示&amp;#34;] --&amp;gt; B[&amp;#34;电报 + 继电器&amp;lt;br/&amp;gt;电流的可控开关&amp;#34;]
    B --&amp;gt; C[&amp;#34;布尔代数 + 逻辑门&amp;lt;br/&amp;gt;开关做判断&amp;#34;]
    C --&amp;gt; D[&amp;#34;加法器&amp;lt;br/&amp;gt;门电路做算术&amp;#34;]
    D --&amp;gt; E[&amp;#34;触发器 + 振荡器&amp;lt;br/&amp;gt;电路有了记忆与节拍&amp;#34;]
    E --&amp;gt; F[&amp;#34;内存阵列&amp;lt;br/&amp;gt;可寻址的存储&amp;#34;]
    F --&amp;gt; G[&amp;#34;自动操作&amp;lt;br/&amp;gt;指令码 + 累加器 + 跳转&amp;#34;]
    G --&amp;gt; H[&amp;#34;微处理器 + 操作系统&amp;lt;br/&amp;gt;进入我们熟悉的世界&amp;#34;]
    style A fill:#e8f5e9
    style G fill:#fff3cd&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这种写法对工程师有一个特别的价值：&lt;strong&gt;它把&amp;quot;底层&amp;quot;从黑话变成了具体的东西&lt;/strong&gt;。我们日常说&amp;quot;这代码编译成指令在 CPU 上跑&amp;quot;，多数人（包括多年前的我）其实只是在复述一个信仰。跟着这本书走完一遍，&amp;ldquo;指令&amp;quot;就是你亲手设计过的那几个二进制编码，&amp;ldquo;CPU&amp;quot;就是你亲手连过的那几个加法器和触发器的组合——再没有神秘空间留给想象。&lt;/p&gt;
&lt;p&gt;Petzold 在全书贯串的那句话，也是这篇笔记的主线：&lt;strong&gt;计算机里没有魔法，一切都建立在二元选择与电路上。&lt;/strong&gt; 剩下的全是组合与封装。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二编码的本质信息表示与二元选择&#34;&gt;二、编码的本质：信息表示与二元选择&lt;/h2&gt;
&lt;p&gt;书的前几章一个电子元件都没有，全在讲&amp;quot;码&amp;rdquo;：摩尔斯电码、布莱叶盲文、手电筒信号。Petzold 这么安排是在立全书的第一个支柱——&lt;strong&gt;编码不依赖任何具体载体，它只依赖&amp;quot;可区分的不同状态&amp;rdquo;&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;摩尔斯电码&lt;/strong&gt;证明：只用两种信号（点、划），加上时序规则，就能表达全部字母和数字。它还顺手展示了编码设计的精妙：常用字母（E、T）用短码，生僻字母用长码——这就是一百多年前的变长编码和频率统计。Petzold 还画出了那张著名的&lt;strong&gt;解码二叉树&lt;/strong&gt;：从树根出发，点是向左、划是向右，顺着信号一路走下去就唯一落到某个字母上——译码本质上是二分查找，这是&amp;quot;二元选择组合出任意复杂度&amp;quot;的第一个具体例子。书里有个诚实的补充也值得记住：摩尔斯电码严格说不是纯二元码，因为&amp;quot;点与划之间的停顿、字母之间的停顿、单词之间的停顿&amp;quot;本身就携带信息——&lt;strong&gt;时序也是一种码元&lt;/strong&gt;。这个细节在后面讲串行通信和时钟的章节里会一再回来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;布莱叶盲文&lt;/strong&gt;证明：6 个凸点位置，每个位置只有&amp;quot;凸/平&amp;quot;两种状态，2 的 6 次方等于 64 个码字，足够装下字母、数字、标点，还留了余量给&amp;quot;数字转义符&amp;quot;&amp;ldquo;大写转义符&amp;quot;这类&lt;strong&gt;转义码（shift/escape code）&lt;/strong&gt;——码不够用时，不扩格子，用前缀切换语义。更进一步，二级布莱叶盲文（Grade 2）还引入了缩写字——用一个码字表示整个常用词——用空间（码表复杂度）换速度。这个思路后来在 ASCII 扩展、Unicode、协议设计、甚至 Huffman 编码里反复出现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手电筒闪码&lt;/strong&gt;证明：编码可以是纯行为，不需要任何纸张和机器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三章合起来说的其实是一句话：&lt;strong&gt;只要一个系统能提供足够多的&amp;quot;可区分的二选一&amp;rdquo;，它就能表示任何信息。&lt;/strong&gt; 二进制不是一种&amp;quot;计算机语言&amp;quot;，它是最小成本的区分方案——两个状态最容易造、最抗干扰、最不容易读错。计算机用二进制不是因为它&amp;quot;喜欢&amp;quot;0 和 1，而是因为造一个可靠区分两档的物理装置（开/关、高/低电压）远比造可靠区分十档的装置便宜。&lt;/p&gt;
&lt;h3 id=&#34;数制二进制是机器的语言十六进制是人的速记&#34;&gt;数制：二进制是机器的语言，十六进制是人的速记&lt;/h3&gt;
&lt;p&gt;书里对数制的处理也很克制：二进制是电路的天然语言，八进制和十六进制&lt;strong&gt;只是人类给二进制发明的速记法&lt;/strong&gt;——一位十六进制数字恰好对应 4 个二进制位，换算零成本。这解释了为什么我们至今还在和 &lt;code&gt;0xFF&lt;/code&gt; 打交道：不是情怀，是十六进制在&amp;quot;人类可读&amp;quot;和&amp;quot;机器对齐&amp;quot;之间的最佳折中。字节（8 位）成为标准单位后，两位十六进制数恰好表示一个字节，这个巧合让十六进制的地位彻底锁死。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：编码这一层想通之后，再看日常的东西眼光会变。字符集是编码、颜色是编码、文件格式是编码、协议报文是编码——全是&amp;quot;约定一组状态，再约定每个状态意味着什么&amp;quot;。出 bug 的时候，一半是逻辑错，另一半是&lt;strong&gt;两边对同一份编码的理解不一致&lt;/strong&gt;（编码声明错了、转义没做、字节序反了）。Petzold 把编码放在第一章不是怀旧，是在定义全书的词汇表。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三电报与继电器一切从一根电线开始&#34;&gt;三、电报与继电器：一切从一根电线开始&lt;/h2&gt;
&lt;p&gt;从手电筒到计算机之间隔着一个关键跳跃：&lt;strong&gt;让电替你传递信号，并且让电替你控制电&lt;/strong&gt;。Petzold 借电报史完成了这一步。&lt;/p&gt;
&lt;p&gt;电报机（Samuel Morse 那一脉）的原理朴素得让人不好意思：一根导线、一个电池、一个能通断电路的按键、另一头一个电磁铁吸动的发声器。按下按键，远处&amp;quot;咔嗒&amp;quot;一声——摩尔斯电码就此摆脱了视线距离的限制。1844 年莫尔斯在华盛顿和巴尔的摩之间架通了第一条实验电报线，发出的第一封电文《圣经》语句&amp;quot;What hath God wrought&amp;quot;，Petzold 引用它时也带着点敬意：这是人类第一次让信息的传播速度超过了马和火车。&lt;/p&gt;
&lt;p&gt;真正的问题出在工程上：导线有电阻，信号传几十公里就衰减得不成样子。解决办法是&lt;strong&gt;继电器（relay）&lt;/strong&gt;——用微弱电流驱动电磁铁，电磁铁吸动一个新的开关，接通一段新的、电力充足的电路，把信号&amp;quot;原样转发&amp;quot;出去。&lt;/p&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;：这是它的本质——一个不需要人手的、可以被另一路电流开合的开关。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第二个身份是整座大厦的地基。Petzold 在这里点破了一件事：&lt;strong&gt;继电器输出的电，可以反过来去控制别的继电器&lt;/strong&gt;。这句话听起来平淡，但它意味着电路第一次可以&amp;quot;自己操作自己&amp;quot;——后面所有的逻辑门、加法器、存储器，都是这句话的展开。把&amp;quot;人手按开关&amp;quot;换成&amp;quot;电流控制开关&amp;quot;，就是硬件开始脱离人工的瞬间。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四布尔代数与逻辑门用开关做判断&#34;&gt;四、布尔代数与逻辑门：用开关做判断&lt;/h2&gt;
&lt;p&gt;19 世纪中叶，George Boole 发表了他的逻辑代数：把&amp;quot;真/假&amp;quot;映射成 1/0，把&amp;quot;并且/或者/否定&amp;quot;映射成代数运算。Boole 本人大概没想过这东西有什么用——直到 1938 年，Claude Shannon 在 MIT 的硕士论文里证明：&lt;strong&gt;开关电路和布尔代数是同一件事的两面&lt;/strong&gt;。串联的开关就是&amp;quot;与&amp;quot;，并联的开关就是&amp;quot;或&amp;quot;，反向接通就是&amp;quot;非&amp;quot;。Petzold 把这段历史搬出来，是为了让逻辑门的出现显得不可避免：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;与门（AND）&lt;/strong&gt;：两个继电器串联，两路都有电流，输出才通。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;或门（OR）&lt;/strong&gt;：两个继电器并联，任一路有电流，输出就通。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反相器（NOT）&lt;/strong&gt;：电磁铁吸动一个&amp;quot;平时闭合&amp;quot;的开关，有输入反而断开。&lt;/li&gt;
&lt;li&gt;由这三样再组合出&lt;strong&gt;与非（NAND）、或非（NOR）、异或（XOR）&lt;/strong&gt;。其中异或——&amp;ldquo;两者不同才为真&amp;rdquo;——马上要成为加法器的主角。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里有个对程序员很有亲切感的转折：&lt;strong&gt;从这一章开始，继电器这个具体的电磁装置就退场了，剩下的书里只剩抽象的门符号。&lt;/strong&gt; Petzold 特意提醒你：门可以用继电器做，后来用真空管做，再后来用晶体管做，今天用 CMOS 做——&lt;strong&gt;实现换了一代又一代，逻辑结构一行都不用改&lt;/strong&gt;。这是我们软件工程师天天享受却未必意识到其来历的红利：抽象层一旦正确，底下怎么换天都无所谓。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：Shannon 那篇论文常被引用为&amp;quot;信息论的开端&amp;quot;，但在这本书的语境里它更像一份&lt;strong&gt;接口定义文档&lt;/strong&gt;——一边是数学家的布尔代数，一边是工程师的开关电路，两边从此可以各自演化、互相对接。好的抽象层都是这个形态：两边的人都不用懂对方的全部细节，只要共同遵守那个映射。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五搭加法器全书的第一个工程高潮&#34;&gt;五、搭加法器：全书的第一个工程高潮&lt;/h2&gt;
&lt;p&gt;有了门，Petzold 立刻提出第一个像样的工程问题：&lt;strong&gt;用门电路算加法&lt;/strong&gt;。这一步的推导漂亮得值得记下来：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先看一位加法的结果表：0+0=0、0+1=1、1+0=1、1+1=0（进位 1）。结果位恰好是&lt;strong&gt;异或&lt;/strong&gt;；进位位恰好是&lt;strong&gt;与&lt;/strong&gt;。一个 XOR 门加一个 AND 门，就是&lt;strong&gt;半加器（half adder）&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;半加器处理不了低位传上来的进位，所以真正的每一位要用&lt;strong&gt;全加器（full adder）&lt;/strong&gt;：两个半加器加一个或门——先加两个输入，再把结果与进位输入相加，两路进位用或门合并输出。&lt;/li&gt;
&lt;li&gt;把 8 个全加器首尾相接（低位的进位输出接高位的进位输入），就是一台 &lt;strong&gt;8 位行波进位加法器（ripple-carry adder）&lt;/strong&gt;。进位像波浪一样从右往左传播，最坏情况要传满 8 级——这是书里第一次出现&amp;quot;性能&amp;quot;概念：电路的正确性和电路的速度是两回事。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;输入 A&amp;#34;] --&amp;gt; HA1[&amp;#34;半加器 1&amp;lt;br/&amp;gt;XOR 出和&amp;lt;br/&amp;gt;AND 出进位&amp;#34;]
    B[&amp;#34;输入 B&amp;#34;] --&amp;gt; HA1
    CI[&amp;#34;低位进位输入&amp;#34;] --&amp;gt; HA2[&amp;#34;半加器 2&amp;#34;]
    HA1 --&amp;gt;|&amp;#34;中间和&amp;#34;| HA2
    HA1 --&amp;gt;|&amp;#34;进位 1&amp;#34;| OR1[&amp;#34;或门&amp;#34;]
    HA2 --&amp;gt;|&amp;#34;进位 2&amp;#34;| OR1
    HA2 --&amp;gt; SUM[&amp;#34;本位和输出&amp;#34;]
    OR1 --&amp;gt; CO[&amp;#34;进位输出&amp;lt;br/&amp;gt;送往更高一位&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;8 个这样的全加器级联，接上 8 个开关作输入、8 个灯泡作输出，你就有一台能算 8 位加法的机器——开关拨出被加数和加数，灯泡亮灭就是结果，输入输出全是二进制，但&lt;strong&gt;这已经是&amp;quot;人机界面&amp;quot;的雏形&lt;/strong&gt;：机器内部的表示和呈现给人的形式，从第一天起就是两回事。Petzold 在这一章结尾的成就感是全书第一个峰值：&lt;strong&gt;减法也不用另造&lt;/strong&gt;——把减数按位取反再加一（补码），扔回同一台加法器就算完了。补码这个设计让减法、负数表示全部复用加法硬件，是&amp;quot;编码设计省掉硬件&amp;quot;的典范——一个纯粹的数制约定，换掉了整整一套减法电路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：读这一章的时候我专门停下来想了一下：我写了这么多年 &lt;code&gt;a + b&lt;/code&gt;，这是第一次真正&amp;quot;看见&amp;quot;那个加号。不是比喻意义上的看见，是逐位地、进位一级一级传过去地看见。这个体验本身值回书价。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六反馈与触发器电路学会了记忆&#34;&gt;六、反馈与触发器：电路学会了记忆&lt;/h2&gt;
&lt;p&gt;到加法器为止，所有电路都是&lt;strong&gt;组合的&lt;/strong&gt;——输出完全由当前输入决定，输入撤了，输出就没了。这样的电路不会&amp;quot;记住&amp;quot;任何东西。Petzold 用整整一章讲那个改变一切的结构：&lt;strong&gt;反馈（feedback）&lt;/strong&gt;——把输出接回输入。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;振荡器（oscillator）&lt;/strong&gt;：把反相器的输出接回自己的输入，电路就在 0 和 1 之间自动来回翻转。不需要任何人操作，它自己产生了节拍——这就是时钟（clock）的雏形，后来计算机里一切&amp;quot;按节拍推进&amp;quot;的源头。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;R-S 触发器（flip-flop）&lt;/strong&gt;：两个或非门交叉耦合——每个门的输出接回另一个门的输入。S（置位）给一个脉冲，输出变 1 并保持；R（复位）给一个脉冲，输出变 0 并保持。两个输入都撤掉之后，&lt;strong&gt;输出停在最后的状态&lt;/strong&gt;。电路记住了&amp;quot;上一次谁碰过我&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D 触发器（电平/边沿触发）&lt;/strong&gt;：给 R-S 触发器加一路&amp;quot;时钟&amp;quot;输入，数据端（D）决定&amp;quot;存什么&amp;quot;，时钟端决定&amp;quot;什么时候存&amp;quot;。时钟跳变的瞬间，D 的值被锁存下来，之后 D 再怎么变都不影响输出。&lt;strong&gt;这就是 1 位存储器。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&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
    S[&amp;#34;置位 S&amp;#34;] --&amp;gt; N1[&amp;#34;或非门 1&amp;#34;]
    R[&amp;#34;复位 R&amp;#34;] --&amp;gt; N2[&amp;#34;或非门 2&amp;#34;]
    N1 --&amp;gt;|&amp;#34;输出 Q&amp;#34;| OUT[&amp;#34;Q&amp;lt;br/&amp;gt;电路记住的状态&amp;#34;]
    N1 -.-&amp;gt;|&amp;#34;交叉反馈&amp;#34;| N2
    N2 -.-&amp;gt;|&amp;#34;交叉反馈&amp;#34;| N1
    N2 --&amp;gt;|&amp;#34;反相输出&amp;#34;| NQ[&amp;#34;Q 反&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;反馈结构还有一个直接的红利：&lt;strong&gt;计数器&lt;/strong&gt;。把触发器的反相输出接回自己的数据端，每来一个时钟脉冲它就翻转一次——输出波形的频率恰好是输入的一半。把 n 个这样的触发器串起来，每级的输出频率依次减半，把各级输出当作二进制的各位来读，你就得到一个 n 位二进制计数器，时钟每跳 2 的 n 次方次循环一轮。Petzold 在这里埋了一个很妙的呼应：前几章刚教会你读二进制数，这一章电路自己就&amp;quot;数&amp;quot;出了二进制数——&lt;strong&gt;数制不是抽象数学，是电路的自然行为&lt;/strong&gt;。计数器在后面的自动操作里会扮演&amp;quot;程序计数器&amp;quot;的角色：&lt;strong&gt;让电路自己记住&amp;quot;执行到第几条指令了&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感悟&lt;/strong&gt;：这一章是全书概念密度最高的一章，也是最值得慢读的一章。软件里&amp;quot;状态&amp;quot;是理所当然的（变量往那一放就有了），硬件里&amp;quot;状态&amp;quot;是要专门设计的——没有反馈回路就没有记忆，没有记忆就没有程序计数器、没有寄存器、没有一切。&amp;ldquo;存储&amp;quot;不是自然存在的，是用两个或非门互相咬尾咬出来的。理解这一点之后，&amp;ldquo;时序电路&amp;quot;和&amp;quot;组合电路&amp;quot;的分野、为什么信号完整性和时钟频率会成为硬件的命门，全都顺理成章。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七内存阵列与自动操作积木的最后两格&#34;&gt;七、内存阵列与自动操作：积木的最后两格&lt;/h2&gt;
&lt;h3 id=&#34;内存把触发器组织成可寻址的阵列&#34;&gt;内存：把触发器组织成可寻址的阵列&lt;/h3&gt;
&lt;p&gt;一个 D 触发器存 1 位。8 个一排存 1 个字节。接下来是纯粹的工程组织问题：怎么让&amp;quot;写入&amp;quot;动作精确命中阵列里的某一个字节？答案是我们今天无比熟悉的三件套：&lt;strong&gt;地址、译码器、选择器&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;16 位地址就是 16 根信号线，经译码器展开成 65536 条选择线，每条线唯一选中一个字节——64K 字节的 RAM 阵列。&lt;/li&gt;
&lt;li&gt;读数据时，选择器（一堆与门加或门）把被选中的字节送到输出；写数据时，只有被选中的那一排触发器的时钟端被放行。&lt;/li&gt;
&lt;li&gt;触发器太贵？书上接着讲了省继电器的折衷（锁存结构的演进、以及后来电容式 DRAM 的思路），但逻辑骨架不变：&lt;strong&gt;可寻址 = 译码 + 选择&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;自动操作让电路按清单自己干活&#34;&gt;自动操作：让电路按清单自己干活&lt;/h3&gt;
&lt;p&gt;全书第十七章&amp;quot;自动化&amp;quot;是积木封顶的地方，Petzold 的目标明确：造一台&lt;strong&gt;不用人盯着拨开关&lt;/strong&gt;的计算机器。零件都已备齐：加法器、RAM 阵列、计数器、触发器。&lt;/p&gt;
&lt;p&gt;在进入自动化之前，书里先让你体会了一把&amp;quot;手工操作&amp;quot;的滋味：要算两个数的乘积，你得自己坐在控制面板前，扳开关把被乘数写进 RAM、扳开关触发加法、把中间结果抄下来再写回去——每算一步人都在场。这个铺垫很关键，它让&amp;quot;自动化&amp;quot;的必要性变成了体感而不是概念：&lt;strong&gt;瓶颈从来不是电路算得慢，是人操作得慢。&lt;/strong&gt; 组装的思路是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;代码进内存&lt;/strong&gt;：把数据和指令都编码成字节放进 RAM——指令就是约定俗成的字节值，比如某个字节表示&amp;quot;Load&amp;rdquo;（把某地址的值装进累加器），某个表示&amp;quot;Add&amp;rdquo;，某个表示&amp;quot;Store&amp;quot;，某个表示&amp;quot;Jump&amp;quot;，某个表示&amp;quot;Halt&amp;quot;。书里这台机器的指令表还包括带进位加（Add with Carry）、带借位减（Subtract with Borrow）、零跳转（Jump If Zero）等变体——多字节运算靠进位标志串起来，这正是后来 x86 里 &lt;code&gt;ADC/SBB&lt;/code&gt; 这类指令的原型。&lt;strong&gt;指令集就是一张编码表&lt;/strong&gt;，回到第二章的主题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;累加器（accumulator）&lt;/strong&gt;：一个带锁存的寄存器，挂在加法器上，是所有运算的集散地——Load 装进来，Add 累加，Store 写回内存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动取指&lt;/strong&gt;：用一个计数器（程序计数器）依次生成地址，每执行完一条指令自动加一，取下一条。RAM 的一部分存指令、一部分存数据，同一套读写机构伺候两者。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跳转（Jump）&lt;/strong&gt;：Jump 指令把指定地址直接装进计数器，下一条指令就换地方取了。配上&amp;quot;条件跳转&amp;quot;（结果为零才跳、有进位才跳），程序有了分支；分支加循环，通用计算的全部要件就此凑齐。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    PC[&amp;#34;程序计数器&amp;lt;br/&amp;gt;自动加一&amp;#34;] --&amp;gt; RAM[&amp;#34;RAM 阵列&amp;lt;br/&amp;gt;指令与数据共处一室&amp;#34;]
    RAM --&amp;gt;|&amp;#34;取出指令字节&amp;#34;| DEC[&amp;#34;译码与控制&amp;lt;br/&amp;gt;查表: 这是什么操作?&amp;#34;]
    DEC --&amp;gt;|&amp;#34;Load / Store&amp;#34;| ADDR[&amp;#34;地址送往 RAM 读写&amp;#34;]
    DEC --&amp;gt;|&amp;#34;Add / Subtract&amp;#34;| ALU[&amp;#34;加法器 + 累加器&amp;#34;]
    ADDR --&amp;gt; ACC[&amp;#34;累加器&amp;#34;]
    ALU --&amp;gt; ACC
    ACC --&amp;gt;|&amp;#34;Store 写回&amp;#34;| RAM
    DEC --&amp;gt;|&amp;#34;Jump: 改写计数器&amp;#34;| PC
    DEC --&amp;gt;|&amp;#34;Halt&amp;#34;| STOP[&amp;#34;停机&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这就是冯·诺依曼结构的全部实质：&lt;strong&gt;指令和数据用同一种编码、住在同一片存储里，机器按计数器自动取指执行&lt;/strong&gt;。书里这台&amp;quot;玩具计算机&amp;quot;只有几条指令，但 Petzold 会算给你看：用 Load/Add/Store/Jump 这几条，循环累加做乘法、乘法套乘法做乘方，都能跑——慢，但没有做不到。&lt;strong&gt;通用性不是来自指令多，是来自跳转。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八从玩具机到真实世界总线微处理器与字符编码&#34;&gt;八、从玩具机到真实世界：总线、微处理器与字符编码&lt;/h2&gt;
&lt;p&gt;登顶之后，书的最后几章把我们熟悉的世界一件件接回来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;微处理器&lt;/strong&gt;：把前面搭的所有东西——运算器、寄存器、控制逻辑——集成进一枚芯片。Petzold 用 Intel 8080 和 Motorola 6800 做标本：你会发现它们的指令集就是自己搭的那台玩具机的豪华版，Load/Add/Store/Jump 一条不少，只是更多、更快、更小。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;总线（bus）&lt;/strong&gt;：处理器、内存、外设之间不能每两个都拉专线，于是大家共享一组公共信号线——地址总线、数据总线、控制总线——挂在上面的设备按地址认领属于自己的请求。总线本质上是&lt;strong&gt;用时间换空间的多路复用&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储层次&lt;/strong&gt;：书里的 RAM 阵列有个致命弱点——断电即失（触发器是电路，不是石刻）。于是需要更慢但持久的介质兜底：纸带、磁芯、磁盘、闪存，一代代接力。这一慢一快的落差逼出了存储层次结构：越快越贵越小的离 CPU 越近，越慢越便宜越大的垫底。&lt;strong&gt;寄存器、缓存、内存、磁盘的金字塔，不是设计出来的品味，是物理约束逼出来的妥协。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操作系统&lt;/strong&gt;：有了能自动执行指令的机器，就需要一个&amp;quot;先跑起来、再替别的程序干活&amp;quot;的程序——管理内存、管理设备、把文件抽象成字节流。从自动操作到 OS，差的只是一层软件约定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高级语言与汇编&lt;/strong&gt;：机器码太难写，于是有人写了一个把助记符翻译成机器码的程序——汇编器；再往上，一个把接近数学表达的文本翻译成机器码的程序——编译器。&lt;strong&gt;软件开始生产软件&lt;/strong&gt;，这是全书暗线的必然终点：每一层抽象一旦稳固，就变成下一层的建材。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定点与浮点&lt;/strong&gt;：要表示小数和极大极小的数，又得回到编码问题——约定哪些位是整数部分、哪些是小数部分（定点），或者约定一套&amp;quot;符号 + 尾数 + 指数&amp;quot;的科学计数法格式（浮点）。浮点的一切怪异（0.1 加 0.2 不等于 0.3）都是这套编码约定的直接推论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ASCII 与字符编码&lt;/strong&gt;：兜兜转转回到编码——计算机要处理文字，于是约定 7 位二进制数对应字符：95 个可打印字符加 33 个控制字符。这套约定和第一章的摩尔斯电码、布莱叶盲文在结构上一模一样：&lt;strong&gt;有限状态、人为约定、带转义&lt;/strong&gt;。全书在这里闭环：从编码出发，绕电路一大圈，最后又回到编码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第 2 版（2022）补充的在线交互演示值得单独提一句：书里搭的继电器、逻辑门、加法器都能在网页上拨开关亲眼看它跑。对&amp;quot;不亲手拨一遍就记不住&amp;quot;型学习者（比如我），这个配套资源把书的完成度又推高了一截。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九落到工程实践这本书改变了什么&#34;&gt;九、落到工程实践：这本书改变了什么&lt;/h2&gt;
&lt;p&gt;这本书不是工具书，但它在我日常工作中留下了几个具体的使用痕迹：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;排障时下意识&amp;quot;降层&amp;quot;。&lt;/strong&gt; 遇到玄学 bug，以前的第一反应是往上翻日志；现在会先问一句&amp;quot;这一层底下是什么&amp;quot;——字符乱码降一层是编码声明错位，再降一层是字节序；接口超时降一层是重试放大，再降一层是队头阻塞。Petzold 的书本质上训练的就是这种&amp;quot;每一层都是上一层的编码&amp;quot;的世界观：&lt;strong&gt;任何一层的魔法，降一层都是约定。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对&amp;quot;性能&amp;quot;有了物理直觉。&lt;/strong&gt; 行波进位加法器那一节是全书第一次讲&amp;quot;正确但慢&amp;quot;：进位要一级一级传，n 位加法器的延迟随位数线性增长，真实 CPU 用超前进位（carry lookahead）用空间换时间。这个&amp;quot;信号要在物理结构里传播&amp;quot;的直觉，换个尺度就是缓存行、内存墙、网络 RTT——&lt;strong&gt;所有性能问题归根结底都是&amp;quot;距离&amp;quot;问题&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设计编码和协议时更克制。&lt;/strong&gt; 布莱叶盲文的转义码、补码省掉减法器、ASCII 的控制字符，讲的都是同一件事：&lt;strong&gt;好的编码用最小状态数表达最大语义，余量留给扩展&lt;/strong&gt;。自己设计消息格式、状态机、错误码时，我会先问：状态数能不能再砍一半？需不需要留一个&amp;quot;转义&amp;quot;出口？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;给新人讲底层时有了最短路径。&lt;/strong&gt; 组里有同学问&amp;quot;程序到底怎么跑起来的&amp;quot;，与其扔一本组成原理，不如复述 Petzold 的积木链：手电筒 → 继电器 → 门 → 加法器 → 触发器 → 内存 → 取指执行。十五分钟讲完，对方的眼神从&amp;quot;敬畏&amp;quot;变成&amp;quot;就这？&amp;quot;——&lt;strong&gt;&amp;ldquo;就这？&amp;ldquo;就是这本书想达到的效果&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动手验证的习惯被强化。&lt;/strong&gt; 这本书的写法本身就是示范：每个论断都跟着一个&amp;quot;不信你自己连一遍&amp;quot;的电路。受它影响，我现在学任何底层概念（调度、GC、索引结构）都会逼自己写一个最小可跑的实现——写一个 50 行的玩具调度器，比读十篇调度原理文章管用。第 2 版的在线交互演示把这个门槛降到零：浏览器里就能拨开关看加法器跑，没有借口不动手。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十积木依赖速查表&#34;&gt;十、积木依赖速查表&lt;/h2&gt;
&lt;p&gt;把全书的搭积木链条压成一张表。每一行回答三个问题：这一层用什么搭的、获得了什么新能力、对应我们今天熟悉的什么。这张表也是我给新人讲底层的提纲——从上到下念一遍，就是计算机的简史。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层级&lt;/th&gt;
					&lt;th&gt;用什么搭的&lt;/th&gt;
					&lt;th&gt;获得的新能力&lt;/th&gt;
					&lt;th&gt;今天的对应物&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;编码（摩尔斯/布莱叶）&lt;/td&gt;
					&lt;td&gt;可区分的两种状态&lt;/td&gt;
					&lt;td&gt;任何信息都可表示&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;电磁铁吸动开关&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;CPU 里的门电路&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;半加器/全加器&lt;/td&gt;
					&lt;td&gt;XOR + AND + OR&lt;/td&gt;
					&lt;td&gt;电路做算术&lt;/td&gt;
					&lt;td&gt;ALU&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;行波进位加法器&lt;/td&gt;
					&lt;td&gt;8 个全加器级联&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;R-S/D 触发器&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;RAM 阵列&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;加法器 + RAM + 计数器 + 指令码&lt;/td&gt;
					&lt;td&gt;不用人盯的通用计算&lt;/td&gt;
					&lt;td&gt;CPU + 冯·诺依曼结构&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;微处理器/OS/总线&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;看这张表有一个额外的收获：&lt;strong&gt;每一层都极其简单，复杂的只有组合方式。&lt;/strong&gt; 这也是面对任何庞大系统时该有的心态——别怕它大，找到它的&amp;quot;继电器层&amp;rdquo;，从那里开始读。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;本篇小结&#34;&gt;本篇小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;全书主线一句话：&lt;strong&gt;计算机没有魔法，一切都建立在二元选择与电路上&lt;/strong&gt;；其余全是组合与封装。&lt;/li&gt;
&lt;li&gt;编码的本质是信息表示：摩尔斯电码、布莱叶盲文、ASCII 是同一结构的三个实例——有限状态、人为约定、转义扩展。&lt;/li&gt;
&lt;li&gt;继电器是地基零件：用电控制的开关，让电路第一次能自己操作自己。&lt;/li&gt;
&lt;li&gt;布尔代数给开关赋予逻辑：与、或、非三件套搭出一切判断；异或加与门就是半加器，两个半加器加或门就是全加器，八个级联就是行波进位加法器；补码让减法复用加法。&lt;/li&gt;
&lt;li&gt;反馈让电路有了记忆：反相器咬尾成振荡器（时钟），或非门交叉耦合出 R-S 触发器，加时钟成 D 触发器（1 位存储），串联成计数器（程序计数器）。&lt;/li&gt;
&lt;li&gt;自动操作的全部要件：指令编码表 + 累加器 + 程序计数器 + 条件跳转；指令与数据同住一片 RAM，就是冯·诺依曼结构。&lt;/li&gt;
&lt;li&gt;往上走的每一层——微处理器、总线、操作系统、高级语言——都只是同一套积木的规模化封装；积木依赖速查表见第十节。&lt;/li&gt;
&lt;li&gt;本书的副产物是一种读系统的方法论：面对任何庞大系统，找到它的&amp;quot;继电器层&amp;rdquo;，从那里开始读。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这本书我常推荐给两类人：刚入行想补计算机地基的，和写了很多年上层代码、开始好奇底下发生了什么的。对前者它是最平滑的入门路径，对后者它是一次&amp;quot;祛魅&amp;quot;——读完你会发现，所谓底层，不过是一群被理解得很透的简单东西。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Charles Petzold, &lt;em&gt;Code: The Hidden Language of Computer Hardware and Software&lt;/em&gt;, Microsoft Press, 1999（第 1 版）/ 2022（第 2 版，含 codehiddenlanguage.com 在线交互演示）。&lt;/li&gt;
&lt;li&gt;中译本：《编码：隐匿在计算机软硬件背后的语言》，左飞、薛佟佟 译，电子工业出版社。&lt;/li&gt;
&lt;li&gt;Claude E. Shannon, &amp;ldquo;A Symbolic Analysis of Relay and Switching Circuits&amp;rdquo;, 1938（书中第四章主线的原始论文，继电器与布尔代数的联姻）。&lt;/li&gt;
&lt;li&gt;George Boole, &lt;em&gt;An Investigation of the Laws of Thought&lt;/em&gt;, 1854（布尔代数的原始出处）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《SICP》笔记：计算机程序的构造和解释，以及编程世界的“无所住而生其心”</title>
      <link>https://ftmao.com/blog/bookstore/sicp-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/sicp-book-notes/</guid>
      <description>&lt;h1 id=&#34;sicp笔记计算机程序的构造和解释以及编程世界的无所住而生其心&#34;&gt;《SICP》笔记：计算机程序的构造和解释，以及编程世界的“无所住而生其心”&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：写过几年代码、用过不止一门语言、想知道“编程这件事的本质到底是什么”的程序员。&lt;br&gt;
参考书籍：Harold Abelson &amp;amp; Gerald Jay Sussman, &lt;em&gt;Structure and Interpretation of Computer Programs&lt;/em&gt;（SICP，中译《计算机程序的构造和解释》）&lt;br&gt;
阅读目标：不求逐章复述，而是提炼一条主线——&lt;strong&gt;过程与数据的对偶&lt;/strong&gt;，看清抽象、求值、解释器这三件事如何环环相扣。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面为什么这本书被称为-cs-的佛经&#34;&gt;0. 写在前面：为什么这本书被称为 CS 的“佛经”&lt;/h2&gt;
&lt;p&gt;SICP 是 MIT 的传奇教材。1980 年代到 2000 年代，MIT 的计算机导论课 6.001 用它做教材：第一堂课不教语法，直接写求平方根的牛顿法；第一学期结束，学生要&lt;strong&gt;用 Scheme 写出一个 Scheme 解释器&lt;/strong&gt;。这门课出了名地难，也出了名地改变人——无数程序员回忆它是“第一次觉得编程是一门手艺，而不是一门手艺活”的时刻。&lt;/p&gt;
&lt;p&gt;几个背景事实，帮你定位这本书：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;书出版于 1985 年（第一版），语言用的是 Lisp 方言 &lt;strong&gt;Scheme&lt;/strong&gt;。Lisp 诞生于 1958 年（John McCarthy），是现存第二老的高级语言（第一是 Fortran，1957 年）。&lt;/li&gt;
&lt;li&gt;2008 年 MIT 把 6.001 换成了 Python 课程。Sussman 本人的解释是：现在工程师更多是在“把不理解的黑盒零件拼起来”，而不是从零构造系统——时代变了。但 SICP 的地位反而因此更稳固：它不再是一门课的教材，而是程序员自我修炼的经典。&lt;/li&gt;
&lt;li&gt;Paul Graham（Y Combinator 创始人）1995 年用 Lisp 写了 Viaweb——史上最早的 Web 应用之一，1998 年被雅虎以约 4900 万美元收购。他后来写《黑客与画家》，反复鼓吹 Lisp 的威力，把 SICP 式的抽象能力说成是 Viaweb 能以 3 个人对抗几十人团队的“秘密武器”。这个故事后面还会用到。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这本书为什么这么神？一句话：它讲的不是“怎么写代码”，而是**“计算是什么”**。全书有一条暗线：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;计算的本质 = 过程（process）与数据（data）的对偶。程序在二者之间不断转换：数据可以被当作过程执行，过程也可以被当作数据传递。到书的结尾，我们用一个程序（求值器）把语言自己解释了一遍——这就是编程世界的“言语道断，心行处灭”。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个博客另有佛学笔记，读完全文你会发现，SICP 的气质确实和《金刚经》那句“应无所住而生其心”隐隐相通：&lt;strong&gt;不执着于任何固定的表示（无所住），才能自由地在过程与数据之间切换（生其心）&lt;/strong&gt;。但先把技术内容讲扎实，佛的部分放最后。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-一句话理解-sicp抽象的三层结构&#34;&gt;1. 一句话理解 SICP：抽象的三层结构&lt;/h2&gt;
&lt;p&gt;SICP 开篇就给出了“什么是编程语言”的定义。任何一门强大的语言，必须提供三样东西：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基本表达（primitive expressions）&lt;/strong&gt;：语言提供的最小积木，比如数字 &lt;code&gt;2&lt;/code&gt;、运算符 &lt;code&gt;+&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合手段（means of combination）&lt;/strong&gt;：把简单东西拼成复杂东西，比如 &lt;code&gt;(+ 2 3)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;抽象手段（means of abstraction）&lt;/strong&gt;：给复杂东西命名，然后像用基本积木一样用它，比如 &lt;code&gt;(define (square x) (* x x))&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一句话理解全书：&lt;strong&gt;SICP 就是在“过程”和“数据”两个战场上，反复演示这三层结构怎么玩。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 1 章：过程的抽象（用函数表达计算过程）。&lt;/li&gt;
&lt;li&gt;第 2 章：数据的抽象（用构造函数/选择函数把表示藏起来）。&lt;/li&gt;
&lt;li&gt;第 3 章：模块化、对象与状态（引入赋值后，代换模型失效，必须换环境模型）。&lt;/li&gt;
&lt;li&gt;第 4 章：元语言抽象（写一个求值器，用语言解释语言）。&lt;/li&gt;
&lt;li&gt;第 5 章：寄存器机器（解释器如何在真实机器上跑起来）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;读 SICP 的正确姿势，不是记知识点，而是看作者怎么&lt;strong&gt;用同一把刀（三层抽象）切开五个不同的问题&lt;/strong&gt;。这把刀，就是这本书真正想传给你的东西。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-过程抽象牛顿法开方sicp-的第一个当头棒喝&#34;&gt;2. 过程抽象：牛顿法开方，SICP 的第一个“当头棒喝”&lt;/h2&gt;
&lt;p&gt;6.001 第一堂课就讲平方根。这不是随便选的例子——它完整演示了“一个数学定义（声明性知识）”和“一个计算过程（操作性知识）”之间的鸿沟，而弥合这个鸿沟，正是编程的本质。&lt;/p&gt;
&lt;h3 id=&#34;21-数学定义--计算过程&#34;&gt;2.1 数学定义 ≠ 计算过程&lt;/h3&gt;
&lt;p&gt;数学上，$\sqrt{x}$ 的定义是：“满足 $y^2 = x$ 且 $y \ge 0$ 的那个 $y$”。这个定义完美，但&lt;strong&gt;不能算&lt;/strong&gt;——它没告诉你怎么找到这个 $y$。&lt;/p&gt;
&lt;p&gt;牛顿法（Newton&amp;rsquo;s method，SICP 中归为 Heron of Alexandria 的巴比伦法特例）给出的是一个过程：从任意猜测 $y$ 出发，反复取 $y$ 和 $x/y$ 的平均值作为新猜测，直到足够接近：&lt;/p&gt;
&lt;p&gt;$$
y_{n+1} = \frac{y_n + x / y_n}{2}
$$&lt;/p&gt;
&lt;h3 id=&#34;22-用小数字完整走一遍求-sqrt2&#34;&gt;2.2 用小数字完整走一遍：求 $\sqrt{2}$&lt;/h3&gt;
&lt;p&gt;从猜测 $y_0 = 1$ 开始：&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;当前猜测 $y_n$&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;$x / y_n$&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;0&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;1&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;2&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;(1 + 2) / 2 = &lt;strong&gt;1.5&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;1&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;1.5&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;1.3333&amp;hellip;&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;(1.5 + 1.3333) / 2 = &lt;strong&gt;1.4167&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;2&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;1.4167&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;1.4118&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;(1.4167 + 1.4118) / 2 = &lt;strong&gt;1.4142&lt;/strong&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;三步就到 1.4142，误差已经小于 $10^{-4}$。真实值是 1.41421356&amp;hellip;。收敛速度是平方级的——每迭代一次，有效位数大约翻倍。&lt;/p&gt;
&lt;h3 id=&#34;23-scheme-实现嵌套定义把复杂性藏起来&#34;&gt;2.3 Scheme 实现：嵌套定义把复杂性藏起来&lt;/h3&gt;
&lt;p&gt;SICP 把牛顿法写成一组互相调用的过程，最后用 &lt;code&gt;define&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;define &lt;/span&gt;(sqrt x)
&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;good-enough?&lt;/span&gt; guess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&amp;lt; (abs (- (&lt;span style=&#34;color:#a6e22e&#34;&gt;square&lt;/span&gt; guess) x)) &lt;span style=&#34;color:#ae81ff&#34;&gt;0.001&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;improve&lt;/span&gt; guess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#a6e22e&#34;&gt;average&lt;/span&gt; guess (/ x guess)))
&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;sqrt-iter&lt;/span&gt; guess)
&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;(&lt;span style=&#34;color:#a6e22e&#34;&gt;good-enough?&lt;/span&gt; guess)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        guess
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#a6e22e&#34;&gt;sqrt-iter&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;improve&lt;/span&gt; guess))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#a6e22e&#34;&gt;sqrt-iter&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户只看到 &lt;code&gt;(sqrt 2)&lt;/code&gt;，&lt;strong&gt;不用知道里面有迭代、有容忍度、有牛顿公式&lt;/strong&gt;。这就是过程抽象：&lt;strong&gt;把一个过程当作黑盒，用名字调用它。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;辅助过程 &lt;code&gt;good-enough?&lt;/code&gt;、&lt;code&gt;improve&lt;/code&gt;、&lt;code&gt;sqrt-iter&lt;/code&gt; 定义在 &lt;code&gt;sqrt&lt;/code&gt; 内部，外面的代码根本看不见它们，而且它们直接共享参数 &lt;code&gt;x&lt;/code&gt;，不用层层传递。&lt;/li&gt;
&lt;li&gt;这是 SICP 给新人的第一棒：&lt;strong&gt;函数不是用来“复用代码”的，是用来“命名计算过程、压制细节”的。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;避坑提示：SICP 练习 1.7 指出，&lt;code&gt;good-enough?&lt;/code&gt; 用固定阈值 &lt;code&gt;0.001&lt;/code&gt; 对很小的数（如 &lt;code&gt;sqrt 0.0001&lt;/code&gt;）和很大的数（如 &lt;code&gt;sqrt 1e13&lt;/code&gt;）都会出错——前者精度不够，后者因为浮点精度问题可能不收敛。工程上的修法是判断“新旧猜测的相对变化量是否足够小”，而不是绝对误差。这个练习至今仍是理解“数值代码里的坑”的最佳入门。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-高阶函数sumintegral-通用模板抽象出模式本身&#34;&gt;3. 高阶函数：sum/integral 通用模板，抽象出“模式”本身&lt;/h2&gt;
&lt;h3 id=&#34;31-三个长得像的求和&#34;&gt;3.1 三个长得像的求和&lt;/h3&gt;
&lt;p&gt;SICP 第 1 章的下一个重点：数学家早就发现一堆求和过程长得几乎一样，只是“每一项怎么算”和“下一项怎么来”不同：&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;; 从 a 加到 b&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;sum-integers&lt;/span&gt; a b)
&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;(&amp;gt; a b) &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; (+ a (&lt;span style=&#34;color:#a6e22e&#34;&gt;sum-integers&lt;/span&gt; (+ a &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) b)))))
&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;; 从 a 的立方加到 b 的立方&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;sum-cubes&lt;/span&gt; a b)
&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;(&amp;gt; a b) &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; (+ (&lt;span style=&#34;color:#a6e22e&#34;&gt;cube&lt;/span&gt; a) (&lt;span style=&#34;color:#a6e22e&#34;&gt;sum-cubes&lt;/span&gt; (+ a &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) b)))))
&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;; 莱布尼茨级数求 π：1/1 - 1/3 + 1/5 - ...&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;pi-sum&lt;/span&gt; a b)
&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;(&amp;gt; a b) &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; (+ (/ &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; (* a (+ a &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;))) (&lt;span style=&#34;color:#a6e22e&#34;&gt;pi-sum&lt;/span&gt; (+ a &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;) b))))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;三个过程的骨架一模一样。SICP 的做法：把骨架抽出来，&lt;strong&gt;让“每一项怎么算（term）”和“下一项怎么来（next）”变成参数&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; term a next b)
&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;(&amp;gt; a b)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#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 style=&#34;color:#a6e22e&#34;&gt;term&lt;/span&gt; a) (&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; term (&lt;span style=&#34;color:#a6e22e&#34;&gt;next&lt;/span&gt; a) next b))))
&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;; π 级数就剩两行&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;pi-sum&lt;/span&gt; a b)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;lambda &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;) (/ &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; (* x (+ x &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;       a
&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;lambda &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;) (+ x &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       b))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;sum&lt;/code&gt; 就是一个&lt;strong&gt;高阶函数（higher-order procedure）&lt;/strong&gt;：以过程为参数、以过程为返回值的过程。&lt;/p&gt;
&lt;h3 id=&#34;32-一句话理解高阶函数&#34;&gt;3.2 一句话理解高阶函数&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;高阶函数 = 把“算法中的变化点”变成参数。你传进去的不是数据，是行为。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果你觉得眼熟，那就对了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你在 JavaScript 里写的 &lt;code&gt;array.map(fn)&lt;/code&gt;、&lt;code&gt;array.filter(fn)&lt;/code&gt;、&lt;code&gt;array.reduce(fn, init)&lt;/code&gt;，就是 &lt;code&gt;sum&lt;/code&gt; 的亲戚。&lt;/li&gt;
&lt;li&gt;Python 的 &lt;code&gt;sorted(lst, key=fn)&lt;/code&gt;、Java 的 &lt;code&gt;Comparator&lt;/code&gt;，传的都是“行为参数”。&lt;/li&gt;
&lt;li&gt;数值积分 $\int_a^b f \approx \sum f(x),dx$ 在 SICP 里就是 &lt;code&gt;sum&lt;/code&gt; 换一套 &lt;code&gt;term/next&lt;/code&gt; 的事——积分器和求和器共享同一个骨架，这在数学上是黎曼和，在代码上就是高阶函数。&lt;/li&gt;
&lt;li&gt;设计模式里的 Strategy、模板方法，本质都是“手写的高阶函数”——语言不支持把函数当值传递，就只能用类和接口模拟。这也是为什么 Peter Norvig 那篇著名文章指出：GoF 23 个设计模式里有 16 个在有头等函数的语言里“要么消失、要么简化到看不见”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;33-lambda-与-let不命名也是自由&#34;&gt;3.3 lambda 与 let：不命名，也是自由&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;(lambda (x) (* x x))&lt;/code&gt; 造出一个匿名过程。SICP 特意强调：能给过程命名（&lt;code&gt;define&lt;/code&gt;）当然好，但&lt;strong&gt;能不命名也同样重要&lt;/strong&gt;——很多小过程只用一次，为它发明一个名字反而污染命名空间。&lt;/p&gt;
&lt;p&gt;工程对照：这就是你写 &lt;code&gt;list.sort(key=lambda x: x.age)&lt;/code&gt; 时干的事。&lt;code&gt;let&lt;/code&gt; 则只是 lambda 的语法糖：&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;let &lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) (&lt;span style=&#34;color:#a6e22e&#34;&gt;b&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;  (+ a b))
&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;; 等价于&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;lambda &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; b) (+ a b)) &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;一句话理解：&lt;code&gt;let&lt;/code&gt; 不是“定义变量的特殊语法”，而是“立刻调用一个匿名函数”。变量绑定就是函数参数绑定。这个视角在第 4 章写求值器时会回报你——&lt;code&gt;let&lt;/code&gt; 根本不用实现，翻译成 lambda 就完了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-数据抽象有理数例子和抽象屏障这道墙&#34;&gt;4. 数据抽象：有理数例子，和“抽象屏障”这道墙&lt;/h2&gt;
&lt;h3 id=&#34;41-用之前先假装它存在&#34;&gt;4.1 用之前，先假装它存在&lt;/h3&gt;
&lt;p&gt;SICP 第 2 章开篇的方法论非常反直觉：&lt;strong&gt;先有“愿望思维”（wishful thinking）——假设有理数的构造函数和选择函数已经存在，先把用它们的上层代码写完，再回头实现它们。&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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:#75715e&#34;&gt;; (make-rat n d)  → 构造一个 n/d 的有理数&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;; (numer x)       → 取分子&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;; (denom x)       → 取分母&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;; 于是加法直接写出来：&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;add-rat&lt;/span&gt; x y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#a6e22e&#34;&gt;make-rat&lt;/span&gt; (+ (* (&lt;span style=&#34;color:#a6e22e&#34;&gt;numer&lt;/span&gt; x) (&lt;span style=&#34;color:#a6e22e&#34;&gt;denom&lt;/span&gt; y))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;               (* (&lt;span style=&#34;color:#a6e22e&#34;&gt;numer&lt;/span&gt; y) (&lt;span style=&#34;color:#a6e22e&#34;&gt;denom&lt;/span&gt; x)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            (* (&lt;span style=&#34;color:#a6e22e&#34;&gt;denom&lt;/span&gt; x) (&lt;span style=&#34;color:#a6e22e&#34;&gt;denom&lt;/span&gt; y))))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;用小数字验证：$\frac{1}{2} + \frac{1}{3} = \frac{1\times3 + 1\times2}{2\times3} = \frac{5}{6}$。对。&lt;/p&gt;
&lt;p&gt;关键点：&lt;code&gt;add-rat&lt;/code&gt; 的代码里&lt;strong&gt;没有任何一处依赖“有理数内部怎么存”&lt;/strong&gt;。它只依赖 &lt;code&gt;make-rat&lt;/code&gt;/&lt;code&gt;numer&lt;/code&gt;/&lt;code&gt;denom&lt;/code&gt; 这三个接口承诺的行为。这条接口，SICP 称之为&lt;strong&gt;抽象屏障（abstraction barrier）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;42-屏障之下表示随便换&#34;&gt;4.2 屏障之下，表示随便换&lt;/h3&gt;
&lt;p&gt;因为有屏障，下面这些是&lt;strong&gt;可以在不改变 &lt;code&gt;add-rat&lt;/code&gt; 一行的前提下&lt;/strong&gt;完成的修改：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存原始分子分母，约分留给打印时做；&lt;/li&gt;
&lt;li&gt;在构造时就约分（SICP 练习 2.1：用 &lt;code&gt;gcd&lt;/code&gt; 化简）；&lt;/li&gt;
&lt;li&gt;保证分母恒为正，把符号统一挪到分子。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是数据抽象的工程价值：&lt;strong&gt;表示的修改被关进笼子，使用方无感知。&lt;/strong&gt; 你在工程里天天享受这个红利：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;java.util.List&lt;/code&gt; 背后是 &lt;code&gt;ArrayList&lt;/code&gt; 还是 &lt;code&gt;LinkedList&lt;/code&gt;，调用方不该关心；&lt;/li&gt;
&lt;li&gt;HTTP API 的响应字段内部存 JSON 还是 protobuf，客户端不该关心；&lt;/li&gt;
&lt;li&gt;数据库的存储引擎从 MyISAM 换 InnoDB，SQL 不变。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一句话理解：数据抽象不是“封装”这个 OOP 词汇的专利，它是更根本的原则——&lt;strong&gt;“用一组行为约定代替对表示的直接依赖”&lt;/strong&gt;。Java 的接口、C 的 opaque pointer、Rust 的 trait，全是这道墙的不同砌法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-conscarcdr-与闭包性质一对盒子造出整个数据世界&#34;&gt;5. cons/car/cdr 与闭包性质：一对盒子造出整个数据世界&lt;/h2&gt;
&lt;h3 id=&#34;51-唯一的胶水cons&#34;&gt;5.1 唯一的胶水：cons&lt;/h3&gt;
&lt;p&gt;Scheme 的数据组合手段只有一个：&lt;code&gt;cons&lt;/code&gt;。&lt;code&gt;(cons 1 2)&lt;/code&gt; 造出一个序对（pair），&lt;code&gt;car&lt;/code&gt; 取左，&lt;code&gt;cdr&lt;/code&gt; 取右。就这一个盒子，SICP 搭出了列表、树、表、集合……全部数据结构。&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(cons &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; (cons &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; (cons &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; nil)))   &lt;span style=&#34;color:#75715e&#34;&gt;; 这就是列表 (1 2 3)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(cons (cons &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;) (cons &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;))     &lt;span style=&#34;color:#75715e&#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;为什么够了？因为 SICP 给出了&lt;strong&gt;闭包性质（closure property）&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;组合手段满足闭包性质 = 组合出来的东西，本身还能继续被同样的手段组合。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;cons 出来的序对，可以再被 cons。这一句话是数据世界的“递归基”：列表是嵌套的 cons，树是嵌套的 cons，任何层级结构都是嵌套的 cons。&lt;/p&gt;
&lt;p&gt;工程对照：HTML 的 DOM 树满足闭包性质（元素套元素）；Unix 管道满足闭包性质（任何命令的输出都能进下一个命令的输入）；JSON 满足闭包性质（对象里套对象）。&lt;strong&gt;满足闭包性质的组合手段，组合成本不随复杂度增长&lt;/strong&gt;——这是 SICP 对“为什么 Lisp 和 Unix 都能小核心、大生态”的统一解释。&lt;/p&gt;
&lt;h3 id=&#34;52-最炸裂的一课cons-可以不用数据实现&#34;&gt;5.2 最炸裂的一课：cons 可以不用数据实现&lt;/h3&gt;
&lt;p&gt;SICP 2.1.3 有一个让无数人第一次头皮发麻的段落：&lt;code&gt;cons&lt;/code&gt;、&lt;code&gt;car&lt;/code&gt;、&lt;code&gt;cdr&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;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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;define &lt;/span&gt;(cons x y)
&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;lambda &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;) (&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt; x y)))
&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;define &lt;/span&gt;(car z)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;lambda &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; q) p)))
&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;define &lt;/span&gt;(cdr z)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#a6e22e&#34;&gt;z&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;lambda &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; q) q)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;走一遍 &lt;code&gt;(car (cons 1 2))&lt;/code&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;(cons 1 2)&lt;/code&gt; 返回一个匿名函数，它等着别人告诉它“你要左还是要右”。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(car z)&lt;/code&gt; 把 &lt;code&gt;(lambda (p q) p)&lt;/code&gt; 塞给它，意思是“给我左边那个”。&lt;/li&gt;
&lt;li&gt;结果：&lt;code&gt;1&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这意味着什么？&lt;strong&gt;“数据”和“过程”的边界是虚构的。&lt;/strong&gt; 一个序对可以完全用行为（“当被问到时左右各报什么”）来定义，不占用任何存储意义上的“盒子”。这就是第 0 节说的对偶性第一次显形：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;数据是被冻住的过程；过程是活过来的数据。&lt;/strong&gt; 佛家说“色即是空，空即是色”，SICP 说“数据即过程，过程即数据”——结构上是同一个句式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;（历史彩蛋：这套“用函数编码数据”的玩法不是 SICP 发明的，是 Alonzo Church 1930 年代 λ 演算里的 Church 编码——连自然数都可以用纯函数定义。SICP 把它拿来当头棒喝用。）&lt;/p&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;p&gt;求值一个组合式，最朴素的模型是&lt;strong&gt;代换模型（substitution model）&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&gt;(square (+ 2 3))
→ (square 5)
→ (* 5 5)
→ 25&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这个模型简单、优雅，而且可以直接证明程序正确性（这正是函数式编程“可推导”的理论基础）。SICP 用它讲清了**应用序（先算实参再代入）&lt;strong&gt;与&lt;/strong&gt;正则序（先代入再按需算）**的区别——后者就是第 7 节惰性求值的种子。&lt;/p&gt;
&lt;h3 id=&#34;62-赋值一来代换模型当场死亡&#34;&gt;6.2 赋值一来，代换模型当场死亡&lt;/h3&gt;
&lt;p&gt;第 3 章引入 &lt;code&gt;set!&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;define &lt;/span&gt;balance &lt;span style=&#34;color:#ae81ff&#34;&gt;100&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;define &lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;withdraw&lt;/span&gt; amount)
&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;set! &lt;/span&gt;balance (- balance amount))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  balance)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;(withdraw 25)&lt;/code&gt; 得 75，再 &lt;code&gt;(withdraw 25)&lt;/code&gt; 得 50。同样的表达式、同样的参数，两次结果不同——代换模型的基石“相同表达式有相同值”（引用透明性）碎了。表达式不再是值的替身，&lt;strong&gt;时间&lt;/strong&gt;进入了程序：值取决于“你什么时候问”。&lt;/p&gt;
&lt;h3 id=&#34;63-环境模型变量不再是值而是在某个环境里的格子&#34;&gt;6.3 环境模型：变量不再“是”值，而是“在”某个环境里的格子&lt;/h3&gt;
&lt;p&gt;替代方案是&lt;strong&gt;环境模型（environment model）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;环境 = 一串框架（frame）&lt;/strong&gt;，每个框架是一张“变量名 → 值”的表，框架之间用指针连成链，最后终结于全局环境。&lt;/li&gt;
&lt;li&gt;变量求值 = 从当前框架往外一层层找，找到第一个绑定。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lambda&lt;/code&gt; 求值 = 造出一个过程对象，它携带两样东西：&lt;strong&gt;参数+函数体&lt;/strong&gt;，和&lt;strong&gt;一个指向定义时环境的指针&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;调用过程 = 建一个新框架，把实参绑进去，新框架的外层指针指向过程对象里存的那个环境。&lt;/li&gt;
&lt;/ul&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
    GE[&amp;#34;全局环境&amp;lt;br/&amp;gt;balance = 50&amp;lt;br/&amp;gt;withdraw = 过程对象&amp;#34;]
    PO[&amp;#34;过程对象&amp;lt;br/&amp;gt;参数: amount&amp;lt;br/&amp;gt;函数体: set! balance ...&amp;lt;br/&amp;gt;环境指针 ──→&amp;#34;]
    E1[&amp;#34;框架 E1&amp;lt;br/&amp;gt;amount = 25&amp;#34;]
    PO --&amp;gt; GE
    E1 --&amp;gt;|&amp;#34;外层指针&amp;#34;| GE
    GE -.-&amp;gt;|&amp;#34;调用 withdraw 25 时创建&amp;#34;| E1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;为什么函数式程序员迷恋“纯函数、不可变数据”？SICP 给了最诚实的答案：&lt;strong&gt;不是因为美，而是因为一旦引入赋值，你从此失去了代换模型这个思维工具，必须用环境模型这种更贵的心智模型去追每一个变量的历史。&lt;/strong&gt; 工程对照：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redux 的“单一不可变 state 树”，本质是在 JS 里人工维持代换模型可用；&lt;/li&gt;
&lt;li&gt;Rust 的所有权与借用检查，是编译器帮你控制“谁在什么时候能改什么”；&lt;/li&gt;
&lt;li&gt;并发 bug 的根源几乎都是“共享可变状态”——环境模型里那条指针链，到了多线程就是一场灾难。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;避坑提示：闭包捕获的是&lt;strong&gt;环境（变量的格子）而不是当时的值&lt;/strong&gt;。经典面试题——Python 里 &lt;code&gt;fns = [lambda: i for i in range(3)]&lt;/code&gt;，三个函数都返回 2——就是这句话的具象化。JS 的 &lt;code&gt;var&lt;/code&gt; 循环、Go 的 goroutine 循环变量（Go 1.22 之前的坑）都是同一原理。学完环境模型，这类 bug 你一眼看穿。&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-问题状态和赋值污染了一切能不要吗&#34;&gt;7.1 问题：状态和赋值污染了一切，能不要吗？&lt;/h3&gt;
&lt;p&gt;第 3 章结尾 SICP 问了一个漂亮的反问：我们引入赋值是为了建模“随时间变化的状态”（比如银行账户余额）。但换个视角——&lt;strong&gt;余额不是“一个会变的格子”，而是“一个无穷序列：每次交易后的全部余额历史”&lt;/strong&gt;。变化的东西，可以被重新表示成一个&lt;strong&gt;不变但无穷&lt;/strong&gt;的东西。&lt;/p&gt;
&lt;p&gt;问题是：无穷的序列怎么在有限的内存里表示？答案：&lt;strong&gt;只在你看它的时候才算它&lt;/strong&gt;——这就是&lt;strong&gt;流（stream）&lt;strong&gt;和&lt;/strong&gt;惰性求值（lazy evaluation）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;72-流的实现cons-的另一半推迟到以后&#34;&gt;7.2 流的实现：cons 的另一半推迟到以后&lt;/h3&gt;
&lt;p&gt;流的全部魔法在一对操作里（SICP 用 &lt;code&gt;cons-stream&lt;/code&gt; 宏和 &lt;code&gt;delay&lt;/code&gt;/&lt;code&gt;force&lt;/code&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(cons-stream a b)&lt;/code&gt;：&lt;code&gt;a&lt;/code&gt; 立刻算，&lt;code&gt;b&lt;/code&gt; &lt;strong&gt;不&lt;/strong&gt;算，包成一个“承诺”（promise）存起来；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(stream-cdr s)&lt;/code&gt;：兑现那个承诺（force），真正算出下一截。&lt;/li&gt;
&lt;/ul&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-scheme&#34; data-lang=&#34;scheme&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;define &lt;/span&gt;fibs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#a6e22e&#34;&gt;cons-stream&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 style=&#34;color:#a6e22e&#34;&gt;cons-stream&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:#a6e22e&#34;&gt;add-streams&lt;/span&gt; fibs (&lt;span style=&#34;color:#a6e22e&#34;&gt;stream-cdr&lt;/span&gt; fibs)))))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;读法：&lt;code&gt;fibs&lt;/code&gt; = 0 开头，接着 1，接着是“fibs 和它自己的错位相加”。用小数字走一遍错位相加：&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&gt;fibs:           0, 1, 1, 2, 3, 5,  8, ...
(stream-cdr fibs): 1, 1, 2, 3, 5, 8, 13, ...
对应位相加:        1, 2, 3, 5, 8, 13, ...&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;从第 3 位开始，每一位都是前两段对应位相加的结果——正是斐波那契定义。但注意：&lt;strong&gt;这个流并没有预先存在，是你每取一个元素，它才长出一个元素&lt;/strong&gt;。定义是递归的，数据却是按需生长的。&lt;/p&gt;
&lt;h3 id=&#34;73-流处理管道信号处理式的程序结构&#34;&gt;7.3 流处理管道：信号处理式的程序结构&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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    G[&amp;#34;生成器&amp;lt;br/&amp;gt;无穷整数流&amp;lt;br/&amp;gt;2, 3, 4, 5, ...&amp;#34;] --&amp;gt; F1[&amp;#34;过滤器&amp;lt;br/&amp;gt;筛掉被 2 整除的&amp;#34;]
    F1 --&amp;gt; F2[&amp;#34;过滤器&amp;lt;br/&amp;gt;筛掉被 3 整除的&amp;#34;]
    F2 --&amp;gt; F3[&amp;#34;过滤器&amp;lt;br/&amp;gt;筛掉被 5 整除的&amp;#34;]
    F3 --&amp;gt; OUT[&amp;#34;输出&amp;lt;br/&amp;gt;素数流&amp;lt;br/&amp;gt;2, 3, 5, 7, 11, ...&amp;#34;]
    style OUT fill:#e8f5e9&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;上图就是 SICP 的&lt;strong&gt;埃氏筛素数流&lt;/strong&gt;：每个素数一旦露面，就派生出一个新过滤器挂在管道上，把它的倍数全部筛掉。整个程序是对“全体素数”这个无穷集合的操作，但任意时刻内存里只有用到的那一小段。&lt;/p&gt;
&lt;p&gt;工程对照，这个思想你今天天天在用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unix 管道&lt;/strong&gt;：&lt;code&gt;cat access.log | grep 500 | wc -l&lt;/code&gt;——三段程序用惰性流串起来，谁也不会把整个日志读进内存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Java 8 Streams / RxJava / Rust 的 Iterator&lt;/strong&gt;：&lt;code&gt;stream.filter(...).map(...).collect()&lt;/code&gt;，中间操作全部惰性，终端操作才触发计算。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python 生成器&lt;/strong&gt;：&lt;code&gt;yield&lt;/code&gt; 就是 &lt;code&gt;delay&lt;/code&gt;，&lt;code&gt;next()&lt;/code&gt; 就是 &lt;code&gt;force&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大数据框架&lt;/strong&gt;（Flink/Spark Streaming）：整个流处理行业就是 SICP 第 3.5 节的工业化。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一句话理解：流 = &lt;strong&gt;用“无穷的静态结构”替代“变化的状态”&lt;/strong&gt;。赋值把“值随时间变”摊给了程序员的心智；流把同样的现象表示成一个数学对象，时间被折叠进“取到第几个元素”这个参数里。这是 FP 社区对抗复杂性的核心武器之一。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-元循环求值器用语言解释语言全书的高潮&#34;&gt;8. 元循环求值器：用语言解释语言，全书的高潮&lt;/h2&gt;
&lt;h3 id=&#34;81-为什么要写求值器&#34;&gt;8.1 为什么要写求值器&lt;/h3&gt;
&lt;p&gt;第 4 章，SICP 做了一件别的教材不敢在入门书里做的事：&lt;strong&gt;用 Scheme 写一个 Scheme 解释器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这个解释器叫&lt;strong&gt;元循环求值器（metacircular evaluator）&lt;/strong&gt;：“元”因为它在谈论语言自身，“循环”因为被解释的语言和实现它的语言是同一个。为什么这是高潮？因为前四章一直在说“过程与数据可以对偶转换”，而&lt;strong&gt;求值器是这个命题的终极证明&lt;/strong&gt;：你的程序，对解释器来说只是数据（一个列表结构）；而解释器本身，也只是一个程序。&lt;/p&gt;
&lt;h3 id=&#34;82-evalapply-循环整个语言只剩两个函数&#34;&gt;8.2 eval/apply 循环：整个语言只剩两个函数&lt;/h3&gt;
&lt;p&gt;剥掉所有细节，求值器的心脏是两个互相递归的函数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;eval（表达式，环境）&lt;/strong&gt;：对表达式分派——
&lt;ul&gt;
&lt;li&gt;自求值的（数字、字符串）→ 直接返回；&lt;/li&gt;
&lt;li&gt;变量 → 去环境里查；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(quote x)&lt;/code&gt; → 返回 x 本身；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(define ...)&lt;/code&gt; / &lt;code&gt;(set! ...)&lt;/code&gt; → 改环境；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(if ...)&lt;/code&gt; → 先 eval 谓词，再选一支；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(lambda ...)&lt;/code&gt; → 打包成“过程对象”（参数 + 函数体 + 当前环境指针，正是第 6 节环境模型里的那个结构）；&lt;/li&gt;
&lt;li&gt;函数调用 → 先 eval 运算符得到过程，再把所有实参 eval 出来，交给 &lt;strong&gt;apply&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;apply（过程，实参列表）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;基本过程（&lt;code&gt;+&lt;/code&gt;、&lt;code&gt;car&lt;/code&gt; 这种内置的）→ 直接调用底层实现；&lt;/li&gt;
&lt;li&gt;复合过程（用户用 lambda 定义的）→ 建一个新框架，形参绑实参，把过程体在这个新环境里交给 &lt;strong&gt;eval&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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
    E[&amp;#34;eval(表达式, 环境)&amp;#34;] --&amp;gt; Q{&amp;#34;表达式是什么类型?&amp;#34;}
    Q --&amp;gt;|&amp;#34;数字/字符串&amp;#34;| R1[&amp;#34;直接返回&amp;#34;]
    Q --&amp;gt;|&amp;#34;变量&amp;#34;| R2[&amp;#34;沿环境链查找绑定&amp;#34;]
    Q --&amp;gt;|&amp;#34;quote&amp;#34;| R3[&amp;#34;返回被引用的数据本身&amp;#34;]
    Q --&amp;gt;|&amp;#34;lambda&amp;#34;| R4[&amp;#34;打包过程对象:&amp;lt;br/&amp;gt;参数+函数体+环境指针&amp;#34;]
    Q --&amp;gt;|&amp;#34;if / define / set!&amp;#34;| R5[&amp;#34;按规则处理&amp;lt;br/&amp;gt;(子表达式递归调 eval)&amp;#34;]
    Q --&amp;gt;|&amp;#34;函数调用&amp;#34;| R6[&amp;#34;eval 运算符得过程&amp;lt;br/&amp;gt;逐个 eval 实参&amp;#34;]
    R6 --&amp;gt; A[&amp;#34;apply(过程, 实参)&amp;#34;]
    A --&amp;gt; Q2{&amp;#34;过程是哪种?&amp;#34;}
    Q2 --&amp;gt;|&amp;#34;基本过程&amp;#34;| R7[&amp;#34;调用底层内置实现&amp;#34;]
    Q2 --&amp;gt;|&amp;#34;复合过程&amp;#34;| R8[&amp;#34;新建框架: 形参→实参&amp;lt;br/&amp;gt;外层指针=过程携带的环境&amp;#34;]
    R8 --&amp;gt;|&amp;#34;在新环境中求值过程体&amp;#34;| E
    style E fill:#e3f2fd
    style A fill:#fff3e0&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这个图值得你盯着看五分钟：&lt;strong&gt;整个语言——所有语法、所有抽象——最后都坍缩成 eval 和 apply 的互相调用&lt;/strong&gt;。这就是“同像性（homoiconicity，代码即数据）”的兑现：因为 Scheme 代码本身就是 Scheme 列表（&lt;code&gt;(f a b)&lt;/code&gt; 既是调用，也是一个三元素列表），所以 eval 可以直接拿程序当数据拆开、分派、重组。&lt;/p&gt;
&lt;h3 id=&#34;83-这一课在工程里的投影&#34;&gt;8.3 这一课在工程里的投影&lt;/h3&gt;
&lt;p&gt;别以为元循环求值是学院派玩具。它直接对应你今天用的东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;每个语言运行时都是 eval/apply&lt;/strong&gt;：V8 解释 JS、CPython 执行字节码、JVM 跑字节码，本质都是“拿一段数据结构（代码），分派，执行”。JIT 只是给这个循环加缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL 引擎&lt;/strong&gt;：你写的 SQL 对数据库是数据，查询规划器就是 eval，执行器就是 apply。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;规则引擎、模板引擎、配置文件即代码&lt;/strong&gt;（Terraform 的 HCL、Kubernetes 的 YAML 控制器循环）：全是“程序读数据，把数据当程序跑”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 时代的镜像&lt;/strong&gt;：今天的 LLM Agent 循环——模型输出一段“计划/代码”（数据），宿主程序解释执行它（eval），结果再喂回模型——结构上和元循环求值器惊人地相似。提示工程里的 ReAct 模式，就是给 LLM 套了一个手写的 eval/apply。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;历史掌故&lt;/strong&gt;：正因为 Lisp 代码即数据，它的**宏（macro）**系统才能在编译期用 Lisp 本身变换 Lisp 代码。Paul Graham 说 Viaweb 能以 3 人团队每周上线几次、碾压竞争对手，核心就是 Lisp 宏让他们随时给语言“长”出适应业务的新语法——&lt;strong&gt;竞争对手在用语言写功能，他们在先造语言再写功能&lt;/strong&gt;。这是 SICP 第 4 章思想最著名的一次商业兑现。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;84-无穷后退以及无所住&#34;&gt;8.4 无穷后退，以及“无所住”&lt;/h3&gt;
&lt;p&gt;一个哲学问题浮出来：元循环求值器自己又是被谁求值的？——被底层的 Scheme 实现。那个实现又被谁执行？被机器。机器呢？……这就是 Sussman 说的“求值的无穷后退（infinite regress）”，像一组两面相对的镜子。&lt;/p&gt;
&lt;p&gt;SICP 的回答很佛系，也很硬核：&lt;strong&gt;后退没有终点，但有可以停下来的层级。&lt;/strong&gt; 求值器把“语言的意义”钉在了一个你能完整看懂的两页代码上，这就够了——剩下的层级，当作黑盒信任。这和第 2 节 &lt;code&gt;sqrt&lt;/code&gt; 的嵌套定义是同一把刀：&lt;strong&gt;在正确的层级上停止追问，把下层当黑盒，是驾驭复杂性的唯一办法。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-收个尾从-evalapply-到无所住而生其心&#34;&gt;9. 收个尾：从 eval/apply 到“无所住而生其心”&lt;/h2&gt;
&lt;p&gt;《金刚经》那句“应无所住而生其心”，粗解是：心不执着于任何固定的相（“住”），才能生起真正的觉照（“生其心”）。&lt;/p&gt;
&lt;p&gt;SICP 教的恰恰是同一个心法的技术版：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不住于表示&lt;/strong&gt;：有理数怎么存不重要，重要的是 &lt;code&gt;make-rat/numer/denom&lt;/code&gt; 的行为约定（第 4 节）；cons 可以用盒子实现，也可以用纯函数实现（第 5 节）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不住于过程与数据的分别&lt;/strong&gt;：代码可以被当数据改写（宏、同像性），数据可以被当过程执行（Church 编码、求值器）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不住于语言的边界&lt;/strong&gt;：当你能为问题造一门小语言（第 4 章元语言抽象、DSL），“用什么语言编程”这个执念就放下了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不住于模型的执念&lt;/strong&gt;：代换模型够用就别上环境模型；状态非要建模时，先试试流能不能把它变成静态结构（第 6、7 节）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;心无所住，方能在过程与数据、语言与元语言之间自由穿行。这是 SICP 作为“CS 佛经”的真正含义：&lt;strong&gt;它教的不是 Scheme，是一种对一切表示层都不执着、只忠于计算本质的编程心法。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;10-带走的清单&#34;&gt;10. 带走的清单&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;五句话记住 SICP：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;语言三要素：&lt;strong&gt;基本表达、组合手段、抽象手段&lt;/strong&gt;。评价任何语言/框架，先数这三样。&lt;/li&gt;
&lt;li&gt;过程抽象：&lt;strong&gt;给计算过程起名字，把细节关进黑盒&lt;/strong&gt;（牛顿法 sqrt 是起点）。&lt;/li&gt;
&lt;li&gt;高阶函数：&lt;strong&gt;把算法的变化点变成参数&lt;/strong&gt;（sum/integral 一个模板；map/filter/reduce 是它的日常形态）。&lt;/li&gt;
&lt;li&gt;数据抽象：&lt;strong&gt;用行为约定代替对表示的依赖&lt;/strong&gt;，屏障之下表示随便换。&lt;/li&gt;
&lt;li&gt;闭包性质：&lt;strong&gt;组合产物可再组合&lt;/strong&gt;，小核心才能长成大生态（cons、Unix 管道、JSON 同理）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;四个思维工具，终身受益：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代换模型：纯函数代码的推理方式；引入赋值前先想想值不值得失去它。&lt;/li&gt;
&lt;li&gt;环境模型：变量是“环境里的格子”不是“值”；闭包捕获格子不捕获值——一类经典 bug 的透视镜。&lt;/li&gt;
&lt;li&gt;流/惰性求值：把“随时间变化的状态”重述为“无穷的静态结构”，按需兑现。&lt;/li&gt;
&lt;li&gt;eval/apply：一切解释器、查询引擎、规则引擎、Agent 循环的两行心脏。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑指南：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读 SICP 不要当“Lisp 教程”读。Scheme 只是手术刀，病灶是“计算的本质”；纠结括号语法就本末倒置了。&lt;/li&gt;
&lt;li&gt;习题一定要做，至少做完第 1、2 章。SICP 的习题（如 1.7 的 &lt;code&gt;good-enough?&lt;/code&gt; 失效分析）是正文的一半。&lt;/li&gt;
&lt;li&gt;不要被第 3 章的状态讨论劝退——代换模型失效、环境模型救场，正是全书承上启下的枢纽，跳过它后面元循环求值器就看不懂。&lt;/li&gt;
&lt;li&gt;元循环求值器别只“看懂”，找一门宿主语言（Python/JS 都行）&lt;strong&gt;亲手写一遍&lt;/strong&gt; eval/apply，哪怕只支持五个特殊形式。写过和看过是两个物种。&lt;/li&gt;
&lt;li&gt;线上资源：MIT 官网有全文免费的 HTML 版（SICP Web Site），1986 年 Abelson 和 Sussman 给惠普员工讲的 20 集录像（MIT OpenCourseWare / &amp;ldquo;SICP Lectures&amp;rdquo;）仍然是最生动的配套。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Harold Abelson, Gerald Jay Sussman, &lt;em&gt;Structure and Interpretation of Computer Programs&lt;/em&gt;, 2nd Edition, MIT Press, 1996（MIT 官网有免费全文）。&lt;/li&gt;
&lt;li&gt;MIT OpenCourseWare：6.001 &lt;em&gt;Structure and Interpretation of Computer Programs&lt;/em&gt;（1986 年 HP 版课程录像）。&lt;/li&gt;
&lt;li&gt;Paul Graham, &lt;em&gt;Hackers &amp;amp; Painters&lt;/em&gt;（Viaweb 与 Lisp 的故事见其中 &amp;ldquo;Beating the Averages&amp;rdquo; 一章）。&lt;/li&gt;
&lt;li&gt;Peter Norvig, &lt;em&gt;Design Patterns in Dynamic Programming&lt;/em&gt;（设计模式与头等函数的关系）。&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    <item>
      <title>《编程珠玑》学习笔记：小书大道理，问题求解的完整闭环</title>
      <link>https://ftmao.com/blog/bookstore/programming-pearls-book-notes/</link>
      <pubDate>Tue, 08 Aug 2023 10:40:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/programming-pearls-book-notes/</guid>
      <description>&lt;h1 id=&#34;编程珠玑学习笔记小书大道理问题求解的完整闭环&#34;&gt;《编程珠玑》学习笔记：小书大道理，问题求解的完整闭环&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：会写代码、刷过一些算法题，但想知道&amp;quot;真实工程里高手是怎么想问题&amp;quot;的程序员。&lt;br&gt;
参考书籍：Jon Bentley《编程珠玑》（Programming Pearls）&lt;br&gt;
阅读目标：提炼全书的问题求解闭环，把几个经典案例变成自己的思维肌肉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;0-写在前面一本中间点的书&#34;&gt;0. 写在前面：一本&amp;quot;中间点&amp;quot;的书&lt;/h2&gt;
&lt;p&gt;如果给程序员的案头书排一条光谱：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一端是吴军《数学之美》：讲的是&lt;strong&gt;大思想&lt;/strong&gt;——数学模型如何降维打击真实世界的问题，视野宏大，但离每天的代码有点远。&lt;/li&gt;
&lt;li&gt;另一端是波利亚《怎样解题》：讲的是&lt;strong&gt;纯方法论&lt;/strong&gt;——&amp;ldquo;看清问题、拟定计划、执行、回顾&amp;rdquo;，放之四海皆准，但抽象到几乎不给具体例子。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;《编程珠玑》正好卡在中间：&lt;strong&gt;用一个个小而真的问题，走完&amp;quot;定义 → 估算 → 设计 → 验证 → 调优&amp;quot;的完整闭环&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它的出身就很&amp;quot;工程&amp;quot;：1983 年起，Jon Bentley 在贝尔实验室工作期间，为《ACM 通讯》（Communications of the ACM）主持 &amp;ldquo;Programming Pearls&amp;rdquo; 专栏，每期挑一个看似不起眼的小问题，带着读者从头到尾把它想透、写对、调快。专栏后来结集成书（1986 年初版，2000 年第二版），薄薄两百多页，却影响了整整几代程序员。&lt;/p&gt;
&lt;p&gt;这本书的&amp;quot;珠玑&amp;quot;（pearls）不是奇技淫巧，而是一种工作方式：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;把问题定义清楚，用估算摸清规模，设计正确的算法，证明它是对的，最后再谈调优。顺序不能乱。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面我按这条主线，把书里最值得带走的几个案例提炼出来。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;1-全书主线问题求解的完整闭环&#34;&gt;1. 全书主线：问题求解的完整闭环&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
    A[&amp;#34;1 定义问题&amp;lt;br/&amp;gt;真正的输入输出是什么&amp;#34;] --&amp;gt; B[&amp;#34;2 估算规模&amp;lt;br/&amp;gt;信封背面算一算&amp;#34;]
    B --&amp;gt; C[&amp;#34;3 设计算法&amp;lt;br/&amp;gt;数据结构 + 算法策略&amp;#34;]
    C --&amp;gt; D[&amp;#34;4 程序验证&amp;lt;br/&amp;gt;断言、边界、测试&amp;#34;]
    D --&amp;gt; E[&amp;#34;5 代码调优&amp;lt;br/&amp;gt;测量驱动的优化&amp;#34;]
    E -.-&amp;gt;|&amp;#34;发现假设不对&amp;#34;| A
    C -.-&amp;gt;|&amp;#34;规模估算变了&amp;#34;| B&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;闭环是环，不是直线&lt;/strong&gt;。调优时发现瓶颈，可能要回到算法设计；设计时发现规模估计错了，要回到估算；甚至写着写着发现&amp;quot;问题本身定义错了&amp;quot;——回到起点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每一步都有&amp;quot;便宜可占&amp;quot;&lt;/strong&gt;。定义清楚了，问题可能消失（位图排序）；估算做对了，方案直接收敛；验证做扎实了，能躲过标准库级别的 bug。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大部分程序员只在&amp;quot;设计&amp;quot;一环花力气&lt;/strong&gt;，这正是这本书要纠正的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面五个案例，分别对应闭环上的关键环节。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;2-定义问题--算法设计开篇的旋转数组&#34;&gt;2. 定义问题 + 算法设计：开篇的旋转数组&lt;/h2&gt;
&lt;h3 id=&#34;21-问题&#34;&gt;2.1 问题&lt;/h3&gt;
&lt;p&gt;把一个有 n 个元素的数组向左循环移动 i 位。比如 n=8、i=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&gt;原数组：  [1, 2, 3, 4, 5, 6, 7, 8]
左移 3 位：[4, 5, 6, 7, 8, 1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;约束：尽量少的额外空间（最好 O(1)），时间也要快。&lt;/p&gt;
&lt;h3 id=&#34;22-解法一逐一移动正确但慢&#34;&gt;2.2 解法一：逐一移动（正确但慢）&lt;/h3&gt;
&lt;p&gt;最直觉的做法：把 &lt;code&gt;x[0]&lt;/code&gt; 存到临时变量，整体左移一位，把临时变量放到末尾；重复 i 次。&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&gt;移动 1 次：[2, 3, 4, 5, 6, 7, 8, 1]
移动 2 次：[3, 4, 5, 6, 7, 8, 1, 2]
移动 3 次：[4, 5, 6, 7, 8, 1, 2, 3]  ✓&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;代码好写、肯定对，但时间是 O(n·i)——n 和 i 都是百万级时就是万亿次操作，直接出局。&lt;strong&gt;它的价值是当&amp;quot;对照组&amp;quot;：先有一个肯定正确的慢版本，后面所有快版本都可以拿它对拍。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;23-解法二求逆三次全书的第一个啊哈&#34;&gt;2.3 解法二：求逆三次（全书的第一个&amp;quot;啊哈&amp;quot;）&lt;/h3&gt;
&lt;p&gt;把数组看成两段：AB（A 是前 i 个元素，B 是剩下的）。我们要 BA。&lt;/p&gt;
&lt;p&gt;看一个神奇的操作序列（X&amp;rsquo; 表示把 X 反转）：&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&gt;原数组：  AB
反转 A：  A&amp;#39;B
再反转 B：A&amp;#39;B&amp;#39;
整体反转：(A&amp;#39;B&amp;#39;)&amp;#39; = BA  ✓&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;手摇上面的例子（i=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&gt;[1, 2, 3 | 4, 5, 6, 7, 8]   反转前 3 个
[3, 2, 1 | 4, 5, 6, 7, 8]   反转后 5 个
[3, 2, 1 | 8, 7, 6, 5, 4]   整体反转
[4, 5, 6, 7, 8, 1, 2, 3]    ✓&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;代码只需一个 reverse 函数加三次调用，O(n) 时间、O(1) 空间，短到几乎不可能写错：&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;reverse&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, i&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:#a6e22e&#34;&gt;reverse&lt;/span&gt;(i, n&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:#a6e22e&#34;&gt;reverse&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, n&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;优雅在哪？&lt;/strong&gt; 旋转是个&amp;quot;整体&amp;quot;操作，我们却一直想逐个元素去搬；而反转是数组上最廉价、最不容易出错的局部操作，三次廉价的局部操作拼出了整体效果。Bentley 借这个例子想说的是：&lt;strong&gt;换个视角看问题，难题会塌缩成 trivial 的三行代码。&lt;/strong&gt; Brian Kernighan 评价这类代码是&amp;quot;既正确又明显地正确&amp;quot;——这是软件里最高级的状态。&lt;/p&gt;
&lt;h3 id=&#34;24-解法三块交换--杂耍算法&#34;&gt;2.4 解法三：块交换 / 杂耍算法&lt;/h3&gt;
&lt;p&gt;还有第三条路：把数组按 gcd(n, i) 分成若干&amp;quot;循环链&amp;quot;，每条链上的元素像杂耍一样依次归位（juggling algorithm）；或者递归地做块交换。时间同样是 O(n)，移动次数比求逆法略少，但代码明显复杂、边界更容易错。&lt;/p&gt;
&lt;p&gt;三种解法摆在一起，就是一堂微型的方法论课：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;解法&lt;/th&gt;
					&lt;th style=&#34;text-align: left&#34;&gt;时间&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;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;O(n·i)&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;O(1)&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;O(n)&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;O(1)&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;O(n)&lt;/td&gt;
					&lt;td style=&#34;text-align: left&#34;&gt;O(1)&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：先写&amp;quot;蠢但对&amp;quot;的版本建立信心，再找&amp;quot;巧且短&amp;quot;的版本上线，复杂版本只用于学习。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;3-重新定义问题位图排序1mb-排-900-万个电话号码&#34;&gt;3. 重新定义问题：位图排序（1MB 排 900 万个电话号码）&lt;/h2&gt;
&lt;p&gt;这是全书第一章 &amp;ldquo;Cracking the Oyster&amp;rdquo;（撬开牡蛎）的核心案例，也是&amp;quot;定义问题&amp;quot;环节最漂亮的一次示范。&lt;/p&gt;
&lt;h3 id=&#34;31-问题来自真实场景&#34;&gt;3.1 问题（来自真实场景）&lt;/h3&gt;
&lt;p&gt;一个文件里有至多 900 万个整数，每个都是 7 位数（即 0 ~ 9,999,999），&lt;strong&gt;没有重复&lt;/strong&gt;。要把它们排序输出。可用内存大约 1 兆字节，磁盘空间充足。&lt;/p&gt;
&lt;h3 id=&#34;32-第一反应归并排序&#34;&gt;3.2 第一反应：归并排序&lt;/h3&gt;
&lt;p&gt;标准答案是多路归并：内存里一次读一批，排序后写回磁盘，再多趟归并。正确、通用，但要好几趟磁盘读写，代码也不少。&lt;/p&gt;
&lt;p&gt;在动手之前，Bentley 追问了一句：&lt;strong&gt;这个问题里有哪些信息被我忽略了？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个数恰好出现一次（无重复）；&lt;/li&gt;
&lt;li&gt;数值范围是已知的、有限的（10^7 种可能）；&lt;/li&gt;
&lt;li&gt;除了排序之外没有其他操作；&lt;/li&gt;
&lt;li&gt;没有关联数据（不像学生记录要跟着学号走）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;33-关键洞察用位表示存在&#34;&gt;3.3 关键洞察：用&amp;quot;位&amp;quot;表示&amp;quot;存在&amp;quot;&lt;/h3&gt;
&lt;p&gt;如果把这些约束用足，答案呼之欲出：&lt;strong&gt;不存数字本身，只存&amp;quot;这个数出现过没有&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;用一个 10^7 位的位图（bitmap），第 j 位为 1 表示整数 j 在输入里。10^7 位 = 1,250,000 字节 ≈ 1.19 MB，刚好塞进那点可怜的内存。整个排序变成三趟：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把所有位置 0；&lt;/li&gt;
&lt;li&gt;读入每个整数 i，把第 i 位置 1；&lt;/li&gt;
&lt;li&gt;从头到尾扫一遍位图，把为 1 的位依次输出——&lt;strong&gt;天然有序&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;输入文件&amp;lt;br/&amp;gt;至多 900 万个 7 位整数&amp;lt;br/&amp;gt;无重复&amp;#34;] --&amp;gt; B[&amp;#34;阶段一&amp;lt;br/&amp;gt;10^7 位的位图全部置 0&amp;#34;]
    B --&amp;gt; C[&amp;#34;阶段二&amp;lt;br/&amp;gt;每读到一个整数 i&amp;lt;br/&amp;gt;把第 i 位置 1&amp;#34;]
    C --&amp;gt; D[&amp;#34;阶段三&amp;lt;br/&amp;gt;从 0 到 10^7-1 扫描位图&amp;lt;br/&amp;gt;位为 1 就输出下标&amp;#34;]
    D --&amp;gt; E[&amp;#34;输出文件&amp;lt;br/&amp;gt;已排序&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;没有比较、没有交换、没有归并——&lt;strong&gt;排序问题被转化成了表示问题&lt;/strong&gt;。时间 O(n)，磁盘只顺序读写两遍，代码量远小于归并方案。&lt;/p&gt;
&lt;h3 id=&#34;34-这个案例真正教的东西&#34;&gt;3.4 这个案例真正教的东西&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题的表象 ≠ 问题的本质。&lt;/strong&gt; 嘴上说的是&amp;quot;排序&amp;quot;，实质是&amp;quot;有限集合的表示与枚举&amp;quot;。一旦换了表示法，排序&amp;quot;消失&amp;quot;了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;追问约束是最大的杠杆。&lt;/strong&gt; &amp;ldquo;无重复&amp;quot;和&amp;quot;范围有限&amp;quot;这两句话，价值千金。真实工作里，甲方给的条件往往比我们用上的多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;思想有长长的影子。&lt;/strong&gt; 位图索引、Bloom Filter（布隆过滤器）、Redis 的 bitmap、Roaring Bitmap，都是&amp;quot;用位表示存在性&amp;quot;这条线上的后代。今天在海量数据去重、用户签到、UV 统计里天天在用。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一句话理解&lt;/strong&gt;：当值域有限且无重复时，&amp;ldquo;存在性位图&amp;quot;可以把排序、去重、判重全部压成 O(n) 的位操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;4-算法设计最大子数组和的复杂度演进&#34;&gt;4. 算法设计：最大子数组和的复杂度演进&lt;/h2&gt;
&lt;p&gt;如果说位图排序展示的是&amp;quot;重新定义问题&amp;rdquo;，最大子数组展示的就是闭环里&amp;quot;设计&amp;quot;环节的完整教科书：&lt;strong&gt;同一个问题，四个算法，复杂度连降三级。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;41-问题&#34;&gt;4.1 问题&lt;/h3&gt;
&lt;p&gt;输入一个有 n 个数的数组（有正有负），找出&lt;strong&gt;连续子数组&lt;/strong&gt;中和最大的那个，返回这个最大和。&lt;/p&gt;
&lt;p&gt;小例子：&lt;code&gt;[-2, 11, -4, 13, -5, -2]&lt;/code&gt;，答案是 20（子数组 &lt;code&gt;[11, -4, 13]&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;这个问题不是编造的智力题。它源自布朗大学 Ulf Grenander 做模式识别时遇到的二维问题：在数字图像里找&amp;quot;最像某个模式&amp;quot;的子区域。二维版本太贵，他先把它化简到一维——&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;strong&gt;版本一：O(n³) 暴力。&lt;/strong&gt; 枚举所有起点 i 和终点 j，对每对 (i, j) 把中间的数加起来。n=1000 时约 10^9 次加法，已经没法用了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;版本二：O(n²)。&lt;/strong&gt; 注意到 &lt;code&gt;sum(i..j+1) = sum(i..j) + x[j+1]&lt;/code&gt;——内层循环不用从头加，带着上一次的累加和走就行。一次&amp;quot;重用已有计算&amp;rdquo;，干掉一个数量级。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;版本三：O(n log n) 分治。&lt;/strong&gt; 把数组从中点劈开。最大子数组只有三种可能：全在左半、全在右半、跨过中点。前两种递归解决；跨中点的答案 = 从中点向左扫描的最大后缀和 + 向右扫描的最大前缀和，O(n) 内搞定。T(n) = 2T(n/2) + O(n)，总共 O(n log n)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;版本四：O(n) 扫描线。&lt;/strong&gt; 从左到右扫一遍，维护&amp;quot;&lt;strong&gt;以当前元素结尾的最大子数组和&lt;/strong&gt;&amp;quot;：&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&gt;ending_here = max(x[i], ending_here + x[i])
so_far      = max(so_far, ending_here)&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&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;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;i  x[i]  ending_here  so_far
0   -2       -2          0   (负数前缀，扔掉)
1   11       11         11
2   -4        7         11
3   13       20         20   ← 答案
4   -5       15         20
5   -2       13         20&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这就是后来大名鼎鼎的 Kadane 算法。&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 演进[&amp;#34;同一个问题的四次设计&amp;#34;]
        V1[&amp;#34;版本一 暴力枚举&amp;lt;br/&amp;gt;O(n³)&amp;#34;] --&amp;gt;|&amp;#34;重用累加和&amp;#34;| V2[&amp;#34;版本二 前缀复用&amp;lt;br/&amp;gt;O(n²)&amp;#34;]
        V2 --&amp;gt;|&amp;#34;分而治之&amp;#34;| V3[&amp;#34;版本三 分治&amp;lt;br/&amp;gt;O(n log n)&amp;#34;]
        V3 --&amp;gt;|&amp;#34;负数前缀果断丢弃&amp;#34;| V4[&amp;#34;版本四 扫描线&amp;lt;br/&amp;gt;O(n)&amp;#34;]
    end
    V4 --&amp;gt; R[&amp;#34;n=10^6 时&amp;lt;br/&amp;gt;从&amp;#39;算到宇宙热寂&amp;#39;&amp;lt;br/&amp;gt;到毫秒级&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;43-真实掌故高手也不是一步到位的&#34;&gt;4.3 真实掌故：高手也不是一步到位的&lt;/h3&gt;
&lt;p&gt;Bentley 在书里交代了这段历史，非常值得记住：Grenander 把一维问题抛给卡内基梅隆的 Michael Shamos，Shamos &lt;strong&gt;一夜之间&lt;/strong&gt;设计出 O(n log n) 的分治算法——当时他们都觉得这大概就是极限了。后来在一次研讨会上，统计学家 Jay Kadane 听了一分钟，当场给出了 O(n) 算法。&lt;/p&gt;
&lt;p&gt;这个故事的两层启示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;连 Shamos 这个级别的算法专家，第一反应也不是最优解。&lt;/strong&gt; 算法设计是迭代的，不是顿悟的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每降一个数量级，靠的都是一个新视角&lt;/strong&gt;：重用计算、分治、负数前缀丢弃——而不是更卖力的实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工程连接：这个 O(n) 扫描思想今天活在很多地方——股票最大收益、信号处理里的峰值检测、乃至面试最高频题。而&amp;quot;版本二&amp;quot;的累加和技巧，就是&lt;strong&gt;前缀和数组&lt;/strong&gt;，是区间查询类问题的通用武器。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;5-程序验证二分查找一个藏了九年的标准库-bug&#34;&gt;5. 程序验证：二分查找，一个藏了九年的标准库 bug&lt;/h2&gt;
&lt;p&gt;闭环里最容易被跳过的一环是&amp;quot;验证&amp;quot;。Bentley 用二分查找给了所有自信的程序员一记耳光。&lt;/p&gt;
&lt;h3 id=&#34;51-一个尴尬的事实&#34;&gt;5.1 一个尴尬的事实&lt;/h3&gt;
&lt;p&gt;二分查找的思想小学生都懂：有序数组里每次砍一半。但 Bentley 在贝尔实验室和后来的课程里反复做过同一个实验：请&lt;strong&gt;专业程序员&lt;/strong&gt;（给了充足时间、任意语言、可以用高级伪码）写出正确的二分查找，结果&lt;strong&gt;大约 90% 的人写错&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这不是个案。他在书里引用过一个考据：第一个二分查找的发表版本出现在 1946 年，而第一个&lt;strong&gt;对任意长度数组都正确&lt;/strong&gt;的发表版本，要等到 1962 年。&lt;strong&gt;十六年里，所有人发表的版本都有 bug。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Knuth 在《计算机程序设计艺术》第三卷里说得更直白（大意）：二分查找的思想虽然简单，细节却可能棘手得出乎意料——很多好程序员都在这上面栽过跟头。&lt;/p&gt;
&lt;h3 id=&#34;52-坑都藏在哪&#34;&gt;5.2 坑都藏在哪&lt;/h3&gt;
&lt;p&gt;错的从来不是&amp;quot;砍一半&amp;quot;这个思想，而是边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;区间定义不清&lt;/strong&gt;：&lt;code&gt;while (low &amp;lt; high)&lt;/code&gt; 还是 &lt;code&gt;low &amp;lt;= high&lt;/code&gt;？mid 算出来之后，新区间是 &lt;code&gt;[low, mid-1]&lt;/code&gt; 还是 &lt;code&gt;[low, mid]&lt;/code&gt;？两两组合里只有一种自洽。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;差一位错误（off-by-one）&lt;/strong&gt;：死循环在 n=2 的小数组上，或者漏查最后一个元素。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回值的语义&lt;/strong&gt;：没找到时返回 -1？还是返回插入位置？调用方怎么处理？&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;53-标准库级别的翻车溢出-bug-藏了九年&#34;&gt;5.3 标准库级别的翻车：溢出 bug 藏了九年&lt;/h3&gt;
&lt;p&gt;2006 年，Google 首席 Java 架构师 Joshua Bloch 在官方博客发了一篇著名的文章《Extra, Extra — Read All About It: Nearly All Binary Searches and Mergesorts are Broken》，自曝家丑：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;java.util.Arrays.binarySearch&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-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; mid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (low &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; high) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; 2;&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; 当 low 和 high 都很大时，&lt;code&gt;low + high&lt;/code&gt; 会溢出成负数，mid 变成负值，抛 ArrayIndexOutOfBoundsException。更扎心的是：这段代码最初正是 Bloch 自己写的，在他的《Effective Java》里也作为范例出现过；同样的 bug 还潜伏在归并排序的实现里，而且&lt;strong&gt;在 JDK 里藏了大约九年&lt;/strong&gt;才被发现——因为要让 low + high 溢出，数组得大到 10^9 量级，当年的机器根本装不下这么大的数组。&lt;/p&gt;
&lt;p&gt;修复方式也很&amp;quot;珠玑&amp;quot;：&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-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; mid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; low &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (high &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; low) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; 2;        &lt;span style=&#34;color:#75715e&#34;&gt;// 减法会溢出吗？low&amp;lt;=high 时不会&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;int&lt;/span&gt; mid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (low &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; high) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 1;            &lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 或者用无符号右移&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;54-验证环节的正确姿势&#34;&gt;5.4 验证环节的正确姿势&lt;/h3&gt;
&lt;p&gt;Bentley 的对策不是&amp;quot;更聪明&amp;quot;，而是&lt;strong&gt;更机械&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用循环不变式写程序&lt;/strong&gt;：写二分查找前先写下&amp;quot;如果目标存在，它一定在 [low, high] 区间内&amp;quot;，每一轮循环保持这个断言为真，程序的正确性就跟着不变式走，而不是靠感觉。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;断言（assert）进代码&lt;/strong&gt;：把不变式直接写成断言，测试期全部打开。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;小数据穷举对拍&lt;/strong&gt;：n=0、1、2、3 的所有情况手算或穷举，目标值在开头、结尾、中间、不存在各试一遍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拿慢版本对拍&lt;/strong&gt;：用一个 O(n) 线性查找和二分查找在随机数据上跑几万次，结果必须一致——这就是 2.2 节&amp;quot;蠢但对&amp;quot;版本的真正用途。&lt;/li&gt;
&lt;/ul&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;6-估算的艺术信封背面的算术&#34;&gt;6. 估算的艺术：信封背面的算术&lt;/h2&gt;
&lt;p&gt;闭环里的&amp;quot;估算&amp;quot;环节，Bentley 把它叫做 &lt;strong&gt;back-of-the-envelope calculation&lt;/strong&gt;（信封背面估算）：在餐厅信封背面用粗略数字、心算，三分钟搞清楚问题的规模。它的目的不是精确，而是&lt;strong&gt;在投入大量工作之前，先确认方向是不是错的&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;61-为什么要估算&#34;&gt;6.1 为什么要估算&lt;/h3&gt;
&lt;p&gt;回到位图排序的例子：那个&amp;quot;大约 1MB 内存&amp;quot;的约束，配合&amp;quot;至多 900 万个 7 位数&amp;quot;，先算一笔——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果每个数存成 32 位整数：900 万 × 4 字节 = 36 MB，&lt;strong&gt;内存放不下&lt;/strong&gt;，所以&amp;quot;全部读进内存排序&amp;quot;这条路当场枪毙；&lt;/li&gt;
&lt;li&gt;而 10^7 位的位图 ≈ 1.19 MB，&lt;strong&gt;勉强塞得下&lt;/strong&gt;，方案成立。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两笔心算，直接决定了整个方案的形态。这就是估算的价值：&lt;strong&gt;它花三分钟，替你省掉三天返工。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;62-随身武器一72-法则&#34;&gt;6.2 随身武器一：72 法则&lt;/h3&gt;
&lt;p&gt;钱以年利率 r% 复利增长，大约 72/r 年翻一番。年利率 6%，约 12 年翻倍；利率 12%，约 6 年翻倍。&lt;/p&gt;
&lt;p&gt;这个法则对程序员的用处远超理财——&lt;strong&gt;凡是指数增长的东西都适用&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据量每年涨 40%？72/40 ≈ 1.8 年翻一倍，三年后是现在的 5 倍多，现在的存储架构撑得住吗？&lt;/li&gt;
&lt;li&gt;程序每慢 1 倍，意味着规模翻倍时要多等一倍时间——反过来，复杂度从 O(n²) 降到 O(n log n) 在 n 翻倍时省多少，心算就能估出来。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;63-随身武器二π-秒就是一个纳世纪&#34;&gt;6.3 随身武器二：π 秒就是一个纳世纪&lt;/h3&gt;
&lt;p&gt;Bentley 在书里给了一个极好记的常数：&lt;strong&gt;π 秒 ≈ 一纳世纪（nanocentury，10^-9 个世纪 ≈ 3.155 秒）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;用法：程序每秒跑多少操作、跑一世纪要多久，换算成&amp;quot;纳世纪&amp;quot;立刻有直觉。比如一个操作 1 微秒，跑 10^9 次就是 1000 秒 ≈ 318 个纳世纪 ≈ 6 分钟——心里有没有数，决定你要不要现在就换算法。&lt;/p&gt;
&lt;p&gt;再配几个该背下来的量级：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2^10 ≈ 10^3（千），2^20 ≈ 10^6（百万），2^30 ≈ 10^9（十亿）；&lt;/li&gt;
&lt;li&gt;磁盘顺序读比随机读快几个数量级；内存比磁盘快约 10^5 倍；&lt;/li&gt;
&lt;li&gt;一年约 π × 10^7 秒。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;64-随身武器三安全系数与双重估算&#34;&gt;6.4 随身武器三：安全系数与双重估算&lt;/h3&gt;
&lt;p&gt;Bentley 建议的重要习惯：&lt;strong&gt;同一个问题用两种不同路径估算，结果应该对得上；对不上就查到哪里错了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;书里举过估算密西西比河日径流量的例子：一条路径从&amp;quot;流域面积 × 降雨&amp;quot;估，另一条从&amp;quot;河道宽度 × 深度 × 流速&amp;quot;估，两个粗算结果在同一个数量级上吻合，估算就可信。工程里的翻版：系统容量规划时，既从&amp;quot;日活 × 人均请求 × 峰值系数&amp;quot;往下算，也从&amp;quot;单机压测 QPS × 机器数&amp;quot;往上顶，两边对上才算完。&lt;/p&gt;
&lt;p&gt;工程连接：今天这活叫 capacity planning，面试里叫 Fermi 问题（&amp;ldquo;北京有多少个加油站&amp;rdquo;）。内核没变：&lt;strong&gt;粗糙但快速的数字直觉，是工程师防止自己被宏大叙事欺骗的唯一武器。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;7-代码调优闭环的最后一环也是最危险的一环&#34;&gt;7. 代码调优：闭环的最后一环，也是最危险的一环&lt;/h2&gt;
&lt;h3 id=&#34;71-先立规矩&#34;&gt;7.1 先立规矩&lt;/h3&gt;
&lt;p&gt;Bentley 对调优的态度非常克制，三条规矩：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先正确，再调优。&lt;/strong&gt; 调优引入的复杂度会制造 bug，而一个跑得快但算错的程序毫无价值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;先测量，再调优。&lt;/strong&gt; 瓶颈几乎从不在你以为的地方。用 profiler 找到热点，只调那 5% 的代码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法层级收益 &amp;raquo; 代码层级收益。&lt;/strong&gt; O(n²) 换 O(n log n) 可能快一千倍；循环展开通常只快百分之几十。调优是锦上添花，不是雪中送炭。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;72-书里反复出现的几招&#34;&gt;7.2 书里反复出现的几招&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;缓存（避免重复计算）。&lt;/strong&gt; 如果同一个值被算了很多次，算一次存起来。最现代的版本就是 memoization 和各级缓存体系。书里有个经典场景：频繁计算 &lt;code&gt;c[i][j]&lt;/code&gt; 这类昂贵表达式，而它在循环里其实不变——&lt;strong&gt;把循环不变式提到循环外&lt;/strong&gt;，或者干脆预处理一张表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;哨兵（sentinel）。&lt;/strong&gt; 顺序查找时，先把要查找的值放到数组末尾当&amp;quot;哨兵&amp;quot;，内层循环就可以省掉&amp;quot;是否越界&amp;quot;的判断，比较次数近似减半：&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x[n] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; t;                    &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;for&lt;/span&gt; (i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; x[i] &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; t; i&lt;span style=&#34;color:#f92672&#34;&gt;++&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;查找命中与否分开处理。思想内核：&lt;strong&gt;用一次提前的准备，换掉循环里千万次的检查。&lt;/strong&gt; 今天的 &lt;code&gt;std::string&lt;/code&gt; 以 &lt;code&gt;\0&lt;/code&gt; 结尾、链表的 dummy head 节点，都是哨兵思想的亲戚。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;循环展开。&lt;/strong&gt; 把循环体复制几份，减少循环控制开销。Bentley 同时警告：展开会让代码变长、指令缓存变挤，而且现代编译器自己会做。&lt;strong&gt;手写展开之前，先确认编译器没帮你做，并且 profiler 说这里值得。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试重排。&lt;/strong&gt; 一串 if-else 判断，把命中频率最高的条件放最前面。思想内核和哈夫曼编码一样：高频路径应该最短。&lt;/p&gt;
&lt;h3 id=&#34;73-今天的读者要打个折扣&#34;&gt;7.3 今天的读者要打个折扣&lt;/h3&gt;
&lt;p&gt;这本书写于编译器还笨、内存按 KB 算的年代。今天读调优章节要带两个过滤器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编译器已经很强&lt;/strong&gt;：很多手工强度削减、展开，&lt;code&gt;-O2&lt;/code&gt; 会替你做，手写反而碍事；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;瓶颈换了地方&lt;/strong&gt;：当年的敌人是指令数，今天的敌人常常是&lt;strong&gt;缓存未命中和内存墙&lt;/strong&gt;——Bentley 的&amp;quot;缓存重复计算&amp;quot;思想没过时，但主战场从 ALU 挪到了 cache line。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不变的元规则：&lt;strong&gt;测量驱动。&lt;/strong&gt; 不调没测过的代码，不信没数字的优化。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;8-番外bentley-与-knuth-的那次交锋&#34;&gt;8. 番外：Bentley 与 Knuth 的那次交锋&lt;/h2&gt;
&lt;p&gt;这是计算机科学史上很有名的一段公案，也值得单独记一笔。&lt;/p&gt;
&lt;p&gt;1986 年，Bentley 在自己的专栏里请 Donald Knuth 展示他倡导的 &lt;strong&gt;literate programming&lt;/strong&gt;（文学编程，用 WEB 系统把代码和文档织在一起）。Knuth 领的题目很朴素：读一个文本文件，统计并输出出现次数最多的 k 个单词及次数。&lt;/p&gt;
&lt;p&gt;Knuth 交出了一篇十多页、结构精妙的 WEB 程序，专为这个问题设计了一个巧妙的数据结构（带计数的有序 trie）。Bentley 把它发了出来，同时请 Unix 之父之一的 Doug McIlroy 写书评。McIlroy 的评论比程序本身更出名——他指出 Knuth 程序的优雅毋庸置疑，但&lt;strong&gt;整个问题用 6 行 Unix shell 就能解决&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-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tr -cs A-Za-z &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; | tr A-Z a-z | sort | uniq -c | sort -rn | sed &lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;1&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;q&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;而且这 6 行是每个工程师随手可写、可改、可组合的。&lt;/p&gt;
&lt;p&gt;这场交锋没有输家，它留下的启示是双面的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Knuth 展示了&lt;strong&gt;为一个值得的问题量身打造工具与文档&lt;/strong&gt;能达到的深度；&lt;/li&gt;
&lt;li&gt;McIlroy 展示了&lt;strong&gt;用已有简单工具组合，可能比重新发明深刻十倍的轮子更工程&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这正是《编程珠玑》整本书的张力所在：&lt;strong&gt;深度与简单，各自在什么场合赢。&lt;/strong&gt; Bentley 把两篇都发表出来，本身就是&amp;quot;珠玑&amp;quot;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;9-带走的清单&#34;&gt;9. 带走的清单&lt;/h2&gt;
&lt;p&gt;把全书压成一张可以贴在工位上的清单：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;想问题时&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 先把问题用一句话写清楚：输入、输出、约束。约束里往往藏着解法（位图排序）。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 信封背面先算规模：数据多大、时间预算多少、哪些方案当场出局（36MB vs 1.19MB）。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 问一句&amp;quot;这个问题有没有更简单的等价形式&amp;quot;——旋转数组的三次求逆就是这么来的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;写代码时&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 先写&amp;quot;蠢但对&amp;quot;的版本，留作对照组，方便对拍。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 每降一个数量级靠新视角，不靠更卖力：重用计算 → 分治 → 丢弃无用信息。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 写二分查找这类&amp;quot;简单&amp;quot;算法时，先写循环不变式，再写代码；n=0、1、2 手动过一遍。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 记住 &lt;code&gt;(low + high) / 2&lt;/code&gt; 会溢出：用 &lt;code&gt;low + (high - low) / 2&lt;/code&gt;。标准库都翻过车，你没有豁免权。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;调优时&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 先 profile，只调热点；调优前后各测一次，没数字不算数。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 优先换算法和数据结构，再考虑代码级技巧。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 哨兵、缓存重复计算、循环不变式外提，这三招便宜又安全；循环展开先问编译器。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;作为工程师&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 背上 72 法则和 &amp;ldquo;π 秒 = 纳世纪&amp;rdquo;，让指数增长在你心里有形状。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 重要估算走两条独立路径，对上了才信。&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 读 Knuth vs McIlroy 之争：深刻的方案和简单的方案，各自配得上各自的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这本书两百多页，值回票价的理由只有一个：&lt;strong&gt;它教的不是答案，而是从拿到问题到交付代码之间，那段大多数人凭感觉乱走的路程，其实每一步都有章法。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考书籍：Jon Bentley《编程珠玑》（Programming Pearls, 2nd Edition）；Joshua Bloch, &amp;ldquo;Extra, Extra — Read All About It: Nearly All Binary Searches and Mergesorts are Broken&amp;rdquo;, Google Research Blog, 2006。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《Netty 源码剖析与应用》详细总结：写给从未接触过 Netty 的人</title>
      <link>https://ftmao.com/blog/bookstore/netty-source-code-analysis-notes/</link>
      <pubDate>Sat, 20 Feb 2021 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/netty-source-code-analysis-notes/</guid>
      <description>&lt;h1 id=&#34;netty-源码剖析与应用详细总结写给从未接触过-netty-的人&#34;&gt;《Netty 源码剖析与应用》详细总结：写给从未接触过 Netty 的人&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：刘耀林《Netty 源码剖析与应用》（电子工业出版社，2020 年 11 月）
目标读者：会 Java、听说过 Netty 但没系统学过，想一篇文章建立 Netty 核心知识框架的人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;这本书讲什么为什么学-netty&#34;&gt;〇、这本书讲什么？为什么学 Netty？&lt;/h2&gt;
&lt;p&gt;普通开发在工作中很少直接碰 Netty，但只要翻开任何一个主流分布式框架的底层源码，通信模块几乎都是它：&lt;strong&gt;Dubbo、gRPC、Spark、Flink、Elasticsearch、HBase&lt;/strong&gt;（HBase 从 2.0 开始默认用 Netty RPC Server 替代原生实现）。不管是做 Java Web 后台还是大数据，Netty 都是绕不过去的地基。&lt;/p&gt;
&lt;p&gt;为什么不直接用 JDK 的 NIO？作者的回答很直白：&lt;strong&gt;用原生 NIO 写一套高效稳定的通信模块非常难&lt;/strong&gt;——要处理多路复用、粘包拆包、线程模型、内存管理、断线重连等一堆坑。Netty 把这些全部封装好，让普通人也能快速开发高性能、面向协议的服务端和客户端。&lt;/p&gt;
&lt;p&gt;全书的结构是循序渐进的四部分：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;第一部分：实战入门&amp;lt;br/&amp;gt;第1章 Netty基础篇&amp;lt;br/&amp;gt;第2章 原理部分&amp;lt;br/&amp;gt;第3章 分布式RPC&amp;#34;] --&amp;gt; B[&amp;#34;第二部分：核心源码剖析&amp;lt;br/&amp;gt;第4章 核心组件&amp;lt;br/&amp;gt;（NioEventLoop/Channel/ByteBuf）&amp;lt;br/&amp;gt;第5章 读/写请求源码&amp;#34;]
    B --&amp;gt; C[&amp;#34;第三部分：内存管理&amp;lt;br/&amp;gt;第6章 jemalloc内存池&amp;lt;br/&amp;gt;（全书最难）&amp;#34;]
    C --&amp;gt; D[&amp;#34;第四部分：高级应用与调优&amp;lt;br/&amp;gt;第7章 时间轮高级应用&amp;lt;br/&amp;gt;第8章 问题分析与性能调优&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两个贯穿全书的高级项目：&lt;strong&gt;一套类似 Dubbo 的分布式 RPC 框架&lt;/strong&gt;（第 3 章写、第 8 章压测调优），以及&lt;strong&gt;一套基于时间轮的 10 亿级任务调度监控引擎&lt;/strong&gt;（第 7 章）。&lt;/p&gt;
&lt;h2 id=&#34;一netty-基础篇先看长连接通信长什么样第-1-章&#34;&gt;一、Netty 基础篇：先看长连接通信长什么样（第 1 章）&lt;/h2&gt;
&lt;h3 id=&#34;11-服务端五步法&#34;&gt;1.1 服务端五步法&lt;/h3&gt;
&lt;p&gt;Netty 服务端程序的固定套路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建两个线程组：&lt;strong&gt;Boss 线程组&lt;/strong&gt;专门接收客户端连接；&lt;strong&gt;Worker 线程组&lt;/strong&gt;处理已接收连接的 I/O 读写；&lt;/li&gt;
&lt;li&gt;用启动辅助类 &lt;code&gt;ServerBootstrap&lt;/code&gt; 配置启动参数（如 &lt;code&gt;ChannelOption.SO_RCVBUF&lt;/code&gt; / &lt;code&gt;SO_SNDBUF&lt;/code&gt; 设置接收/发送缓冲区大小）；&lt;/li&gt;
&lt;li&gt;Boss 把接收到的连接注册到 Worker 后，交给 &lt;code&gt;ChannelInitializer&lt;/code&gt; 初始化 Handler 链；&lt;/li&gt;
&lt;li&gt;编写业务 Handler（继承 &lt;code&gt;ChannelInboundHandlerAdapter&lt;/code&gt;，重写 &lt;code&gt;channelRead&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;绑定端口——绑定由 Boss 线程完成，所以主线程要执行同步阻塞方法等待绑定完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这就是经典的&lt;strong&gt;主从 Reactor 模型&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
    subgraph BossGroup[&amp;#34;Boss 线程组（接收连接）&amp;#34;]
        B1[&amp;#34;Boss NioEventLoop&amp;lt;br/&amp;gt;监听 OP_ACCEPT&amp;#34;]
    end
    subgraph WorkerGroup[&amp;#34;Worker 线程组（处理 I/O）&amp;#34;]
        W1[&amp;#34;Worker 线程 1&amp;lt;br/&amp;gt;管理多个 Channel&amp;#34;]
        W2[&amp;#34;Worker 线程 2&amp;lt;br/&amp;gt;管理多个 Channel&amp;#34;]
    end
    C1[&amp;#34;客户端连接 1&amp;#34;] --&amp;gt; B1
    C2[&amp;#34;客户端连接 2&amp;#34;] --&amp;gt; B1
    C3[&amp;#34;客户端连接 3&amp;#34;] --&amp;gt; B1
    B1 --&amp;gt;|&amp;#34;注册 SocketChannel&amp;#34;| W1
    B1 --&amp;gt;|&amp;#34;注册 SocketChannel&amp;#34;| W2
    W1 --&amp;gt; P1[&amp;#34;ChannelPipeline&amp;lt;br/&amp;gt;Decoder → 业务Handler → Encoder&amp;#34;]
    W2 --&amp;gt; P2[&amp;#34;ChannelPipeline&amp;lt;br/&amp;gt;Decoder → 业务Handler → Encoder&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;12-一个值得琢磨的示例客户端怎么同步等异步响应&#34;&gt;1.2 一个值得琢磨的示例：客户端怎么&amp;quot;同步等&amp;quot;异步响应&lt;/h3&gt;
&lt;p&gt;第 1 章的客户端示例藏着一个 Netty 编程的核心范式。客户端连续发 100 个请求，服务端异步返回。问题是：&lt;strong&gt;&lt;code&gt;channel.writeAndFlush()&lt;/code&gt; 是异步的，主线程怎么拿到响应结果？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;书里的解法是自实现一套 &lt;code&gt;RequestFuture&lt;/code&gt; 机制（这正是 Dubbo 等 RPC 框架的原型）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个请求带唯一 id，发送前把 &lt;code&gt;RequestFuture&lt;/code&gt; 存入以 id 为 key 的 Map，主线程在 &lt;code&gt;future.get()&lt;/code&gt; 上阻塞等待；&lt;/li&gt;
&lt;li&gt;服务端 Handler 处理完后，把结果&lt;strong&gt;带上请求 id&lt;/strong&gt; 写回；&lt;/li&gt;
&lt;li&gt;客户端 &lt;code&gt;channelRead&lt;/code&gt; 收到响应，按 id 找到对应的 Future，设置结果并唤醒主线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;书中的思考题也很有意思：示例里客户端输出是有序的（按 id 逐个 get），&lt;strong&gt;如何修改代码让输出无序？&lt;/strong&gt;——答案是不再按顺序 get，而是在响应到达的回调里直接输出。这个小实验能帮你真正理解&amp;quot;异步 + 回调&amp;quot;与&amp;quot;同步等待&amp;quot;的差别。&lt;/p&gt;
&lt;h2 id=&#34;二原理部分绕不开的六块基石第-2-章&#34;&gt;二、原理部分：绕不开的六块基石（第 2 章）&lt;/h2&gt;
&lt;h3 id=&#34;21-多路复用器selectpollepoll&#34;&gt;2.1 多路复用器：select、poll、epoll&lt;/h3&gt;
&lt;p&gt;BIO 时代每个 Socket 连接要一条线程同步阻塞，1 万连接就要 1 万条线程，根本扛不住。NIO 的**多路复用器（Selector）**让一条线程可以管理成千上万个连接的接入和读写事件。&lt;/p&gt;
&lt;p&gt;三种经典模型的区别（面试高频）：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模型&lt;/th&gt;
					&lt;th&gt;遍历方式&lt;/th&gt;
					&lt;th&gt;连接数上限&lt;/th&gt;
					&lt;th&gt;触发方式&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;select&lt;/td&gt;
					&lt;td&gt;轮询全部 fd&lt;/td&gt;
					&lt;td&gt;1024 左右&lt;/td&gt;
					&lt;td&gt;水平触发&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;poll&lt;/td&gt;
					&lt;td&gt;轮询全部 fd&lt;/td&gt;
					&lt;td&gt;无硬上限&lt;/td&gt;
					&lt;td&gt;水平触发&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;epoll&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个容易混淆的点：&lt;strong&gt;JDK 自带的 epoll 封装是水平触发（LT）的，而 Netty 通过 JNI 自己重写的 epoll 是边缘触发（ET）的&lt;/strong&gt;，这也是 Netty 在 Linux 上推荐 EpollEventLoop 的原因之一。&lt;/p&gt;
&lt;h3 id=&#34;22-netty-的线程模型&#34;&gt;2.2 Netty 的线程模型&lt;/h3&gt;
&lt;p&gt;Netty 的 Boss/Worker 默认线程数与 CPU 核数挂钩（默认 核数 × 2）。Boss 只关心 &lt;code&gt;OP_ACCEPT&lt;/code&gt;，接收连接后把 &lt;code&gt;SocketChannel&lt;/code&gt; 按策略轮询注册到某个 Worker 的 Selector 上。关键约束：&lt;strong&gt;一个 Channel 在其生命周期内只绑定一个 EventLoop 线程&lt;/strong&gt;，所以处理 Channel 的所有事件都不需要加锁——这是 Netty 高并发能力的重要来源。&lt;/p&gt;
&lt;h3 id=&#34;23-编码和解码tcp-粘包半包问题&#34;&gt;2.3 编码和解码：TCP 粘包/半包问题&lt;/h3&gt;
&lt;p&gt;TCP 是面向字节流的协议，它把数据看成无结构的字节流，根本不知道&amp;quot;一条消息&amp;quot;的边界在哪。于是产生了经典问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;粘包&lt;/strong&gt;：两条消息被合并成一次发送/接收；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;半包（拆包）&lt;/strong&gt;：一条消息被拆成多次接收。&lt;/li&gt;
&lt;/ul&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 发送方
        A[&amp;#34;业务对象&amp;#34;] --&amp;gt; B[&amp;#34;编码器 Encoder&amp;lt;br/&amp;gt;对象 → 字节&amp;#34;]
        B --&amp;gt; C[&amp;#34;TCP 输出缓冲区&amp;lt;br/&amp;gt;SO_SNDBUF&amp;#34;]
    end
    C --&amp;gt;|&amp;#34;② TCP 网络传输&amp;lt;br/&amp;gt;（受滑动窗口/拥塞控制影响）&amp;#34;| D[&amp;#34;TCP 输入缓冲区&amp;lt;br/&amp;gt;SO_RCVBUF&amp;#34;]
    subgraph 接收方
        D --&amp;gt; E[&amp;#34;解码器 Decoder&amp;lt;br/&amp;gt;处理粘包/半包&amp;#34;]
        E --&amp;gt; F[&amp;#34;业务对象&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;Netty 的解法是在 Pipeline 里插&lt;strong&gt;解码器&lt;/strong&gt;：固定长度解码器、分隔符解码器、&lt;strong&gt;长度字段解码器（LengthFieldBasedFrameDecoder，最常用）&lt;/strong&gt;——协议头里放一个长度字段，解码器攒够一条完整消息才往下游传。书中第 3 章手写 RPC 时就是用自定义协议（协议头 + 长度 + 内容）解决的。&lt;/p&gt;
&lt;h3 id=&#34;24-序列化protobuf-与-kryo&#34;&gt;2.4 序列化：Protobuf 与 Kryo&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protobuf&lt;/strong&gt;（Google）：跨语言、压缩率高（相同数据比 JSON 小 3~5 倍）、需要预定义 schema，适合 RPC 服务间通信；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kryo&lt;/strong&gt;：专注 Java，使用简单、速度快，但不跨语言，适合 Java 生态内部。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;25-零拷贝少搬一次数据是一次&#34;&gt;2.5 零拷贝：少搬一次数据是一次&lt;/h3&gt;
&lt;p&gt;零拷贝指操作数据时无须把 Buffer 从一个内存区域拷贝到另一个区域。Netty 里主要有三种场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;堆外直接内存收发&lt;/strong&gt;：用堆内存做 Socket 读写时，JVM 会先把堆内数据拷贝到堆外再写入 Socket；Netty 收发直接使用堆外内存，省掉这次拷贝；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CompositeByteBuf 组合视图&lt;/strong&gt;：一条消息被拆成多个数据包时，把多个 Buffer 逻辑上组合成一个（类似数据库视图），而不是物理拷贝拼接；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FileChannel.transferTo()&lt;/strong&gt;：文件传输直接在内核空间完成，不经用户态缓冲区。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;26-背压backpressure让快生产者等一等慢消费者&#34;&gt;2.6 背压（Backpressure）：让快生产者等一等慢消费者&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 LR
    subgraph 生产者
        P1[&amp;#34;消息生产者 A&amp;lt;br/&amp;gt;写入缓存队列&amp;#34;]
        P2[&amp;#34;消息生产者 B&amp;lt;br/&amp;gt;写入缓存队列&amp;#34;]
    end
    P1 &amp;amp; P2 --&amp;gt; Q[&amp;#34;TCP 链路&amp;#34;]
    Q --&amp;gt;|&amp;#34;背压&amp;#34;| C[&amp;#34;消费者&amp;lt;br/&amp;gt;I/O 读取放入缓存队列&amp;#34;]
    C -.-&amp;gt;|&amp;#34;消费速率 &amp;lt; 生产速率时&amp;lt;br/&amp;gt;TCP 窗口缩小 → 通道阻塞&amp;lt;br/&amp;gt;→ 生产者降速&amp;#34;| P1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;机制链条：消费者消费变慢 → TCP 接收缓冲区满 → &lt;strong&gt;TCP 滑动窗口&lt;/strong&gt;变小 → 生产者发送缓冲区写不进去 → 生产者降速。书中还以 &lt;strong&gt;Flink 为例&lt;/strong&gt;讲了它的应用：Flink 基于 Netty 实现了 credit-based（基于信用额度）的反压机制，下游告诉上游&amp;quot;我还能收多少&amp;quot;，从根本上防止快生产慢消费把系统压垮。&lt;/p&gt;
&lt;h2 id=&#34;三分布式-rpc把-netty-组装成一个-dubbo第-3-章&#34;&gt;三、分布式 RPC：把 Netty 组装成一个 Dubbo（第 3 章）&lt;/h2&gt;
&lt;p&gt;这一章是全书第一个大项目：手写一套分布式 RPC。目标是业务方用起来像 SpringMVC/Dubbo 一样——引个 jar 包就行，不用关心 Netty 底层；服务端可动态扩容，不用写死 IP 端口。&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
    subgraph 客户端
        A[&amp;#34;业务代码&amp;lt;br/&amp;gt;像调用本地方法一样调用&amp;#34;] --&amp;gt; B[&amp;#34;动态代理&amp;lt;br/&amp;gt;拦截方法调用&amp;#34;]
        B --&amp;gt; C[&amp;#34;协议封装&amp;lt;br/&amp;gt;类名/方法名/参数 + 请求id&amp;#34;]
    end
    subgraph 网络层
        C --&amp;gt; D[&amp;#34;Netty 长连接&amp;lt;br/&amp;gt;自定义协议 + Kryo 序列化&amp;#34;]
    end
    subgraph 服务端
        D --&amp;gt; E[&amp;#34;Netty Server Handler&amp;lt;br/&amp;gt;解码出调用信息&amp;#34;]
        E --&amp;gt; F[&amp;#34;Java 反射&amp;lt;br/&amp;gt;调用 Spring 容器中的实现类&amp;#34;]
    end
    subgraph 注册中心[&amp;#34;Zookeeper 注册中心&amp;#34;]
        G[&amp;#34;服务启动时注册&amp;lt;br/&amp;gt;IP/端口/权重&amp;#34;]
        H[&amp;#34;客户端订阅服务列表&amp;lt;br/&amp;gt;负载均衡选择节点&amp;#34;]
    end
    F --&amp;gt; G
    C --&amp;gt; H&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个关键技术点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;协议设计&lt;/strong&gt;：自定义协议头（魔数 + 长度 + 请求 id）+ 序列化内容，配合 LengthField 解码器解决粘包；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Java 反射解耦&lt;/strong&gt;：服务端 Handler 解析出&amp;quot;类名 + 方法名 + 参数类型 + 参数值&amp;quot;，从 Spring 容器找到实现类反射调用——业务代码和 Netty 的 Handler 逻辑完全解耦；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务注册与发现&lt;/strong&gt;：引入 Zookeeper（对比了 Consul/ETCD/Eureka），服务端启动注册临时节点，客户端监听节点变化做动态感知和负载均衡（可按权重）。这一层替代了传统 Nginx/LVS 的硬负载方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;四核心组件源码剖析第-4-章全书的硬核部分&#34;&gt;四、核心组件源码剖析（第 4 章，全书的硬核部分）&lt;/h2&gt;
&lt;h3 id=&#34;41-nioeventloopgroup-与-nioeventloopnetty-的发动机&#34;&gt;4.1 NioEventLoopGroup 与 NioEventLoop：Netty 的发动机&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;NioEventLoopGroup&lt;/code&gt; 就是线程池，&lt;code&gt;NioEventLoop&lt;/code&gt; 是里面的每条线程，&lt;strong&gt;每条线程一个 Selector，绑定一堆 Channel&lt;/strong&gt;。核心在它的 &lt;code&gt;run()&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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;run() 死循环开始&amp;#34;] --&amp;gt; B[&amp;#34;select() 轮询 I/O 事件&amp;lt;br/&amp;gt;（带超时，防止空轮询 bug）&amp;#34;]
    B --&amp;gt; C{&amp;#34;有就绪事件？&amp;#34;}
    C -- 是 --&amp;gt; D[&amp;#34;处理 SelectionKey&amp;lt;br/&amp;gt;OP_ACCEPT / OP_READ / OP_WRITE&amp;#34;]
    C -- 否 --&amp;gt; E[&amp;#34;跳过 I/O 处理&amp;#34;]
    D --&amp;gt; F[&amp;#34;执行异步任务队列&amp;lt;br/&amp;gt;（用户提交的任务 + 定时任务）&amp;#34;]
    E --&amp;gt; F
    F --&amp;gt;|&amp;#34;ioRatio 控制 I/O 与任务的时间配比&amp;lt;br/&amp;gt;默认各 50%&amp;#34;| A
    B -.-&amp;gt;|&amp;#34;连续空轮询超过阈值&amp;lt;br/&amp;gt;→ rebuildSelector() 重建 Selector&amp;lt;br/&amp;gt;（规避 JDK epoll 空轮询 bug）&amp;#34;| B&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;源码里的几个精妙优化，作者都点了出来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SelectedSelectionKeySet&lt;/strong&gt;：用数组替换 JDK Selector 内部的 HashSet，遍历就绪事件从哈希遍历变成数组遍历，更快；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selector 重建&lt;/strong&gt;：JDK epoll 有著名的&amp;quot;空轮询 bug&amp;quot;（select 立刻返回但无事件，CPU 100%），Netty 的解法是统计空轮询次数，超阈值就新建一个 Selector 把所有 Channel 迁过去；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ioRatio&lt;/strong&gt;：I/O 处理与异步任务执行的时间比例可调，默认 50:50。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-channel-的继承体系每层抽象都有明确职责&#34;&gt;4.2 Channel 的继承体系：每层抽象都有明确职责&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[&amp;#34;Channel 接口&amp;lt;br/&amp;gt;id/eventLoop/pipeline/config&amp;#34;] --&amp;gt; B[&amp;#34;AbstractChannel&amp;lt;br/&amp;gt;模板方法：bind/connect/read/write&amp;lt;br/&amp;gt;持有 Unsafe 和 Pipeline&amp;#34;]
    B --&amp;gt; C[&amp;#34;AbstractNioChannel&amp;lt;br/&amp;gt;对接 NIO：SelectionKey&amp;lt;br/&amp;gt;interestOps/readInterestOp&amp;#34;]
    C --&amp;gt; D[&amp;#34;AbstractNioByteChannel&amp;lt;br/&amp;gt;字节流读写（TCP 数据通道）&amp;#34;]
    C --&amp;gt; E[&amp;#34;AbstractNioMessageChannel&amp;lt;br/&amp;gt;消息读写（接收连接用）&amp;#34;]
    D --&amp;gt; F[&amp;#34;NioSocketChannel&amp;lt;br/&amp;gt;客户端连接通道&amp;lt;br/&amp;gt;doRead/doWrite 具体实现&amp;#34;]
    E --&amp;gt; G[&amp;#34;NioServerSocketChannel&amp;lt;br/&amp;gt;服务端监听通道&amp;lt;br/&amp;gt;doReadMessages = accept 新连接&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;读源码时最值得体会的设计：I/O 操作全部收敛到内部类 &lt;strong&gt;Unsafe&lt;/strong&gt; 里，把&amp;quot;Netty 的 Channel API&amp;quot;和&amp;quot;JDK 底层的 Socket 操作&amp;quot;隔离开；&lt;code&gt;doXxx()&lt;/code&gt; 抽象方法留给子类实现，&lt;code&gt;AbstractChannel&lt;/code&gt; 用模板方法串起流程。书中还点名了 Netty 的性能优化手段：JDK 的 &lt;code&gt;Unsafe&lt;/code&gt;、乐观锁、对象池等。&lt;/p&gt;
&lt;h3 id=&#34;43-bytebuf比-nio-bytebuffer-好用得多的缓冲区&#34;&gt;4.3 ByteBuf：比 NIO ByteBuffer 好用得多的缓冲区&lt;/h3&gt;
&lt;p&gt;JDK 的 &lt;code&gt;ByteBuffer&lt;/code&gt; 只有一个 position 指针，读写模式要 &lt;code&gt;flip()&lt;/code&gt; 来回切换，极易出错。Netty 的 &lt;code&gt;ByteBuf&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 LR
    subgraph ByteBuf[&amp;#34;ByteBuf 内部结构&amp;#34;]
        A[&amp;#34;[0, readerIndex)&amp;lt;br/&amp;gt;已读区域（可丢弃）&amp;#34;] --&amp;gt; B[&amp;#34;[readerIndex, writerIndex)&amp;lt;br/&amp;gt;可读区域 readableBytes&amp;#34;]
        B --&amp;gt; C[&amp;#34;[writerIndex, capacity)&amp;lt;br/&amp;gt;可写区域 writableBytes&amp;#34;]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;继承体系（每层干一件事）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AbstractByteBuf&lt;/code&gt;：读写索引维护、边界检查；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AbstractReferenceCountedByteBuf&lt;/code&gt;：&lt;strong&gt;引用计数&lt;/strong&gt;（&lt;code&gt;retain()&lt;/code&gt;/&lt;code&gt;release()&lt;/code&gt;），配合 &lt;code&gt;ReferenceCountUpdater&lt;/code&gt; 用 CAS 乐观锁更新计数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CompositeByteBuf&lt;/code&gt;：多个 Buffer 的逻辑组合（零拷贝场景 2）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PooledByteBuf&lt;/code&gt;：池化缓冲区，用完归还对象池复用，是第 6 章内存管理的入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;44-内存泄漏检测弱引用--采样的巧思&#34;&gt;4.4 内存泄漏检测：弱引用 + 采样的巧思&lt;/h3&gt;
&lt;p&gt;堆外内存不归 GC 管，忘了 &lt;code&gt;release()&lt;/code&gt; 就泄漏。Netty 的 &lt;code&gt;ResourceLeakDetector&lt;/code&gt; 机制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个 ByteBuf 创建时（按级别&lt;strong&gt;采样&lt;/strong&gt;，不是全量——开销可控）包装成弱引用/虚引用，记录创建时的调用栈；&lt;/li&gt;
&lt;li&gt;ByteBuf 被 GC 时，若引用计数没归零，说明泄漏了；&lt;/li&gt;
&lt;li&gt;从引用队列中捞出泄漏记录，打印带调用栈的告警日志。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个机制本身就是一节生动的 JVM 引用类型教学课——书中还配了弱引用的 JUnit 测试用例帮助理解。&lt;/p&gt;
&lt;h2 id=&#34;五读写请求源码剖析一次-io-的完整旅程第-5-章&#34;&gt;五、读/写请求源码剖析：一次 I/O 的完整旅程（第 5 章）&lt;/h2&gt;
&lt;h3 id=&#34;51-serverbootstrap-启动过程&#34;&gt;5.1 ServerBootstrap 启动过程&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;bind()&lt;/code&gt; 背后是：反射创建 &lt;code&gt;NioServerSocketChannel&lt;/code&gt; → 初始化 Pipeline（插入 &lt;code&gt;ServerBootstrapAcceptor&lt;/code&gt;）→ 注册到 Boss EventLoop → 绑定端口 → 监听 &lt;code&gt;OP_ACCEPT&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;52-接入与读写两个-unsafe-的分工&#34;&gt;5.2 接入与读写：两个 Unsafe 的分工&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;sequenceDiagram
    participant Client as 客户端
    participant Boss as Boss线程(NioMessageUnsafe)
    participant Acceptor as ServerBootstrapAcceptor
    participant Worker as Worker线程(NioByteUnsafe)
    participant Handler as 业务Handler链

    Client-&amp;gt;&amp;gt;Boss: 发起 TCP 连接
    Boss-&amp;gt;&amp;gt;Boss: 监听到 OP_ACCEPT&amp;lt;br/&amp;gt;accept 出 SocketChannel
    Boss-&amp;gt;&amp;gt;Acceptor: fireChannelRead(新连接)
    Acceptor-&amp;gt;&amp;gt;Worker: 设置 options/attrs&amp;lt;br/&amp;gt;注册到 Worker 的 Selector
    Client-&amp;gt;&amp;gt;Worker: 发送数据
    Worker-&amp;gt;&amp;gt;Worker: 监听到 OP_READ&amp;lt;br/&amp;gt;分配 ByteBuf 循环读
    Worker-&amp;gt;&amp;gt;Handler: fireChannelRead(msg)&amp;lt;br/&amp;gt;沿 Pipeline 传播&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Boss 侧用 &lt;strong&gt;NioMessageUnsafe&lt;/strong&gt;（读出来的是&amp;quot;一条消息&amp;quot;= 一个新连接）；Worker 侧用 &lt;strong&gt;NioByteUnsafe&lt;/strong&gt;（读出来的是字节流）——这就是第 4 章两个 AbstractNio 子类分工的实际体现；&lt;/li&gt;
&lt;li&gt;OP_READ 是&lt;strong&gt;循环读&lt;/strong&gt;：一次事件里反复读直到读空，每轮用 &lt;code&gt;RecvByteBufAllocator&lt;/code&gt; 预测该分配多大的缓冲区（第 6 章细讲）；&lt;/li&gt;
&lt;li&gt;写操作先进 &lt;strong&gt;ChannelOutboundBuffer&lt;/strong&gt; 链表（Entry 节点用完回收进对象池），&lt;code&gt;flush0()&lt;/code&gt; 时交给 &lt;code&gt;NioSocketChannel.doWrite()&lt;/code&gt; 批量写（合并成 &lt;code&gt;NIO_BUFFERS&lt;/code&gt; 数组一次 writev），写不完（TCP 缓冲区满）则注册 &lt;code&gt;OP_WRITE&lt;/code&gt; 等可写时再续写——这就是&lt;strong&gt;写半包&lt;/strong&gt;的处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;六内存管理jemalloc-思想在-java-的落地第-6-章最难也最值&#34;&gt;六、内存管理：jemalloc 思想在 Java 的落地（第 6 章，最难也最值）&lt;/h2&gt;
&lt;p&gt;为什么 Netty 要自己管内存？堆外内存的 &lt;code&gt;malloc/free&lt;/code&gt; 系统调用很贵，频繁申请释放是性能杀手。Netty 把 &lt;strong&gt;jemalloc&lt;/strong&gt; 的分配思想搬到了 Java 里，三层结构：&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
    subgraph 三层结构[&amp;#34;内存分配的三层结构&amp;#34;]
        A[&amp;#34;① PoolThreadCache&amp;lt;br/&amp;gt;本地线程缓存&amp;lt;br/&amp;gt;每个 I/O 线程一个，免锁&amp;#34;]
        B[&amp;#34;② PoolArena&amp;lt;br/&amp;gt;分配区（arena）&amp;lt;br/&amp;gt;管理一组 PoolChunk&amp;#34;]
        C[&amp;#34;③ 系统内存&amp;lt;br/&amp;gt;堆外/堆内直接申请&amp;#34;]
    end
    A --&amp;gt;|&amp;#34;缓存命中就直接用&amp;lt;br/&amp;gt;不命中向下申请&amp;#34;| B
    B --&amp;gt;|&amp;#34;没有可用 chunk 才申请&amp;#34;| C
    D[&amp;#34;PoolChunk（默认 16MB）&amp;lt;br/&amp;gt;= 2048 个 page（8KB）&amp;#34;] -.属于.- B
    E[&amp;#34;PoolSubpage&amp;lt;br/&amp;gt;把 page 再切成 tiny/small 小块&amp;#34;] -.切分.- D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;分配策略（申请一块内存时）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先查当前线程的 &lt;code&gt;PoolThreadCache&lt;/code&gt;，命中直接返回——&lt;strong&gt;无线程竞争，无锁&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;不命中则找 &lt;code&gt;PoolArena&lt;/code&gt; 分配：大于等于 8KB 走 &lt;code&gt;PoolChunk&lt;/code&gt; 按 page 分配；小于 8KB 走 &lt;code&gt;PoolSubpage&lt;/code&gt; 把一个 page 切成更小的块（tiny/small 规格）；&lt;/li&gt;
&lt;li&gt;Arena 也分不到，才向系统申请新 chunk（默认 16MB），用完放回 Arena 重复利用；&lt;/li&gt;
&lt;li&gt;超大内存（超过 chunk 大小）不池化，直接申请、用完直接回收。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;PoolChunk&lt;/code&gt; 内部用&lt;strong&gt;完全二叉树&lt;/strong&gt;管理 2048 个 page：分配 ≥8KB 时在树上找深度合适的连续节点；&lt;code&gt;PoolSubpage&lt;/code&gt; 用位图（bitmap）记录小块占用。&lt;code&gt;PoolArena&lt;/code&gt; 里维护按使用率分档的 chunk 链表（qInit/q000/q025/q050/q075/q100），让分配尽量集中在少数 chunk 上，提高内存利用率。&lt;/p&gt;
&lt;p&gt;最后一块拼图是 &lt;code&gt;RecvByteBufAllocator&lt;/code&gt;（配合第 5 章的 OP_READ 循环读）：&lt;strong&gt;AdaptiveRecvByteBufAllocator 会自适应预测&lt;/strong&gt;——这次实际读了多少，下次就按这个量级分配缓冲区（默认从 1KB 起步，在 64B ~ 64KB 之间动态调整），避免&amp;quot;每次固定分配 64KB 却只读到 16B&amp;quot;的浪费。&lt;/p&gt;
&lt;p&gt;学习建议（书中原话）：这部分涉及大量多线程并发和位移与或非操作，&lt;strong&gt;把难以理解的代码贴到测试类里单独调试&lt;/strong&gt;是最有效的办法。&lt;/p&gt;
&lt;h2 id=&#34;七时间轮高级应用从心跳检测到-10-亿级任务监控第-7-章&#34;&gt;七、时间轮高级应用：从心跳检测到 10 亿级任务监控（第 7 章）&lt;/h2&gt;
&lt;h3 id=&#34;71-hashedwheeltimer-的原理&#34;&gt;7.1 HashedWheelTimer 的原理&lt;/h3&gt;
&lt;p&gt;IM 系统里有成千上万甚至百万条长连接链路，Netty 自带的心跳检测是给每条链路创建 &lt;code&gt;ScheduledFutureTask&lt;/code&gt; 定时任务——链路多了定时任务就爆炸。时间轮（HashedWheelTimer）是另一种方案：&lt;strong&gt;只用一条额外的 Worker 线程&lt;/strong&gt;，像钟表一样每隔一个 tick 走一格，只把当前格子的任务捞出来执行，没到的根本不碰。&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
    subgraph 时间轮[&amp;#34;时间轮（wheelSize 个刻度）&amp;#34;]
        direction LR
        T0[&amp;#34;刻度 0&amp;lt;br/&amp;gt;task 链表&amp;#34;] --- T1[&amp;#34;刻度 1&amp;lt;br/&amp;gt;task 链表&amp;#34;] --- T2[&amp;#34;...&amp;#34;] --- TN[&amp;#34;刻度 N-1&amp;lt;br/&amp;gt;task 链表&amp;#34;]
    end
    P[&amp;#34;指针（tick）&amp;lt;br/&amp;gt;每 tickDuration 走一格&amp;#34;] --&amp;gt; T0
    W[&amp;#34;Worker 线程&amp;lt;br/&amp;gt;tick++&amp;lt;br/&amp;gt;落格 = tick &amp;amp; (wheelSize-1)&amp;#34;] -.驱动.- P&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;先思考三个问题（书中的引导方式很好）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;指针走一轮是多久？&lt;/strong&gt; tickDuration × wheelSize；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用什么容器存任务？&lt;/strong&gt; 每个刻度（bucket）一条链表；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务时间晚于一轮终点放哪？&lt;/strong&gt; 照样按落格公式放进去，任务上记录 &lt;strong&gt;round 数（还要等几圈）&lt;/strong&gt;，指针扫到它时 round 减 1，减到 0 才真正执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;7274-实战外卖订单超时监控系统&#34;&gt;7.2~7.4 实战：外卖订单超时监控系统&lt;/h3&gt;
&lt;p&gt;书中设计的应用场景非常接地气：外卖平台每天上千万订单，用户下单后在规定时间内无人处理就要告警（超时要被投诉、要赔偿）。类似的还有优惠券过期提醒、医院预约提醒——&lt;strong&gt;凡是&amp;quot;与未来一段时间相关&amp;quot;的业务都能用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但 10 亿级任务放内存里必然溢出，于是引出改造方案：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;业务系统&amp;lt;br/&amp;gt;（外卖订单等）&amp;#34;] --&amp;gt;|&amp;#34;上报监控数据&amp;#34;| K[&amp;#34;Kafka 数据源&amp;lt;br/&amp;gt;（处理能力弱时数据留在 Kafka，削峰）&amp;#34;]
    K --&amp;gt; B[&amp;#34;时间轮计算服务&amp;lt;br/&amp;gt;（分布式部署，每分钟百万级吞吐）&amp;#34;]
    B &amp;lt;--&amp;gt; R[&amp;#34;Redis&amp;lt;br/&amp;gt;任务数据按 时间轮节点wheel + 刻度idx 存储&amp;#34;]
    B --&amp;gt;|&amp;#34;到期且未处理（isOver=0）&amp;#34;| C[&amp;#34;告警输出&amp;lt;br/&amp;gt;回调 callBackUrl / 通知相关人员&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;数据源选 Kafka&lt;/strong&gt;：数据量大、要监控&amp;quot;是否被及时处理&amp;quot;，Kafka 天然削峰；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务数据外置 Redis&lt;/strong&gt;：内存里只放时间轮的调度结构，任务内容放 Redis，用&amp;quot;时间轮节点名称 wheel + 刻度编号 idx&amp;quot;做 key 映射，还能直接从 Redis 捞数据做问题定位；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控数据通用化设计&lt;/strong&gt;：&lt;code&gt;dataId&lt;/code&gt;（唯一标识）、&lt;code&gt;sysCode&lt;/code&gt;（系统来源）、&lt;code&gt;detectionTime&lt;/code&gt;（检测时间）、&lt;code&gt;isOver&lt;/code&gt;（是否结束，相当于任务取消）、&lt;code&gt;isCycle&lt;/code&gt;/&lt;code&gt;cycleTime&lt;/code&gt;（支持周期性调度监控）、&lt;code&gt;callBackUrl&lt;/code&gt;（到期回调）——这套字段设计可以直接抄到自己的项目里。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;八问题分析与性能调优第-8-章&#34;&gt;八、问题分析与性能调优（第 8 章）&lt;/h2&gt;
&lt;h3 id=&#34;81-linux-部署&#34;&gt;8.1 Linux 部署&lt;/h3&gt;
&lt;p&gt;把第 3 章的 RPC 服务部署到 Linux：分环境配置文件（&lt;code&gt;server.properties&lt;/code&gt;：服务名、端口、Zookeeper 地址、权重，开发/测试/生产各一份）+ &lt;code&gt;PropertyUtil&lt;/code&gt; 工具类加载 + shell 启动脚本。&lt;/p&gt;
&lt;h3 id=&#34;82-jmeter-秒杀压测&#34;&gt;8.2 JMeter 秒杀压测&lt;/h3&gt;
&lt;p&gt;普通 JMeter TCP 脚本只能压一台服务器，也不用为此加 Nginx/LVS——书中选择的方式是&lt;strong&gt;把 RPC 客户端打成 jar 包嵌入 JMeter&lt;/strong&gt;（实现 &lt;code&gt;AbstractJavaSamplerClient&lt;/code&gt;），直接模拟长连接高并发调用分布式服务，观察 TPS、并发用户数、平均响应耗时。&lt;/p&gt;
&lt;h3 id=&#34;83-常见生产问题排查实战中真能救命的套路&#34;&gt;8.3 常见生产问题排查（实战中真能救命的套路）&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;症状&lt;/th&gt;
					&lt;th&gt;排查方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;内存溢出&lt;strong&gt;一定会出现&lt;/strong&gt;（跑一段时间就炸）&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;jmap -histo&lt;/code&gt; 排序观察，看哪些对象增长快且不释放&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;内存溢出&lt;strong&gt;偶尔出现&lt;/strong&gt;（很久才发生）&lt;/td&gt;
					&lt;td&gt;启动参数加 &lt;code&gt;-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/xxx.hprof&lt;/code&gt;，溢出时自动快照，用 Eclipse MAT 分析&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;无溢出但响应慢、TPS 极低&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;top -H -p pid&lt;/code&gt; 找到 CPU 最高的线程 id → 转 16 进制小写 → &lt;code&gt;jstack -l pid | grep &#39;线程id&#39;&lt;/code&gt; 定位运行栈&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;84-性能调优三大方向&#34;&gt;8.4 性能调优三大方向&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Linux 系统参数&lt;/strong&gt;：压测常见的&amp;quot;文件句柄数过多&amp;quot;——全局改 &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; 加 &lt;code&gt;fs.file-max=1000000&lt;/code&gt;（支持百万连接），&lt;code&gt;sysctl -p&lt;/code&gt; 生效；局部改 &lt;code&gt;/etc/security/limits.conf&lt;/code&gt; 加 &lt;code&gt;* hard/soft nofile 1000000&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP 参数&lt;/strong&gt;：缓冲区大小、 backlog 队列等内核参数调整；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netty 应用层&lt;/strong&gt;：理解第 6 章后大胆用内存池减少堆外分配次数；用第 7 章的时间轮替换默认心跳检测，减轻 I/O 线程负担。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;书中一个坦诚的结论值得记住：&lt;strong&gt;无复杂业务逻辑时，Netty 不调优性能也已经很好&lt;/strong&gt;——调优的价值在于真实业务（数据库操作、远程调用混入 Service 层）出现瓶颈时，你知道从哪里下手。&lt;/p&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;mindmap
  root((Netty))
    应用层
      长连接通信（第1章）
      分布式RPC（第3章 协议+反射+ZK）
      时间轮（第7章 心跳/10亿级任务监控）
    原理层
      多路复用器 epoll/select
      Reactor线程模型 Boss/Worker
      编解码与粘包拆包
      序列化 Protobuf/Kryo
      零拷贝
      背压 TCP窗口/Flink
    源码层
      NioEventLoop run（）循环
      Channel继承体系/Unsafe
      ByteBuf 双指针/引用计数
      内存泄漏检测 弱引用+采样
      读写流程 accept/read/write半包
    内存层
      PoolThreadCache 线程缓存
      PoolArena/PoolChunk 16MB
      PoolSubpage 小块位图
      AdaptiveRecvByteBuf 自适应预测
    运维层
      Linux部署与句柄调优
      JMeter长连接压测
      jmap/MAT/jstack排查三板斧&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;最后给新人的学习路径建议（也是作者的阅读建议）：先跑通第 1 章的长连接示例和第 3 章的 RPC，建立&amp;quot;会用&amp;quot;的信心；再反复啃第 4~6 章源码（作者建议源码至少读 3 遍，直到对 &lt;code&gt;Channel&lt;/code&gt;、&lt;code&gt;Handler&lt;/code&gt;、&lt;code&gt;NioEventLoop&lt;/code&gt;、&lt;code&gt;ByteBuf&lt;/code&gt; 的各个方法都了如指掌）；最后用第 7、8 章的实战项目把知识串起来。Netty 涉及多线程、复杂数据结构与内存管理模型，读完它对并发编程能力的提升是肉眼可见的。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《入侵的艺术》详解：米特尼克笔下的十个真实黑客故事与防御启示</title>
      <link>https://ftmao.com/blog/bookstore/art-of-intrusion-notes/</link>
      <pubDate>Sat, 24 Feb 2018 09:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/art-of-intrusion-notes/</guid>
      <description>&lt;h1 id=&#34;入侵的艺术详解米特尼克笔下的十个真实黑客故事与防御启示&#34;&gt;《入侵的艺术》详解：米特尼克笔下的十个真实黑客故事与防御启示&lt;/h1&gt;
&lt;p&gt;凯文·米特尼克（Kevin Mitnick）是网络安全史上最著名的名字之一：上世纪 90 年代他是 FBI 的头号通缉黑客，入狱五年后转型为安全咨询师。《入侵的艺术》（The Art of Intrusion，2005，与 William Simon 合著）是他的第二本书——如果说前作《欺骗的艺术》讲的是&amp;quot;&lt;strong&gt;骗人&lt;/strong&gt;&amp;quot;（社会工程），这本书讲的就是&amp;quot;&lt;strong&gt;破门&lt;/strong&gt;&amp;quot;：十个基于真实事件的入侵故事，主角包括赌场老千、恐怖分子、监狱看守、警察、银行黑客和企业窃密者。&lt;/p&gt;
&lt;p&gt;这本书的结构非常有特色，也是它区别于技术手册的地方：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每一章 = &lt;strong&gt;一个完整的真实入侵故事&lt;/strong&gt; + &lt;strong&gt;&amp;ldquo;启迪&amp;rdquo;（攻击者的视角总结）&lt;/strong&gt; + &lt;strong&gt;&amp;ldquo;对策&amp;rdquo;（防御者该怎么做）&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它几乎不讲工具操作，而是反复展示一个观点：&lt;strong&gt;入侵者成功的关键往往不是高深技术，而是防御方的疏忽、傲慢和&amp;quot;没想到&amp;quot;&lt;/strong&gt;。本文按章节梳理十个故事，拆解每条的攻击链与防御启示。为保护当事人，书中人物多用化名。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第-1-章-攻击赌场百万美元的老虎机骗局&#34;&gt;第 1 章 攻击赌场：百万美元的老虎机骗局&lt;/h2&gt;
&lt;p&gt;全书最精彩的开篇。主角 Alex 是个会点电子技术的赌徒，他和同伙 Mike、Larry、Marco 盯上了赌场的&lt;strong&gt;视频扑克机&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;故事的核心不是&amp;quot;黑进电脑&amp;quot;，而是&lt;strong&gt;逆向工程&lt;/strong&gt;：Alex 买下同款退役机器，拆出 ROM 芯片，把伪随机数生成器（PRNG）的算法彻底研究透。视频扑克机用 PRNG 决定发牌，而 PRNG 的种子来自机器内部的时钟——&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
    A[&amp;#34;购入同款退役机器&amp;#34;] --&amp;gt; B[&amp;#34;拆出 ROM&amp;lt;br/&amp;gt;反汇编研究发牌算法&amp;#34;]
    B --&amp;gt; C[&amp;#34;发现 PRNG 种子&amp;lt;br/&amp;gt;来自机器内部时钟&amp;#34;]
    C --&amp;gt; D[&amp;#34;同伙在赌场观察机器&amp;lt;br/&amp;gt;用微型计算机同步时钟状态&amp;#34;]
    D --&amp;gt; E[&amp;#34;预测下一张牌&amp;lt;br/&amp;gt;只在稳赢时下注&amp;#34;]
    E --&amp;gt; F[&amp;#34;分散在多家赌场小额赢钱&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;伪随机不是随机&lt;/strong&gt;：用可预测的输入（时间）做安全相关的种子，等于没有随机性。今天这条对应密码学随机数（CSPRNG）的基本常识，但在嵌入式设备上至今仍是重灾区。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永远不要假设&amp;quot;对方拆不开&amp;quot;&lt;/strong&gt;：把安全建立在算法保密上（security by obscurity），一旦 ROM 被逆向就全线崩溃。&lt;/li&gt;
&lt;li&gt;赌场最终是靠&lt;strong&gt;行为模式分析&lt;/strong&gt;（赢率异常）而不是技术手段发现端倪的——监控&amp;quot;结果&amp;quot;有时比监控&amp;quot;手段&amp;quot;更有效。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;第-2-章-当恐怖分子来袭&#34;&gt;第 2 章 当恐怖分子来袭&lt;/h2&gt;
&lt;p&gt;这一章的基调完全不同：米特尼克调查了极端组织（Harkatul-Mujahideen 等）如何利用互联网。Khalid、Comrade 等化名的黑客为组织服务：入侵网站、窃取情报、寻找美军机密网络（如 SIPRNET）的接入点。&lt;/p&gt;
&lt;p&gt;本章对普通读者最大的价值是&lt;strong&gt;打破一个刻板印象&lt;/strong&gt;：网络攻击的对手不只是&amp;quot;找乐子的少年&amp;quot;，还有有明确目标、有耐心、有组织的团体。他们不追求炫技，追求的是&lt;strong&gt;长期潜伏、悄悄拿数据&lt;/strong&gt;——这正是今天 APT（高级持续性威胁）概念的雏形。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;：你的系统被入侵后可能&lt;strong&gt;没有任何动静&lt;/strong&gt;——不删数据、不搞破坏，只是安静地拷贝。只依赖&amp;quot;有没有出事&amp;quot;来判断安全状况是靠不住的，需要主动审计&amp;quot;谁访问了什么&amp;quot;。&lt;/p&gt;
&lt;h2 id=&#34;第-3-章-来自德州监狱的入侵&#34;&gt;第 3 章 来自德州监狱的入侵&lt;/h2&gt;
&lt;p&gt;本章主角 William 的身份令人大跌眼镜：他是监狱系统的&lt;strong&gt;内部人员&lt;/strong&gt;。故事展示了&amp;quot;内部威胁&amp;quot;的经典形态——有权限的人滥用权限，外部防火墙形同虚设。&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;graph LR
    subgraph 防御体系[&amp;#34;企业的防御体系&amp;#34;]
        FW[&amp;#34;防火墙 / 入侵检测&amp;lt;br/&amp;gt;（防外面的人）&amp;#34;]
        ACL[&amp;#34;权限系统&amp;lt;br/&amp;gt;（信任里面的人）&amp;#34;]
    end
    INS[&amp;#34;内部人员 William&amp;lt;br/&amp;gt;已有合法权限&amp;#34;] --&amp;gt;|&amp;#34;权限滥用&amp;lt;br/&amp;gt;不被审计&amp;#34;| DATA[&amp;#34;敏感数据&amp;#34;]
    FW -.挡不住.- INS
    ACL -.默认信任.- INS
    style INS fill:#fce8e6&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;：安全模型不能只画一条&amp;quot;内外边界&amp;quot;。最小权限原则、操作审计、职责分离（一个人不能既管系统又管审计）是对付内部威胁的三板斧。&lt;/p&gt;
&lt;h2 id=&#34;第-4-章-警察与入侵者&#34;&gt;第 4 章 警察与入侵者&lt;/h2&gt;
&lt;p&gt;一个颇具戏剧性的故事：一名执法系统的人员利用自己的技术能力进行未授权访问。和第 3 章类似，它再次印证&amp;quot;&lt;strong&gt;堡垒最容易从内部攻破&lt;/strong&gt;&amp;quot;，并多了一个维度：当掌握调查权力的人自己越界时，发现与追责都更加困难。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;：对高权限岗位（管理员、执法人员、DBA）必须有独立监督——&amp;ldquo;信任但要核查&amp;rdquo;（trust but verify）。&lt;/p&gt;
&lt;h2 id=&#34;第-5-章-黑客中的罗宾汉adrian-lamo-传奇&#34;&gt;第 5 章 黑客中的&amp;quot;罗宾汉&amp;quot;：Adrian Lamo 传奇&lt;/h2&gt;
&lt;p&gt;全书最著名的一章，主角是真实人物 Adrian Lamo——被称为&amp;quot;流浪黑客&amp;quot;（他睡沙发、用咖啡馆的网络）。他的猎物名单堪称豪华：&lt;strong&gt;《纽约时报》、MCI WorldCom、Yahoo、微软&lt;/strong&gt;……&lt;/p&gt;
&lt;p&gt;他的手法简单到令人难以置信：&lt;strong&gt;不利用任何软件漏洞，只利用配置错误&lt;/strong&gt;。他在网上搜索开放代理（open proxy）服务器——这些服务器本来用于内部网络访问控制，却错误地面向公网开放。通过开放代理，他仿佛&amp;quot;从公司内网&amp;quot;发起访问，直接进入了《纽约时报》的内部系统，甚至能查阅该报专栏作家的个人信息。&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 Adrian Lamo
    participant P as 开放代理服务器&amp;lt;br/&amp;gt;（配置错误，公网可用）
    participant I as 目标公司内部系统&amp;lt;br/&amp;gt;（只信任内网来源）
    A-&amp;gt;&amp;gt;P: 连接（来自公网，被放行）
    P-&amp;gt;&amp;gt;I: 转发请求（源 IP = 内网代理）
    I-&amp;gt;&amp;gt;I: 信任检查通过&amp;lt;br/&amp;gt;“这是自己人”
    I--&amp;gt;&amp;gt;P: 内部数据
    P--&amp;gt;&amp;gt;A: 数据到手&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;他入侵后常做的两件事也让他声名狼藉又颇具争议：主动联系媒体曝光，以及向被入侵的公司&amp;quot;自首&amp;quot;并帮忙修复。最终他因入侵《纽约时报》被判刑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;基于来源 IP 的信任&amp;quot;极其脆弱&lt;/strong&gt;：一个暴露在公网的代理就把整个内网信任体系击穿；&lt;/li&gt;
&lt;li&gt;攻击面里最大的往往是&lt;strong&gt;配置错误&lt;/strong&gt;，而不是 0day 漏洞。定期从外部视角扫描自己的暴露面（开放的代理、VPN、测试机）是最划算的投入。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;第-6-章-渗透测试的智慧与愚蠢&#34;&gt;第 6 章 渗透测试的智慧与愚蠢&lt;/h2&gt;
&lt;p&gt;这一章米特尼克亲自下场，讲的是&amp;rdquo;&lt;strong&gt;雇佣兵&lt;/strong&gt;&amp;ldquo;视角：企业花钱请安全公司做渗透测试（pentest），合法地模拟攻击。章节中出现了著名工具 l0phtCrack（Windows 口令破解）的身影。&lt;/p&gt;
&lt;p&gt;故事里的经典桥段：渗透团队白天被客户保安盯得死死的，晚上一个电话打给值夜班的新员工，自称&amp;quot;总部 IT 支持&amp;rdquo;，就套出了拨号接入号码和口令。&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 技术路线[&amp;#34;正面技术攻击&amp;#34;]
        T1[&amp;#34;端口扫描&amp;#34;] --&amp;gt; T2[&amp;#34;漏洞利用&amp;#34;] --&amp;gt; T3[&amp;#34;遇到补丁齐全的系统&amp;lt;br/&amp;gt;进展缓慢&amp;#34;]
    end
    subgraph 社工路线[&amp;#34;社会工程侧门&amp;#34;]
        S1[&amp;#34;收集组织架构&amp;#34;] --&amp;gt; S2[&amp;#34;伪装 IT 支持&amp;lt;br/&amp;gt;打电话给夜班员工&amp;#34;]
        S2 --&amp;gt; S3[&amp;#34;获得接入点 + 口令&amp;lt;br/&amp;gt;几分钟搞定&amp;#34;]
    end
    技术路线 --&amp;gt; R[&amp;#34;拿到域控&amp;#34;]
    社工路线 --&amp;gt; R
    style 社工路线 fill:#fce8e6&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;（也是米特尼克两本书的共同主题）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;人是最薄弱的环节&lt;/strong&gt;，技术上把门焊死，不如培训接电话的人；&lt;/li&gt;
&lt;li&gt;渗透测试的价值取决于范围是否包含&amp;quot;人&amp;quot;——只扫端口的 pentest 会给出虚假的安全感；&lt;/li&gt;
&lt;li&gt;口令强度策略不是摆设：l0phtCrack 之类的工具能在几分钟内破解弱口令哈希。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;第-7-章-你的银行账户安全吗&#34;&gt;第 7 章 你的银行账户安全吗&lt;/h2&gt;
&lt;p&gt;本章围绕银行系统展开（涉及化名 Perogie 的人物）。银行是防御最严密的行业之一，但故事展示了攻击者的迂回思路：&lt;strong&gt;正面攻不进核心系统，就从外围的第三方、分支机构、合作伙伴入手&lt;/strong&gt;——供应链的薄弱环节是整个链条的安全水位。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;：你的安全边界不止于你的机房。第三方供应商、外包运维、合作机构的接入，都应该按&amp;quot;不可信&amp;quot;标准做隔离和最小授权。&lt;/p&gt;
&lt;h2 id=&#34;第-8-章-你的知识产权岌岌可危&#34;&gt;第 8 章 你的知识产权岌岌可危&lt;/h2&gt;
&lt;p&gt;篇幅最长、技术含量最高的一章，讲述对一家科技公司（化名，涉及 CEO 层面的窃密）的入侵，主角 Robert。本章几乎是一份 Web 入侵教科书，两个关键片段：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① helpdesk.exe：一个被信任的木马&lt;/strong&gt;。攻击者先通过社会工程了解公司内部使用的工具，然后精心制作了一个伪装成内部 helpdesk 工具的程序，诱导员工运行——因为&amp;quot;看起来就是自己人发的东西&amp;quot;，毫无戒心。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② SQL 注入拿下数据库&lt;/strong&gt;。应用把用户输入直接拼进 SQL 语句，攻击者通过精心构造的输入绕过验证、拖走数据：&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 R as Robert（攻击者）
    participant W as Web 应用
    participant DB as 数据库
    R-&amp;gt;&amp;gt;W: 登录框输入构造的字符串&amp;lt;br/&amp;gt;（含 SQL 片段）
    W-&amp;gt;&amp;gt;W: 直接拼接 SQL（未参数化）
    W-&amp;gt;&amp;gt;DB: SELECT * FROM users WHERE name=&amp;#39;&amp;#39; OR &amp;#39;1&amp;#39;=&amp;#39;1&amp;#39; ...
    DB--&amp;gt;&amp;gt;W: 返回全部记录
    W--&amp;gt;&amp;gt;R: 认证被绕过/数据外泄&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;拿到立足点之后是典型的内网横向移动：提权 → 抓取口令哈希 → 访问文件服务器 → 窃取源代码和设计文档。书中还提到了通过 DNS 通道、Microsoft VPN、MS SQL 配置弱点等多种进出通道。&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;#34;社工投递 helpdesk.exe&amp;lt;br/&amp;gt;或 SQL 注入&amp;#34;] --&amp;gt; B[&amp;#34;获得内网立足点&amp;#34;]
    B --&amp;gt; C[&amp;#34;本地提权&amp;lt;br/&amp;gt;抓取口令哈希&amp;#34;]
    C --&amp;gt; D[&amp;#34;横向移动&amp;lt;br/&amp;gt;文件服务器 / 代码库&amp;#34;]
    D --&amp;gt; E[&amp;#34;定位知识产权&amp;lt;br/&amp;gt;源代码、设计文档&amp;#34;]
    E --&amp;gt; F[&amp;#34;经 DNS/VPN 等&amp;lt;br/&amp;gt;隐蔽通道外传&amp;#34;]
    style A fill:#fce8e6
    style F fill:#fce8e6&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL 注入的根治方法只有一个：&lt;strong&gt;参数化查询&lt;/strong&gt;，任何拼接都是定时炸弹；&lt;/li&gt;
&lt;li&gt;员工运行&amp;quot;内部工具&amp;quot;前要验证来源（代码签名、白名单）；&lt;/li&gt;
&lt;li&gt;数据外发监控（DNS 异常查询、VPN 异常流量）是最后一道防线；&lt;/li&gt;
&lt;li&gt;知识产权的存放需要分区分级——不是所有人都能访问所有代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;第-9-章-人在大陆&#34;&gt;第 9 章 人在大陆&lt;/h2&gt;
&lt;p&gt;本章讲述了一起牵涉跨国线索的入侵事件（故事中出现 3Com 公司）。对今天读者的意义在于：攻击者与受害者可以分处地球两端，&lt;strong&gt;网络攻击天然跨国界&lt;/strong&gt;，取证、追责都极其困难。这也解释了为什么书里大量攻击者最终没有被法律制裁——不是查不到，而是司法管辖鞭长莫及。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防御启示&lt;/strong&gt;：既然事后追责很难，防御重心必须前移——让攻击者进不来、拿不走、带不出。&lt;/p&gt;
&lt;h2 id=&#34;第-10-章-社交工程师的骗局&#34;&gt;第 10 章 社交工程师的骗局&lt;/h2&gt;
&lt;p&gt;压轴章节回到米特尼克的老本行：&lt;strong&gt;社会工程&lt;/strong&gt;。这一章更像骗术纪实——高明的骗子根本不需要碰键盘，几部电话、一点心理学、对大公司流程的了解，就足以让员工亲手交出钥匙。&lt;/p&gt;
&lt;p&gt;米特尼克借这些故事总结了社会工程师的通用手法：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;① 调研&amp;lt;br/&amp;gt;摸清组织架构、&amp;lt;br/&amp;gt;内部术语、流程&amp;#34;] --&amp;gt; B[&amp;#34;② 建立信任&amp;lt;br/&amp;gt;伪装身份：IT 支持、&amp;lt;br/&amp;gt;高管助理、供应商&amp;#34;]
    B --&amp;gt; C[&amp;#34;③ 制造紧迫感&amp;lt;br/&amp;gt;&amp;#39;老板等着要&amp;#39;&amp;lt;br/&amp;gt;&amp;#39;系统马上宕机&amp;#39;&amp;#34;]
    C --&amp;gt; D[&amp;#34;④ 提出请求&amp;lt;br/&amp;gt;口令 / 接入方式 / 内部文档&amp;#34;]
    D --&amp;gt; E[&amp;#34;⑤ 抹除痕迹&amp;lt;br/&amp;gt;道谢、闲聊，&amp;lt;br/&amp;gt;让对方觉得帮了个小忙&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;破解之道也朴素：&lt;strong&gt;任何索要敏感信息的请求，先验证身份再执行&lt;/strong&gt;——回拨官方号码、走工单流程、向主管确认。骗术对抗不了流程。&lt;/p&gt;
&lt;h2 id=&#34;第-11-章-防御者的总结&#34;&gt;第 11 章 防御者的总结&lt;/h2&gt;
&lt;p&gt;最后一章把全书的对策收敛成面向企业的防御清单，核心思想是&lt;strong&gt;纵深防御（Defense in Depth）&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;graph TD
    L1[&amp;#34;第一层：人&amp;lt;br/&amp;gt;安全意识培训、验证流程、最小权限&amp;#34;]
    L2[&amp;#34;第二层：边界&amp;lt;br/&amp;gt;防火墙、关闭不必要服务、修复配置错误&amp;#34;]
    L3[&amp;#34;第三层：主机与应用&amp;lt;br/&amp;gt;补丁管理、参数化查询、口令策略&amp;#34;]
    L4[&amp;#34;第四层：数据&amp;lt;br/&amp;gt;分级、加密、外发监控&amp;#34;]
    L5[&amp;#34;第五层：检测与响应&amp;lt;br/&amp;gt;日志审计、异常行为分析、应急预案&amp;#34;]
    L1 --&amp;gt; L2 --&amp;gt; L3 --&amp;gt; L4 --&amp;gt; L5
    style L1 fill:#fce8e6
    style L5 fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&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;th&gt;防御关键词&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;赌场视频扑克机&lt;/td&gt;
					&lt;td&gt;PRNG 可预测 + ROM 逆向&lt;/td&gt;
					&lt;td&gt;密码学随机数、别依赖算法保密&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;恐怖分子网络&lt;/td&gt;
					&lt;td&gt;长期潜伏窃密&lt;/td&gt;
					&lt;td&gt;审计&amp;quot;谁访问了什么&amp;quot;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&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;4&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;5&lt;/td&gt;
					&lt;td&gt;Adrian Lamo&lt;/td&gt;
					&lt;td&gt;开放代理配置错误&lt;/td&gt;
					&lt;td&gt;外部视角攻击面扫描&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;6&lt;/td&gt;
					&lt;td&gt;渗透测试&lt;/td&gt;
					&lt;td&gt;社工电话 + 弱口令&lt;/td&gt;
					&lt;td&gt;把&amp;quot;人&amp;quot;纳入测试范围&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;7&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;8&lt;/td&gt;
					&lt;td&gt;知识产权窃密&lt;/td&gt;
					&lt;td&gt;木马 + SQL 注入 + 横向移动&lt;/td&gt;
					&lt;td&gt;参数化查询、代码签名、外发监控&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;9&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;10&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;/tbody&gt;
&lt;/table&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;：防火墙解决的是&amp;quot;外面的人&amp;quot;，审计和最小权限解决的是&amp;quot;里面的人&amp;quot;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个故事结尾的&amp;quot;对策&amp;quot;都比故事本身值钱&lt;/strong&gt;：这本书写于 2005 年，但那些对策（最小权限、参数化查询、纵深防御、身份验证流程）今天依然是安全建设的地基。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考资料：Kevin D. Mitnick, William L. Simon. 《The Art of Intrusion: The Real Stories Behind the Exploits of Hackers, Intruders and Deceivers》. Wiley, 2005. 中译本《入侵的艺术》。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title> TCP/IP 学习总结与避坑指南</title>
      <link>https://ftmao.com/blog/bookstore/tcp-ip-illustrated-notes/</link>
      <pubDate>Sat, 09 Dec 2017 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/tcp-ip-illustrated-notes/</guid>
      <description>&lt;h1 id=&#34;tcpip-学习总结与避坑指南&#34;&gt;TCP/IP 学习总结与避坑指南&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;最近华为项目交付碰到各种头大的网络问题，现在整理下TCP/IP相关知识。参考书籍：W. Richard Stevens《TCP/IP 详解 卷1：协议》、W. Richard Stevens《Unix 网络编程 卷1：套接字联网 API》&lt;br&gt;
目标读者：刚接触网络编程，或者学了很多概念但总觉得「协议栈串不起来」「代码一跑就掉坑」的同学。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;TCP/IP 不是一门「背完就能会」的技术。它由一整套分层协议组成，每一层只关心自己的事情，又通过严格定义的头部把信息交给下一层。理解这套分层思维，是看懂网络报文、写出稳定网络程序、排查线上连接问题的第一步。&lt;/p&gt;
&lt;p&gt;这篇文章不会把所有 RFC 都讲一遍（那是 1000 多页书做的事），而是帮你建立&lt;strong&gt;主干知识框架&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCP/IP 协议栈为什么要分层？数据是怎么一层层包起来的？&lt;/li&gt;
&lt;li&gt;IP、TCP、UDP、ARP、ICMP、DNS 这些协议各自解决什么问题？&lt;/li&gt;
&lt;li&gt;Unix Socket 编程的核心 API 与调用顺序是什么？&lt;/li&gt;
&lt;li&gt;写网络程序最容易踩哪些坑，怎么避开？&lt;/li&gt;
&lt;li&gt;遇到连接失败、卡顿、端口耗尽这些问题，该按什么思路排查？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;读完之后，你应该能看懂 Wireshark 里的大部分常见报文，也能独立写出稳定的 TCP 客户端/服务端，并具备基础的线上网络问题排查能力。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一tcpip-协议栈为什么必须分层&#34;&gt;一、TCP/IP 协议栈：为什么必须分层？&lt;/h2&gt;
&lt;p&gt;网络通信的复杂度非常高：不同的物理介质、不同的操作系统、不同的应用程序要在全球范围内交换数据。如果不分层，每一台设备都要同时处理「电信号怎么传」「数据怎么路由」「传输可不可靠」「应用数据什么意思」所有问题。&lt;/p&gt;
&lt;p&gt;分层的目的就是&lt;strong&gt;关注点分离&lt;/strong&gt;：每一层只解决本层的问题，向上层提供服务，把复杂问题拆成可管理的小块。&lt;/p&gt;
&lt;h3 id=&#34;11-osi-七层模型-vs-tcpip-四层模型&#34;&gt;1.1 OSI 七层模型 vs TCP/IP 四层模型&lt;/h3&gt;
&lt;p&gt;教学中常用 OSI 七层模型，但互联网实际运行的是 TCP/IP 四层模型。&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;graph LR
    subgraph OSI七层模型
        A7[7 应用层] --&amp;gt; A6[6 表示层]
        A6 --&amp;gt; A5[5 会话层]
        A5 --&amp;gt; A4[4 传输层]
        A4 --&amp;gt; A3[3 网络层]
        A3 --&amp;gt; A2[2 数据链路层]
        A2 --&amp;gt; A1[1 物理层]
    end

    subgraph TCP/IP四层模型
        B4[应用层&amp;lt;br/&amp;gt;HTTP/FTP/DNS/SMTP] --&amp;gt; B3[传输层&amp;lt;br/&amp;gt;TCP/UDP]
        B3 --&amp;gt; B2[网络层&amp;lt;br/&amp;gt;IP/ICMP/IGMP]
        B2 --&amp;gt; B1[网络接口层&amp;lt;br/&amp;gt;以太网/WiFi/PPP]
    end

    A7 -.对应.-&amp;gt; B4
    A6 -.对应.-&amp;gt; B4
    A5 -.对应.-&amp;gt; B4
    A4 -.对应.-&amp;gt; B3
    A3 -.对应.-&amp;gt; B2
    A2 -.对应.-&amp;gt; B1
    A1 -.对应.-&amp;gt; B1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;TCP/IP 层&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;HTTP、DNS、FTP、SMTP、SSH&lt;/td&gt;
					&lt;td&gt;应用程序之间「说什么」&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;传输层&lt;/td&gt;
					&lt;td&gt;TCP、UDP&lt;/td&gt;
					&lt;td&gt;进程到进程的通信，可靠或高效传输&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;网络层&lt;/td&gt;
					&lt;td&gt;IP、ICMP、IGMP、路由协议&lt;/td&gt;
					&lt;td&gt;主机到主机的寻址与路由&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;网络接口层&lt;/td&gt;
					&lt;td&gt;以太网、WiFi、ARP、PPP&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;：TCP/IP 里的「应用层」实际上对应了 OSI 的应用层、表示层、会话层三层。不要死磕七层，实际工程里四层模型更常用。&lt;/p&gt;
&lt;h3 id=&#34;12-数据封装与解封装&#34;&gt;1.2 数据封装与解封装&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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 发送方
        A[应用数据] --&amp;gt;|&amp;#34;应用层封装&amp;#34;| B[&amp;#34;应用层头部 + 应用数据&amp;#34;]
        B --&amp;gt;|&amp;#34;传输层封装&amp;#34;| C[&amp;#34;TCP/UDP 头部 + 应用层数据&amp;#34;]
        C --&amp;gt;|&amp;#34;网络层封装&amp;#34;| D[&amp;#34;IP 头部 + TCP 数据段&amp;#34;]
        D --&amp;gt;|&amp;#34;链路层封装&amp;#34;| E[&amp;#34;以太网头部 + IP 数据报 + 以太网尾部&amp;#34;]
    end

    E --&amp;gt;|&amp;#34;比特流在物理介质传输&amp;#34;| F

    subgraph 接收方
        F[&amp;#34;以太网帧&amp;#34;] --&amp;gt;|&amp;#34;链路层解封装&amp;#34;| G[&amp;#34;IP 数据报&amp;#34;]
        G --&amp;gt;|&amp;#34;网络层解封装&amp;#34;| H[&amp;#34;TCP/UDP 数据段&amp;#34;]
        H --&amp;gt;|&amp;#34;传输层解封装&amp;#34;| I[&amp;#34;应用层数据&amp;#34;]
        I --&amp;gt;|&amp;#34;应用层处理&amp;#34;| J[交给对应进程]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键概念&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MTU（Maximum Transmission Unit）&lt;/strong&gt;：链路层帧能承载的最大数据量，以太网常见值是 1500 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MSS（Maximum Segment Size）&lt;/strong&gt;：TCP 一次能发送的最大数据段，通常 = MTU - IP 头 - TCP 头，以太网常见 1460 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分片（Fragmentation）&lt;/strong&gt;：当 IP 数据报超过链路 MTU 时，IP 层会把它切成多个片。TCP 一般会通过 MSS 避免分片，UDP 则容易触发分片。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要随意修改 MTU，VPN/隧道场景特别容易出现「大数据包被静默丢弃」的问题。&lt;/li&gt;
&lt;li&gt;UDP 应用如果要传输大报文，建议在应用层自己拆包，不要让 IP 层分片。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二链路层与-arp同一局域网怎么找到对方&#34;&gt;二、链路层与 ARP：同一局域网怎么找到对方？&lt;/h2&gt;
&lt;p&gt;链路层负责&lt;strong&gt;相邻节点&lt;/strong&gt;之间的数据传输。在以太网里，设备用 MAC 地址标识，交换机根据 MAC 地址转发帧。&lt;/p&gt;
&lt;h3 id=&#34;21-以太网帧结构&#34;&gt;2.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;+----------------+----------------+----------------+----------------+----------------+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| 目的 MAC 6B    | 源 MAC 6B      | 类型 2B        | 数据 46~1500B  | FCS 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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MAC 地址&lt;/strong&gt;：48 位，全球唯一（理论上），用于局域网内定位网卡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型字段&lt;/strong&gt;：例如 &lt;code&gt;0x0800&lt;/code&gt; 表示上层是 IP，&lt;code&gt;0x0806&lt;/code&gt; 表示 ARP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FCS（Frame Check Sequence）&lt;/strong&gt;：帧校验序列，检测传输过程中的比特错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;22-arpip-地址--mac-地址&#34;&gt;2.2 ARP：IP 地址 → MAC 地址&lt;/h3&gt;
&lt;p&gt;ARP（Address Resolution Protocol）解决的是「知道对方 IP，但不知道对方 MAC」的问题。&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 主机 A&amp;lt;br/&amp;gt;192.168.1.10
    participant B as 主机 B&amp;lt;br/&amp;gt;192.168.1.20

    A-&amp;gt;&amp;gt;B: ARP 请求：谁的 IP 是 192.168.1.20？请告诉我你的 MAC
    Note over A,B: 广播帧，局域网内所有主机收到
    B-&amp;gt;&amp;gt;A: ARP 应答：我是 192.168.1.20，MAC 是 xx:xx:xx:xx:xx:xx
    Note over A,B: 单播帧，只有 A 收到&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;ARP 表会缓存一段时间：&lt;code&gt;arp -a&lt;/code&gt; 可以查看本机 ARP 缓存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ARP 只在&lt;strong&gt;同一二层网络&lt;/strong&gt;（同一广播域）有效。跨网段通信时，主机把报文发给网关，由网关继续转发。&lt;/li&gt;
&lt;li&gt;网关配错是新手常见问题：如果默认网关不在同一子网，ARP 请求会失败，数据根本发不出去。&lt;/li&gt;
&lt;li&gt;ARP 欺骗/中间人攻击在局域网内存在，敏感环境要开启动态 ARP 检测（DAI）或静态绑定。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三ip-协议主机到主机的寻址与路由&#34;&gt;三、IP 协议：主机到主机的寻址与路由&lt;/h2&gt;
&lt;p&gt;IP（Internet Protocol）是网络层的核心，负责把数据从源主机送到目的主机。它&lt;strong&gt;不保证可靠、不保证有序&lt;/strong&gt;，这些由 TCP 或应用层自己处理。&lt;/p&gt;
&lt;h3 id=&#34;31-ipv4-头部核心字段&#34;&gt;3.1 IPv4 头部核心字段&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|  IHL  |Type of Service|          Total Length         |
&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;|         Identification        |Flags|      Fragment Offset    |
&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;|  Time to Live |    Protocol   |         Header Checksum       |
&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;|                       Source Address                          |
&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;|                    Destination Address                        |
&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;|                    Options                    |    Padding    |
&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;关键字段含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version&lt;/strong&gt;：IPv4 填 4，IPv6 填 6。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IHL（Header Length）&lt;/strong&gt;：头部长度，单位是 4 字节。没有选项时最小是 5，即 20 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total Length&lt;/strong&gt;：整个 IP 数据报的长度，最大 65535 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identification / Flags / Fragment Offset&lt;/strong&gt;：用于分片和重组。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTL（Time to Live）&lt;/strong&gt;：每经过一个路由器减 1，到 0 被丢弃，防止报文在网络里无限循环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protocol&lt;/strong&gt;：上层协议类型。6 = TCP，17 = UDP，1 = ICMP，等等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source/Destination Address&lt;/strong&gt;：源 IP 和目的 IP。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-ip-地址子网掩码与-cidr&#34;&gt;3.2 IP 地址、子网掩码与 CIDR&lt;/h3&gt;
&lt;p&gt;IP 地址 = 网络号 + 主机号。子网掩码用来划分这两部分。&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;IP 地址：    192.168.1.100   → 11000000.10101000.00000001.01100100
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;子网掩码：   255.255.255.0   → 11111111.11111111.11111111.00000000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;网络号：     192.168.1.0     → 前 24 位
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;主机号：     0.0.0.100       → 后 8 位&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;CIDR 表示法：&lt;code&gt;192.168.1.0/24&lt;/code&gt;，斜杠后面的数字表示网络号位数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;判断两台主机是否在同一子网&lt;/strong&gt;：用各自的 IP 和子网掩码做按位与，得到网络号，网络号相同就在同一子网。&lt;/p&gt;
&lt;h3 id=&#34;33-路由基础&#34;&gt;3.3 路由基础&lt;/h3&gt;
&lt;p&gt;当目的 IP 不在本子网时，主机会把报文发给&lt;strong&gt;默认网关&lt;/strong&gt;（Default Gateway）。路由器根据路由表决定下一跳。&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[主机 A&amp;lt;br/&amp;gt;192.168.1.10/24] --&amp;gt;|&amp;#34;目的 IP 不在同一子网&amp;#34;| R[路由器&amp;lt;br/&amp;gt;192.168.1.1]
    R --&amp;gt;|&amp;#34;查路由表&amp;#34;| B[主机 B&amp;lt;br/&amp;gt;10.0.0.5/24]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Linux/macOS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ip route show        &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;route -n             &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 style=&#34;color:#75715e&#34;&gt;# Windows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;route print&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置静态 IP 时，最容易漏掉或配错的就是&lt;strong&gt;子网掩码&lt;/strong&gt;和&lt;strong&gt;默认网关&lt;/strong&gt;。只配 IP 不配上两者，通常只能 ping 通同网段。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/24&lt;/code&gt; 和 &lt;code&gt;/16&lt;/code&gt; 的区别不只是数字大小，它决定了网络规模和可用主机数。&lt;code&gt;/24&lt;/code&gt; 有 254 个可用主机地址，&lt;code&gt;/16&lt;/code&gt; 有 65534 个。&lt;/li&gt;
&lt;li&gt;路由表顺序很重要：更具体的子网（前缀更长）优先于默认路由。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四icmpip-的诊断助手&#34;&gt;四、ICMP：IP 的「诊断助手」&lt;/h2&gt;
&lt;p&gt;ICMP（Internet Control Message Protocol）本身不传输用户数据，而是用于&lt;strong&gt;传递控制消息和诊断信息&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;41-ping-原理&#34;&gt;4.1 Ping 原理&lt;/h3&gt;
&lt;p&gt;Ping 使用 ICMP Echo Request 和 Echo Reply。&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 主机 A
    participant B as 主机 B

    A-&amp;gt;&amp;gt;B: ICMP Echo Request
    Note over A,B: 携带标识符和序列号
    B-&amp;gt;&amp;gt;A: ICMP Echo Reply
    Note over A,B: A 根据往返时间计算延迟&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;Ping 不通不一定是对端关机，可能是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;防火墙拦截了 ICMP&lt;/li&gt;
&lt;li&gt;路由不可达&lt;/li&gt;
&lt;li&gt;对端 ARP 表异常&lt;/li&gt;
&lt;li&gt;本机默认网关配错&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-traceroute-原理&#34;&gt;4.2 Traceroute 原理&lt;/h3&gt;
&lt;p&gt;Traceroute 利用 TTL 递增或端口不可达来追踪路径。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[主机] --&amp;gt;|&amp;#34;TTL=1&amp;#34;| R1[路由器 1]
    R1 --&amp;gt;|&amp;#34;TTL 超时，返回 ICMP Time Exceeded&amp;#34;| A
    A --&amp;gt;|&amp;#34;TTL=2&amp;#34;| R2[路由器 2]
    R2 --&amp;gt;|&amp;#34;TTL 超时，返回 ICMP Time Exceeded&amp;#34;| A
    A --&amp;gt;|&amp;#34;TTL=3&amp;#34;| B[目标主机]
    B --&amp;gt;|&amp;#34;端口不可达 / Echo Reply&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有些路由器配置了「不回应 ICMP Time Exceeded」，Traceroute 会显示 &lt;code&gt;* * *&lt;/code&gt;，不代表网络不通。&lt;/li&gt;
&lt;li&gt;防火墙可能会阻断 Traceroute 的 UDP 探测端口，换 &lt;code&gt;-I&lt;/code&gt;（ICMP）或 &lt;code&gt;-T&lt;/code&gt;（TCP）模式可能成功。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五udp简单高效不可靠&#34;&gt;五、UDP：简单、高效、不可靠&lt;/h2&gt;
&lt;p&gt;UDP（User Datagram Protocol）提供&lt;strong&gt;无连接、不可靠、面向数据报&lt;/strong&gt;的传输服务。&lt;/p&gt;
&lt;h3 id=&#34;51-udp-头部&#34;&gt;5.1 UDP 头部&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      7 8     15 16    23 24    31
&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;|     Source Port    |   Dest Port   |
&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;|     Length         |   Checksum    |
&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;|          data octets ...            |
&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;Source Port / Dest Port&lt;/strong&gt;：各 16 位，标识发送和接收进程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Length&lt;/strong&gt;：UDP 头部 + 数据的总长度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checksum&lt;/strong&gt;：可选（IPv4 中），覆盖头部、数据和伪头部。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;52-udp-的特点与适用场景&#34;&gt;5.2 UDP 的特点与适用场景&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特点&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&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;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;低开销&lt;/td&gt;
					&lt;td&gt;头部只有 8 字节&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;面向数据报&lt;/td&gt;
					&lt;td&gt;发一次收一次，保留边界&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;适合用 UDP 的场景&lt;/strong&gt;：DNS 查询、视频直播、在线游戏、VoIP、NTP、QUIC 底层。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UDP 没有流量控制，发送方太快会导致接收方丢包。实时音视频要做抖动缓冲（jitter buffer）和前向纠错（FEC）。&lt;/li&gt;
&lt;li&gt;UDP 报文超过 MTU 会被 IP 层分片，分片丢失会导致整个 UDP 报文作废。大数据包要在应用层分片。&lt;/li&gt;
&lt;li&gt;不要认为 UDP 一定比 TCP 快。在丢包率高的网络里，应用层自己实现可靠传输可能比 TCP 更复杂且效果更差。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六tcp可靠传输的基石&#34;&gt;六、TCP：可靠传输的基石&lt;/h2&gt;
&lt;p&gt;TCP（Transmission Control Protocol）是互联网最常用的传输协议。它提供&lt;strong&gt;面向连接、可靠、按序、字节流&lt;/strong&gt;的服务。&lt;/p&gt;
&lt;h3 id=&#34;61-tcp-头部核心字段&#34;&gt;6.1 TCP 头部核心字段&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;|          Source Port          |       Destination Port        |
&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;|                    Acknowledgment 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;|  Data |           |U|A|P|R|S|F|                               |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;| Offset| Reserved  |R|C|S|S|Y|I|            Window             |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;|       |           |G|K|H|T|N|N|                               |
&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;|           Checksum            |         Urgent Pointer        |
&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;|                    Options                    |    Padding    |
&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;关键字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sequence Number（序号）&lt;/strong&gt;：标识本报文段发送的数据在字节流中的位置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acknowledgment Number（确认号）&lt;/strong&gt;：期望收到的下一个字节的序号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACK&lt;/strong&gt;：确认号有效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN&lt;/strong&gt;：发起连接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN&lt;/strong&gt;：结束连接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RST&lt;/strong&gt;：重置连接，通常表示异常。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Window（窗口大小）&lt;/strong&gt;：接收方当前可用缓存大小，用于流量控制。&lt;/li&gt;
&lt;/ul&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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant C as 客户端
    participant S as 服务端

    C-&amp;gt;&amp;gt;S: SYN, seq=x
    Note over C,S: 客户端请求建立连接
    S-&amp;gt;&amp;gt;C: SYN, seq=y, ACK=x+1
    Note over C,S: 服务端同意，也请求建立反向连接
    C-&amp;gt;&amp;gt;S: ACK=y+1
    Note over C,S: 客户端确认，连接建立&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;为什么是三次？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两次握手无法确认客户端的接收能力，也不能防止历史重复连接请求造成资源浪费。&lt;/li&gt;
&lt;li&gt;三次是「双方都能发送和接收」的最小验证次数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关键状态&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端：SYN_SENT → ESTABLISHED&lt;/li&gt;
&lt;li&gt;服务端：LISTEN → SYN_RCVD → ESTABLISHED&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;63-四次挥手关闭连接&#34;&gt;6.3 四次挥手：关闭连接&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;sequenceDiagram
    participant C as 客户端
    participant S as 服务端

    C-&amp;gt;&amp;gt;S: FIN, seq=u
    Note over C,S: 客户端没有数据要发了
    S-&amp;gt;&amp;gt;C: ACK=u+1
    Note over C,S: 服务端确认，但可能还有数据要发
    S-&amp;gt;&amp;gt;C: FIN, seq=w
    Note over C,S: 服务端也没有数据要发了
    C-&amp;gt;&amp;gt;S: ACK=w+1
    Note over C,S: 客户端确认&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;为什么一般是四次？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TCP 是全双工的，两个方向要分别关闭。一方发完 FIN 只表示「我这方向没数据了」，另一方可能还有数据要发。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键状态与时间等待&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主动关闭方最后进入 &lt;strong&gt;TIME_WAIT&lt;/strong&gt;，等待 2MSL（Maximum Segment Lifetime，通常 1-4 分钟）后才彻底关闭。&lt;/li&gt;
&lt;li&gt;TIME_WAIT 的作用：
&lt;ol&gt;
&lt;li&gt;确保最后一个 ACK 被对方收到，如果丢失可以重传。&lt;/li&gt;
&lt;li&gt;让网络中延迟的报文自然消失，避免影响下一个使用相同四元组的新连接。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;64-tcp-状态机&#34;&gt;6.4 TCP 状态机&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; CLOSED
    CLOSED --&amp;gt; SYN_SENT : 主动打开/send SYN
    CLOSED --&amp;gt; LISTEN : 被动打开
    SYN_SENT --&amp;gt; ESTABLISHED : 收到 SYN+ACK
    LISTEN --&amp;gt; SYN_RCVD : 收到 SYN
    SYN_RCVD --&amp;gt; ESTABLISHED : 收到 ACK
    ESTABLISHED --&amp;gt; FIN_WAIT_1 : 主动关闭/send FIN
    ESTABLISHED --&amp;gt; CLOSE_WAIT : 收到 FIN
    FIN_WAIT_1 --&amp;gt; FIN_WAIT_2 : 收到 ACK
    FIN_WAIT_1 --&amp;gt; TIME_WAIT : 收到 FIN+ACK
    FIN_WAIT_2 --&amp;gt; TIME_WAIT : 收到 FIN
    CLOSE_WAIT --&amp;gt; LAST_ACK : 发送 FIN
    LAST_ACK --&amp;gt; CLOSED : 收到 ACK
    TIME_WAIT --&amp;gt; CLOSED : 2MSL 超时&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;65-可靠传输机制&#34;&gt;6.5 可靠传输机制&lt;/h3&gt;
&lt;p&gt;TCP 通过以下机制保证可靠性：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[序号与确认机制] --&amp;gt; B[超时重传]
    C[滑动窗口] --&amp;gt; D[流量控制]
    E[拥塞窗口] --&amp;gt; F[拥塞控制]
    B --&amp;gt; G[可靠传输]
    D --&amp;gt; G
    F --&amp;gt; G&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;序号与确认&lt;/strong&gt;：每发一个字节都有序号，接收方用 ACK 确认连续收到的最大序号。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;超时重传&lt;/strong&gt;：发送方启动定时器，超时未收到 ACK 就重传。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;滑动窗口&lt;/strong&gt;：允许发送方在没收到 ACK 的情况下连续发送多个段，提高吞吐量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流量控制&lt;/strong&gt;：接收方通过 Window 字段告诉发送方自己还能接收多少数据，防止接收方缓存溢出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;拥塞控制&lt;/strong&gt;：发送方根据网络拥塞程度调整发送速率，主要算法包括慢启动、拥塞避免、快重传、快恢复。&lt;/p&gt;
&lt;h3 id=&#34;66-拥塞控制四个阶段&#34;&gt;6.6 拥塞控制四个阶段&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
    A[慢启动&amp;lt;br/&amp;gt;cwnd 指数增长] --&amp;gt;|&amp;#34;cwnd &amp;gt;= ssthresh&amp;#34;| B[拥塞避免&amp;lt;br/&amp;gt;cwnd 线性增长]
    B --&amp;gt;|&amp;#34;收到 3 个重复 ACK&amp;#34;| C[快重传&amp;lt;br/&amp;gt;立即重传]
    C --&amp;gt; D[快恢复&amp;lt;br/&amp;gt;cwnd=ssthresh]
    D --&amp;gt; B
    B --&amp;gt;|&amp;#34;超时&amp;#34;| E[超时重传&amp;lt;br/&amp;gt;cwnd=1]
    E --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;慢启动（Slow Start）&lt;/strong&gt;：初始窗口小，每收到一个 ACK 窗口近似翻倍，直到达到慢启动阈值（ssthresh）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拥塞避免（Congestion Avoidance）&lt;/strong&gt;：超过阈值后，窗口线性增长。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快重传（Fast Retransmit）&lt;/strong&gt;：收到 3 个重复 ACK 就立刻重传，不等超时。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快恢复（Fast Recovery）&lt;/strong&gt;：快重传后不把窗口降到 1，而是降到 ssthresh，再线性增长。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;67-nagle-算法与延迟-ack&#34;&gt;6.7 Nagle 算法与延迟 ACK&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nagle 算法&lt;/strong&gt;：减少小报文数量。如果上一次发送的数据还没被确认，且当前数据量小于 MSS，就把小数据攒起来一起发。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟 ACK&lt;/strong&gt;：接收方不立即对每个收到的段发 ACK，而是等 40ms 左右，看是否有数据要一起回，减少 ACK 数量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nagle + 延迟 ACK 会造成明显延迟&lt;/strong&gt;：发送方等小 ACK，接收方等大数据一起回，形成「写 - 等 - 写 - 等」的 ping-pong 效应。&lt;/li&gt;
&lt;li&gt;交互式应用（如游戏、SSH、RPC）通常要关闭 Nagle：&lt;code&gt;setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &amp;amp;on, sizeof(on))&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;批量传输场景（文件下载）可以保留 Nagle，减少小包数量。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七dns-与-dhcp网络配置的基础设施&#34;&gt;七、DNS 与 DHCP：网络配置的基础设施&lt;/h2&gt;
&lt;h3 id=&#34;71-dns域名--ip&#34;&gt;7.1 DNS：域名 → IP&lt;/h3&gt;
&lt;p&gt;DNS（Domain Name System）是互联网的「电话簿」，把人类可读的域名解析成 IP 地址。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[客户端] --&amp;gt;|&amp;#34;查询 example.com&amp;#34;| B[本地 DNS 缓存]
    B --&amp;gt;|&amp;#34;未命中&amp;#34;| C[递归 DNS 服务器]
    C --&amp;gt;|&amp;#34;迭代查询&amp;#34;| D[根 DNS 服务器]
    D --&amp;gt; E[.com 顶级域服务器]
    E --&amp;gt; F[example.com 权威服务器]
    F --&amp;gt;|&amp;#34;返回 IP&amp;#34;| C
    C --&amp;gt;|&amp;#34;返回 IP&amp;#34;| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;DNS 记录类型&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;用途&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;A&lt;/td&gt;
					&lt;td&gt;域名 → IPv4 地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;AAAA&lt;/td&gt;
					&lt;td&gt;域名 → IPv6 地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CNAME&lt;/td&gt;
					&lt;td&gt;域名别名&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;MX&lt;/td&gt;
					&lt;td&gt;邮件交换服务器&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;NS&lt;/td&gt;
					&lt;td&gt;权威 DNS 服务器&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TXT&lt;/td&gt;
					&lt;td&gt;文本记录，常用于验证和 SPF&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DNS 有缓存，修改记录后全球生效可能要几分钟到 48 小时（受 TTL 控制）。&lt;/li&gt;
&lt;li&gt;注意 DNS 解析失败和连接超时的区别：先 &lt;code&gt;nslookup&lt;/code&gt;/&lt;code&gt;dig&lt;/code&gt; 确认域名能否解析，再 ping/telnet 确认网络层是否通。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gethostbyname&lt;/code&gt; 不是线程安全的，现代程序应使用 &lt;code&gt;getaddrinfo&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;72-dhcp自动获取网络配置&#34;&gt;7.2 DHCP：自动获取网络配置&lt;/h3&gt;
&lt;p&gt;DHCP（Dynamic Host Configuration Protocol）让主机自动获取 IP 地址、子网掩码、网关、DNS 服务器。&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 H as 主机
    participant D as DHCP 服务器

    H-&amp;gt;&amp;gt;D: DHCP Discover（广播）
    D-&amp;gt;&amp;gt;H: DHCP Offer
    H-&amp;gt;&amp;gt;D: DHCP Request
    D-&amp;gt;&amp;gt;H: DHCP ACK&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;八unix-网络编程核心socket-api&#34;&gt;八、Unix 网络编程核心：Socket API&lt;/h2&gt;
&lt;p&gt;Socket 是 Unix 系统提供的网络编程接口，屏蔽了底层协议细节，让程序像读写文件一样通信。&lt;/p&gt;
&lt;h3 id=&#34;81-tcp-服务端典型调用流程&#34;&gt;8.1 TCP 服务端典型调用流程&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
    A[socket] --&amp;gt; B[bind]
    B --&amp;gt; C[listen]
    C --&amp;gt; D[accept]
    D --&amp;gt; E[read/write]
    E --&amp;gt; F[close]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;82-tcp-客户端典型调用流程&#34;&gt;8.2 TCP 客户端典型调用流程&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
    A[socket] --&amp;gt; B[connect]
    B --&amp;gt; C[read/write]
    C --&amp;gt; D[close]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;83-核心-api-说明&#34;&gt;8.3 核心 API 说明&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;API&lt;/th&gt;
					&lt;th&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;socket()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;创建套接字，指定协议族（IPv4/IPv6）和类型（流式/数据报）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;bind()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;把套接字绑定到本地地址和端口&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;listen()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;把套接字置为监听状态，指定全连接队列长度&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;accept()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;从全连接队列取出一个已完成握手的连接&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;connect()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;客户端发起连接&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;read()/write()&lt;/code&gt; 或 &lt;code&gt;recv()/send()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;收发数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;close()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;关闭连接&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;shutdown()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;半关闭：只关闭读或写&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;84-最简-tcp-服务端示例&#34;&gt;8.4 最简 TCP 服务端示例&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdio.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdlib.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;string.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;unistd.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;arpa/inet.h&amp;gt;&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 style=&#34;color:#75715e&#34;&gt;#define PORT 8080
&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;#define BACKLOG 128
&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;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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;int&lt;/span&gt; listen_fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;socket&lt;/span&gt;(AF_INET, SOCK_STREAM, &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 style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (listen_fd &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&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 style=&#34;color:#a6e22e&#34;&gt;perror&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;socket&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:#a6e22e&#34;&gt;exit&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&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;int&lt;/span&gt; reuse &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:#a6e22e&#34;&gt;setsockopt&lt;/span&gt;(listen_fd, SOL_SOCKET, SO_REUSEADDR, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;reuse, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(reuse));
&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;struct&lt;/span&gt; sockaddr_in addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memset&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;addr, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(addr));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    addr.sin_family &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AF_INET;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    addr.sin_addr.s_addr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; INADDR_ANY;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    addr.sin_port &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;htons&lt;/span&gt;(PORT);
&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;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;bind&lt;/span&gt;(listen_fd, (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; sockaddr &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;addr, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(addr)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&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 style=&#34;color:#a6e22e&#34;&gt;perror&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bind&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:#a6e22e&#34;&gt;exit&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&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;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;listen&lt;/span&gt;(listen_fd, BACKLOG) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&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 style=&#34;color:#a6e22e&#34;&gt;perror&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;listen&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:#a6e22e&#34;&gt;exit&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&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:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Server listening on port %d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, PORT);
&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;while&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;struct&lt;/span&gt; sockaddr_in client_addr;
&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;socklen_t&lt;/span&gt; len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(client_addr);
&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;int&lt;/span&gt; conn_fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;accept&lt;/span&gt;(listen_fd, (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; sockaddr &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;client_addr, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;len);
&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; (conn_fd &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&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 style=&#34;color:#a6e22e&#34;&gt;perror&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;accept&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&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;char&lt;/span&gt; buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;1024&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;ssize_t&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(conn_fd, buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(buf) &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;if&lt;/span&gt; (n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;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;            buf[n] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\0&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:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Received: %s&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, buf);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(conn_fd, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;OK&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&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&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:#a6e22e&#34;&gt;close&lt;/span&gt;(conn_fd);
&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:#a6e22e&#34;&gt;close&lt;/span&gt;(listen_fd);
&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; &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;85-最简-tcp-客户端示例&#34;&gt;8.5 最简 TCP 客户端示例&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdio.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdlib.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;string.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;unistd.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;arpa/inet.h&amp;gt;&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 style=&#34;color:#75715e&#34;&gt;#define SERVER &amp;#34;127.0.0.1&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;#define PORT 8080
&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;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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;int&lt;/span&gt; fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;socket&lt;/span&gt;(AF_INET, SOCK_STREAM, &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 style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fd &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&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 style=&#34;color:#a6e22e&#34;&gt;perror&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;socket&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:#a6e22e&#34;&gt;exit&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&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;struct&lt;/span&gt; sockaddr_in addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memset&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;addr, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(addr));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    addr.sin_family &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AF_INET;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    addr.sin_port &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;htons&lt;/span&gt;(PORT);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;inet_pton&lt;/span&gt;(AF_INET, SERVER, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;addr.sin_addr);
&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;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;connect&lt;/span&gt;(fd, (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; sockaddr &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;addr, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(addr)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&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 style=&#34;color:#a6e22e&#34;&gt;perror&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;connect&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:#a6e22e&#34;&gt;exit&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&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:#a6e22e&#34;&gt;write&lt;/span&gt;(fd, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&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;char&lt;/span&gt; buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;1024&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;ssize_t&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(fd, buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(buf) &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;if&lt;/span&gt; (n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;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;        buf[n] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\0&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:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Server reply: %s&amp;#34;&lt;/span&gt;, buf);
&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:#a6e22e&#34;&gt;close&lt;/span&gt;(fd);
&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; &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;九unix-网络编程常见陷阱与避坑技巧&#34;&gt;九、Unix 网络编程常见陷阱与避坑技巧&lt;/h2&gt;
&lt;h3 id=&#34;91-必须设置-so_reuseaddr&#34;&gt;9.1 必须设置 SO_REUSEADDR&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; reuse &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:#a6e22e&#34;&gt;setsockopt&lt;/span&gt;(listen_fd, SOL_SOCKET, SO_REUSEADDR, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;reuse, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(reuse));&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;：服务端主动关闭连接后会进入 TIME_WAIT，该连接的本地端口短时间内不能复用。如果不设置 SO_REUSEADDR，重启服务时可能报 &lt;code&gt;Address already in use&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：SO_REUSEADDR 允许复用 TIME_WAIT 的端口，但不会允许两个进程同时绑定到完全相同的 &lt;code&gt;IP:端口&lt;/code&gt;（Linux 上 SO_REUSEPORT 才能做到）。&lt;/p&gt;
&lt;h3 id=&#34;92-正确处理-sigpipe&#34;&gt;9.2 正确处理 SIGPIPE&lt;/h3&gt;
&lt;p&gt;向一个已经收到 RST 的 socket 写数据会触发 &lt;code&gt;SIGPIPE&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;signal&lt;/span&gt;(SIGPIPE, SIG_IGN);  &lt;span style=&#34;color:#75715e&#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;更优雅的做法是在写之前检查连接状态，或者使用 &lt;code&gt;MSG_NOSIGNAL&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;send&lt;/span&gt;(fd, buf, len, MSG_NOSIGNAL);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;93-处理部分读写&#34;&gt;9.3 处理部分读写&lt;/h3&gt;
&lt;p&gt;TCP 是字节流，&lt;code&gt;read&lt;/code&gt; 和 &lt;code&gt;write&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;ssize_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;write_all&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; len) {
&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;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; buf;
&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;size_t&lt;/span&gt; remaining &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; len;
&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; (remaining &lt;span style=&#34;color:#f92672&#34;&gt;&amp;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 style=&#34;color:#66d9ef&#34;&gt;ssize_t&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(fd, p, remaining);
&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; (n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&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 style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (errno &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; EINTR) &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;return&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&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;        p &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; n;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        remaining &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; n;
&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; len;
&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;h3 id=&#34;94-处理-eintr&#34;&gt;9.4 处理 EINTR&lt;/h3&gt;
&lt;p&gt;慢系统调用（如 &lt;code&gt;accept&lt;/code&gt;、&lt;code&gt;read&lt;/code&gt;、&lt;code&gt;write&lt;/code&gt;）被信号中断后会返回 -1 并设置 &lt;code&gt;errno = EINTR&lt;/code&gt;。 robust 的程序应该重试：&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ((n &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(fd, buf, len)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; errno &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; EINTR)
&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;h3 id=&#34;95-不要用-strlen-计算二进制数据长度&#34;&gt;9.5 不要用 strlen 计算二进制数据长度&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;send(fd, buf, strlen(buf), 0)&lt;/code&gt; 只适合文本。二进制数据里可能有 &lt;code&gt;\0&lt;/code&gt;，要用实际长度。&lt;/p&gt;
&lt;h3 id=&#34;96-使用-getaddrinfo-替代-gethostbyname&#34;&gt;9.6 使用 getaddrinfo 替代 gethostbyname&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;gethostbyname&lt;/code&gt; 只支持 IPv4，不是线程安全的，已经被废弃。现代程序应使用 &lt;code&gt;getaddrinfo&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; addrinfo hints, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;res;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;memset&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;hints, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(hints));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hints.ai_family &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AF_UNSPEC;      &lt;span style=&#34;color:#75715e&#34;&gt;// 支持 IPv4 和 IPv6
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hints.ai_socktype &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; SOCK_STREAM;
&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;int&lt;/span&gt; err &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getaddrinfo&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;example.com&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;80&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;hints, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;res);
&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; (err &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 style=&#34;color:#a6e22e&#34;&gt;fprintf&lt;/span&gt;(stderr, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;getaddrinfo: %s&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;gai_strerror&lt;/span&gt;(err));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;exit&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&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;int&lt;/span&gt; fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;socket&lt;/span&gt;(res&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ai_family, res&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ai_socktype, res&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ai_protocol);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;connect&lt;/span&gt;(fd, res&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ai_addr, res&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ai_addrlen);
&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:#a6e22e&#34;&gt;freeaddrinfo&lt;/span&gt;(res);  &lt;span style=&#34;color:#75715e&#34;&gt;// 记得释放
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;97-区分-close-与-shutdown&#34;&gt;9.7 区分 close 与 shutdown&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;close(fd)&lt;/code&gt;：关闭文件描述符，引用计数减到 0 时释放资源，双向关闭。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shutdown(fd, SHUT_WR)&lt;/code&gt;：只关闭写方向，读仍然可以接收数据，常用于通知对端「我发完了」。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;98-阻塞-vs-非阻塞-io&#34;&gt;9.8 阻塞 vs 非阻塞 IO&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;模式&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;阻塞&lt;/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;调用立即返回，没数据返回 EAGAIN/EWOULDBLOCK&lt;/td&gt;
					&lt;td&gt;高并发、IO 多路复用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;99-io-多路复用&#34;&gt;9.9 IO 多路复用&lt;/h3&gt;
&lt;p&gt;单线程服务大量连接时，要用 IO 多路复用同时监视多个 fd 的就绪状态。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[主线程] --&amp;gt; B[select/poll/epoll]
    B --&amp;gt;|&amp;#34;哪些 fd 可读/可写&amp;#34;| C[处理可读 fd]
    B --&amp;gt; D[处理可写 fd]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;机制&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;select&lt;/td&gt;
					&lt;td&gt;有 fd 数量限制（通常 1024），每次调用要遍历全部 fd&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;poll&lt;/td&gt;
					&lt;td&gt;用数组描述 fd，没有数量硬限制，但仍需遍历&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;epoll（Linux）&lt;/td&gt;
					&lt;td&gt;事件驱动，只关注活跃的 fd，支持边缘触发和水平触发&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;kqueue（BSD/macOS）&lt;/td&gt;
					&lt;td&gt;类似 epoll，事件驱动&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;epoll 水平触发（LT）vs 边缘触发（ET）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LT：只要 fd 还有数据可读，epoll 就会一直通知。&lt;/li&gt;
&lt;li&gt;ET：只有 fd 状态变化时通知一次，需要一次性把数据读完，通常配合非阻塞 IO。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十典型-unix-网络障碍问题与排查思路&#34;&gt;十、典型 Unix 网络障碍问题与排查思路&lt;/h2&gt;
&lt;h3 id=&#34;101-排查流程总览&#34;&gt;10.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 TD
    A[网络问题] --&amp;gt; B[确认是 DNS/路由/端口/应用哪一层]
    B --&amp;gt; C{能解析域名吗?}
    C --&amp;gt;|否| D[检查 DNS / hosts / 防火墙]
    C --&amp;gt;|是| E{能 ping 通 IP 吗?}
    E --&amp;gt;|否| F[检查路由 / ACL / 安全组]
    E --&amp;gt;|是| G{端口通吗?}
    G --&amp;gt;|否| H[检查服务是否监听 / 防火墙 / 安全组]
    G --&amp;gt;|是| I[检查应用日志 / 抓包 / 连接状态]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;102-connection-refused&#34;&gt;10.2 Connection refused&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：目标 IP 可达，但目标端口没有进程在监听，或者防火墙明确拒绝。&lt;/p&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 服务端检查是否在监听&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ss -tlnp | grep &lt;span style=&#34;color:#ae81ff&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;netstat -tlnp | grep &lt;span style=&#34;color:#ae81ff&#34;&gt;8080&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;# 客户端测试端口是否开放&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;telnet server_ip &lt;span style=&#34;color:#ae81ff&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nc -vz server_ip &lt;span style=&#34;color:#ae81ff&#34;&gt;8080&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;103-connection-timed-out&#34;&gt;10.3 Connection timed out&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：报文到达目的地过程中被丢弃，没有收到任何 RST 或响应，常见于防火墙静默丢弃、路由不可达、NAT 映射问题。&lt;/p&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 看路径到哪里断了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;traceroute server_ip
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mtr server_ip
&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;# 抓包看 SYN 有没有出去、有没有回来&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tcpdump -i any port &lt;span style=&#34;color:#ae81ff&#34;&gt;8080&lt;/span&gt; -nn&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;104-too-many-time_wait--端口耗尽&#34;&gt;10.4 Too many TIME_WAIT / 端口耗尽&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：高并发短连接场景下，客户端连接失败，报错 &lt;code&gt;Cannot assign requested address&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：大量主动关闭的连接堆积在 TIME_WAIT，占用了本地端口。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决思路&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;尽量使用长连接 / 连接池。&lt;/li&gt;
&lt;li&gt;服务端设置 &lt;code&gt;SO_REUSEADDR&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Linux 可调整 &lt;code&gt;net.ipv4.ip_local_port_range&lt;/code&gt; 扩大临时端口范围。&lt;/li&gt;
&lt;li&gt;开启 &lt;code&gt;net.ipv4.tcp_tw_reuse&lt;/code&gt;（谨慎，仅对客户端出站连接有效，不要开 &lt;code&gt;tcp_tw_recycle&lt;/code&gt;，已在内核 4.12 后移除）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;105-close_wait-堆积&#34;&gt;10.5 CLOSE_WAIT 堆积&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：服务端 &lt;code&gt;ss -tan | grep CLOSE_WAIT&lt;/code&gt; 看到很多连接。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：对端已经发了 FIN，但本端应用没有调用 &lt;code&gt;close()&lt;/code&gt;，通常是因为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用逻辑忘记关闭 socket。&lt;/li&gt;
&lt;li&gt;应用在处理完数据前阻塞在某处，无法执行 close。&lt;/li&gt;
&lt;li&gt;异常分支没有释放连接资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：检查代码里所有异常路径是否都调用了 &lt;code&gt;close&lt;/code&gt;，确保每个 &lt;code&gt;accept&lt;/code&gt; 出来的 fd 都有对应的关闭点。&lt;/p&gt;
&lt;h3 id=&#34;106-syn-flood--半连接队列溢出&#34;&gt;10.6 SYN Flood / 半连接队列溢出&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：服务偶尔出现连接建立慢或大量 &lt;code&gt;SYN_SENT&lt;/code&gt;/&lt;code&gt;SYN_RECV&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理&lt;/strong&gt;：Linux 内核有两个队列：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;半连接队列（syn queue）&lt;/strong&gt;：存放收到 SYN 但还没完成三次握手的连接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全连接队列（accept queue）&lt;/strong&gt;：存放已完成三次握手但还没被 &lt;code&gt;accept&lt;/code&gt; 取走的连接。&lt;/li&gt;
&lt;/ul&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[客户端 SYN] --&amp;gt;|&amp;#34;进入半连接队列&amp;#34;| B[SYN_RCVD]
    B --&amp;gt;|&amp;#34;收到 ACK&amp;#34;| C[ESTABLISHED]
    C --&amp;gt;|&amp;#34;进入全连接队列&amp;#34;| D[等待 accept]
    D --&amp;gt;|&amp;#34;accept 取走&amp;#34;| E[交给业务处理]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ss -s          &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;ss -lnt        &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;netstat -s | grep overflow&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;/p&gt;
&lt;ol&gt;
&lt;li&gt;调大 &lt;code&gt;net.ipv4.tcp_max_syn_backlog&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;开启 SYN Cookies：&lt;code&gt;net.ipv4.tcp_syncookies = 1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;增大 &lt;code&gt;listen(fd, backlog)&lt;/code&gt; 的 backlog，同时配合 &lt;code&gt;somaxconn&lt;/code&gt; 调整。&lt;/li&gt;
&lt;li&gt;提升应用 &lt;code&gt;accept&lt;/code&gt; 速度。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;107-高并发下-accept-慢或全连接队列溢出&#34;&gt;10.7 高并发下 accept 慢或全连接队列溢出&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：连接完成三次握手，但客户端一直等待服务端响应。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：&lt;code&gt;accept&lt;/code&gt; 处理太慢，全连接队列满了，新的完成连接被丢弃。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用多线程/多进程分担 accept。&lt;/li&gt;
&lt;li&gt;使用 IO 多路复用 + 事件驱动架构。&lt;/li&gt;
&lt;li&gt;调整 &lt;code&gt;net.core.somaxconn&lt;/code&gt; 和 &lt;code&gt;listen()&lt;/code&gt; 的 backlog。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;108-mtumss-导致的大包不通&#34;&gt;10.8 MTU/MSS 导致的大包不通&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：小数据包能通，大数据包不通；或某些网站/服务能打开，某些打不开。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：路径中某段链路 MTU 较小，大包被丢弃，且中间设备不发送 ICMP &amp;ldquo;Fragmentation Needed&amp;rdquo;，导致无法自动降包大小。这就是著名的 &lt;strong&gt;PMTUD（Path MTU Discovery）黑洞&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 &lt;code&gt;net.ipv4.tcp_mtu_probing = 1&lt;/code&gt; 启用 TCP MTU 探测。&lt;/li&gt;
&lt;li&gt;在 VPN/隧道两端统一调低 MTU/MSS。&lt;/li&gt;
&lt;li&gt;中间网络设备不要丢弃 ICMP &amp;ldquo;Fragmentation Needed&amp;rdquo;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;109-应用层协议粘包拆包&#34;&gt;10.9 应用层协议粘包/拆包&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;现象&lt;/strong&gt;：TCP 客户端发了两条消息，服务端一次 read 读到了两条消息的内容。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：TCP 是字节流，没有消息边界。应用层需要自己定义消息边界。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;固定长度&lt;/strong&gt;：每个消息固定 N 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分隔符&lt;/strong&gt;：如 &lt;code&gt;\n&lt;/code&gt;、自定义分隔符。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长度前缀&lt;/strong&gt;：消息头 4 字节表示消息体长度。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 长度前缀示例：先读 4 字节长度，再读对应长度的 body
&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;uint32_t&lt;/span&gt; len;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;read_all&lt;/span&gt;(fd, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;len, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ntohl&lt;/span&gt;(len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;read_all&lt;/span&gt;(fd, body, len);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;十一学习路线与推荐资源&#34;&gt;十一、学习路线与推荐资源&lt;/h2&gt;
&lt;h3 id=&#34;111-学习顺序建议&#34;&gt;11.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 LR
    A[网络分层模型] --&amp;gt; B[IP/子网/路由]
    B --&amp;gt; C[ARP/ICMP]
    C --&amp;gt; D[TCP 握手挥手与状态机]
    D --&amp;gt; E[滑动窗口与拥塞控制]
    E --&amp;gt; F[Socket API 编程]
    F --&amp;gt; G[IO 多路复用]
    G --&amp;gt; H[Wireshark 抓包分析]
    H --&amp;gt; I[线上问题排查]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;112-推荐实践&#34;&gt;11.2 推荐实践&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;用 Wireshark 抓一次完整的三次握手和四次挥手&lt;/strong&gt;，亲眼看看每个字段的值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写一个 echo 服务器&lt;/strong&gt;，先用阻塞 IO，再用 select/epoll 改写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;故意制造问题&lt;/strong&gt;：把客户端改成快速创建大量短连接，观察 TIME_WAIT；把服务端 accept 故意睡几秒，观察全连接队列溢出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用 &lt;code&gt;ss&lt;/code&gt;、&lt;code&gt;tcpdump&lt;/code&gt;、&lt;code&gt;traceroute&lt;/code&gt;、&lt;code&gt;nc&lt;/code&gt;、&lt;code&gt;iperf&lt;/code&gt; 这些工具做实验&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;113-推荐参考书&#34;&gt;11.3 推荐参考书&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;《TCP/IP 详解 卷1：协议》（W. Richard Stevens）——协议细节最权威。&lt;/li&gt;
&lt;li&gt;《Unix 网络编程 卷1：套接字联网 API》（W. Richard Stevens）——Socket 编程圣经。&lt;/li&gt;
&lt;li&gt;《Wireshark 网络分析就这么简单》——抓包实战。&lt;/li&gt;
&lt;li&gt;《Linux 高性能服务器编程》（游双）——中文入门友好。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十二总结&#34;&gt;十二、总结&lt;/h2&gt;
&lt;p&gt;TCP/IP 协议栈并不神秘，它的核心思想就是&lt;strong&gt;分层与解耦&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链路层负责「相邻设备怎么传」。&lt;/li&gt;
&lt;li&gt;网络层负责「怎么找到对方主机」。&lt;/li&gt;
&lt;li&gt;传输层负责「怎么把数据交给正确的进程，并决定可靠还是高效」。&lt;/li&gt;
&lt;li&gt;应用层负责「这些数据代表什么业务含义」。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unix 网络编程的核心是理解 Socket API 的调用顺序、IO 模型、以及 TCP 连接的生命周期。写出稳定的网络程序，不在于记住所有参数，而在于理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接是什么时候建立的，什么时候关闭的。&lt;/li&gt;
&lt;li&gt;数据是怎么流动的，会不会被截断、会不会粘包。&lt;/li&gt;
&lt;li&gt;异常情况下系统会怎么表现，程序能不能正确处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;希望这份总结能帮你把《TCP/IP 详解》里的知识点串成一张网，少踩一些网络编程里的经典大坑。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>《深入理解计算机系统》（CSAPP）学习笔记：给新手的硬件、操作系统与网络并发入门</title>
      <link>https://ftmao.com/blog/bookstore/computer-systems-programmer-perspective-notes/</link>
      <pubDate>Wed, 01 Nov 2017 11:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/bookstore/computer-systems-programmer-perspective-notes/</guid>
      <description>&lt;h1 id=&#34;深入理解计算机系统csapp学习笔记给新手的硬件操作系统与网络并发入门&#34;&gt;《深入理解计算机系统》（CSAPP）学习笔记：给新手的硬件、操作系统与网络并发入门&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;参考书籍：《深入理解计算机系统》（Computer Systems: A Programmer&amp;rsquo;s Perspective，简称 CSAPP）&lt;br&gt;
目标读者：想学透计算机底层、准备面试、或者写代码总感觉&amp;quot;知其然不知其所以然&amp;quot;的新手。&lt;br&gt;
阅读建议：这篇文章不讲完 700 页书的全部细节，而是帮你建立&lt;strong&gt;主干知识框架&lt;/strong&gt;，并标注容易踩坑的地方。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一为什么要学-csapp&#34;&gt;一、为什么要学 CSAPP？&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&gt;语法 → 框架 → 项目 → 面试八股&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;但遇到这些问题时往往卡住：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么 &lt;code&gt;int&lt;/code&gt; 溢出会变成负数？&lt;/li&gt;
&lt;li&gt;为什么同样的代码，有时快有时慢？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fork()&lt;/code&gt; 之后到底发生了什么？&lt;/li&gt;
&lt;li&gt;为什么多线程程序偶尔结果不对？&lt;/li&gt;
&lt;li&gt;TCP 连接为什么要三次握手、四次挥手？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CSAPP 就是帮你从&amp;quot;会写代码&amp;quot;进化到&amp;quot;理解代码为什么这样跑&amp;quot;。&lt;/p&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;graph TD
    A[应用程序] --&amp;gt; B[操作系统]
    B --&amp;gt; C[硬件层]
    C --&amp;gt; D[CPU / 处理器]
    C --&amp;gt; E[内存 / 主存]
    C --&amp;gt; F[磁盘 / SSD]
    C --&amp;gt; G[网络接口 / I/O 设备]
    B --&amp;gt; H[进程管理]
    B --&amp;gt; I[虚拟内存]
    B --&amp;gt; J[文件系统]
    B --&amp;gt; K[网络协议栈]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：操作系统是应用程序和硬件之间的中间层。它负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;让多个程序&amp;quot;同时&amp;quot;运行（进程调度）&lt;/li&gt;
&lt;li&gt;给每个程序独立的内存空间（虚拟内存）&lt;/li&gt;
&lt;li&gt;统一管理文件、网络、输入输出&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三信息就是位--上下文&#34;&gt;三、信息就是位 + 上下文&lt;/h2&gt;
&lt;p&gt;计算机里所有的信息——数字、文字、图片、声音、代码——最终都是 &lt;strong&gt;0 和 1&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 LR
    A[人类可理解的数据] --&amp;gt;|编码| B[二进制位流]
    B --&amp;gt;|解释规则| C[整数 / 浮点数 / 字符 / 指令]
    C --&amp;gt;|上下文| D[有意义的信息]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;31-整数溢出新手最常踩的坑&#34;&gt;3.1 整数溢出：新手最常踩的坑&lt;/h3&gt;
&lt;p&gt;在 C/C++ 中，有符号整数溢出是&lt;strong&gt;未定义行为&lt;/strong&gt;（Undefined Behavior）。&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2147483647&lt;/span&gt;;  &lt;span style=&#34;color:#75715e&#34;&gt;// int 最大值
&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;int&lt;/span&gt; b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; a &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:#75715e&#34;&gt;// 溢出，结果未定义，通常变成 -2147483648
&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;做加法前判断是否接近上限。&lt;/li&gt;
&lt;li&gt;涉及大数时用 &lt;code&gt;long long&lt;/code&gt; 或任意精度库。&lt;/li&gt;
&lt;li&gt;无符号整数运算不会溢出，但会发生&lt;strong&gt;回绕&lt;/strong&gt;（wrap around），同样要小心。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-浮点数精度问题&#34;&gt;3.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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.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;double&lt;/span&gt; b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.2&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; (a &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; b &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.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:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;相等&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&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;else&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:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;不相等！&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&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&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;：0.1 在二进制浮点中无法精确表示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;比较浮点数时用误差范围：&lt;code&gt;fabs(a + b - 0.3) &amp;lt; 1e-9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;财务计算避免用 &lt;code&gt;float&lt;/code&gt;/&lt;code&gt;double&lt;/code&gt;，考虑定点数或专用库。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;33-大端与小端&#34;&gt;3.3 大端与小端&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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[数据 0x12345678] --&amp;gt; B[大端序&amp;lt;br/&amp;gt;内存: 12 34 56 78]
    A --&amp;gt; C[小端序&amp;lt;br/&amp;gt;内存: 78 56 34 12]&lt;/code&gt;&lt;/pre&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;：低位字节放低地址（x86-64 常用）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：网络传输用&lt;strong&gt;大端序&lt;/strong&gt;（网络字节序），调用 &lt;code&gt;htonl()&lt;/code&gt; / &lt;code&gt;htons()&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;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[源代码 hello.c] --&amp;gt;|预处理器 cpp| B[预处理后的 .i]
    B --&amp;gt;|编译器 cc1| C[汇编代码 .s]
    C --&amp;gt;|汇编器 as| D[目标文件 .o]
    D --&amp;gt;|链接器 ld| E[可执行文件]
    E --&amp;gt;|操作系统加载| F[CPU 执行]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;每一步都可能出问题：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;阶段&lt;/th&gt;
					&lt;th&gt;典型错误&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&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;/tr&gt;
			&lt;tr&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;undefined reference&lt;/code&gt;、&lt;code&gt;multiple definition&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;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;42-调用栈函数调用的底层&#34;&gt;4.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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 栈内存[&amp;#34;栈内存（从高地址向低地址增长）&amp;#34;]
        A[调用者栈帧] --&amp;gt; B[返回地址]
        B --&amp;gt; C[被调用者局部变量]
        C --&amp;gt; D[被调用者参数空间]
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键概念&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;栈帧（Stack Frame）&lt;/strong&gt;：每次函数调用都会在栈上分配一块空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回地址&lt;/strong&gt;：函数执行完后要回到哪里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓冲区溢出&lt;/strong&gt;：局部数组写越界，可能覆盖返回地址，导致程序崩溃或被攻击。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;43-缓冲区溢出经典安全漏洞&#34;&gt;4.3 缓冲区溢出：经典安全漏洞&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;vulnerable&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;char&lt;/span&gt; buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;8&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:#a6e22e&#34;&gt;gets&lt;/span&gt;(buf);  &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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;永远不要用 &lt;code&gt;gets()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;fgets()&lt;/code&gt;、&lt;code&gt;snprintf()&lt;/code&gt;、&lt;code&gt;strncpy()&lt;/code&gt; 等带长度限制的函数。&lt;/li&gt;
&lt;li&gt;编译时开启保护：&lt;code&gt;-fstack-protector-strong&lt;/code&gt;、&lt;code&gt;-D_FORTIFY_SOURCE=2&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&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;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[寄存器&amp;lt;br/&amp;gt;最快 / 容量最小] --&amp;gt; B[L1 缓存]
    B --&amp;gt; C[L2 缓存]
    C --&amp;gt; D[L3 缓存]
    D --&amp;gt; E[主存 / 内存]
    E --&amp;gt; F[磁盘 / SSD]
    F --&amp;gt; G[网络存储]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;核心规律&lt;/strong&gt;：越靠近 CPU，速度越快，容量越小，价格越贵。&lt;/p&gt;
&lt;h3 id=&#34;52-局部性原理&#34;&gt;5.2 局部性原理&lt;/h3&gt;
&lt;p&gt;程序倾向于重复使用最近用过的数据和指令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时间局部性&lt;/strong&gt;：刚访问的数据很可能再次被访问。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;空间局部性&lt;/strong&gt;：访问某个地址后，附近地址很可能被访问。&lt;/li&gt;
&lt;/ul&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; j &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; n; j&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; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; n; i&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;        sum &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; a[i][j];
&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;// 缓存友好：按行访问
&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; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; n; i&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; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; j &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; n; j&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;        sum &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; a[i][j];&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;/p&gt;
&lt;h3 id=&#34;53-cache-未命中&#34;&gt;5.3 Cache 未命中&lt;/h3&gt;
&lt;p&gt;CPU 先去缓存找数据，没找到再去内存找，这就是 &lt;strong&gt;Cache Miss&lt;/strong&gt;，会带来几十到几百个时钟周期的延迟。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;减少 Cache Miss 的方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据紧凑排列，减少结构体填充。&lt;/li&gt;
&lt;li&gt;避免伪共享（多线程中不同变量落在同一缓存行）。&lt;/li&gt;
&lt;li&gt;按行遍历二维数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六链接&#34;&gt;六、链接&lt;/h2&gt;
&lt;p&gt;链接器把多个 &lt;code&gt;.o&lt;/code&gt; 文件拼成一个可执行文件。&lt;/p&gt;
&lt;h3 id=&#34;61-静态链接-vs-动态链接&#34;&gt;6.1 静态链接 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
    A[静态链接] --&amp;gt; B[库代码复制进可执行文件]
    B --&amp;gt; C[优点: 独立运行]
    B --&amp;gt; D[缺点: 体积大 / 更新麻烦]
    
    E[动态链接] --&amp;gt; F[运行时才加载共享库]
    F --&amp;gt; G[优点: 体积小 / 共享内存]
    F --&amp;gt; H[缺点: 依赖环境 .so 文件]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;62-常见链接错误&#34;&gt;6.2 常见链接错误&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;错误&lt;/th&gt;
					&lt;th&gt;原因&lt;/th&gt;
					&lt;th&gt;解决&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;undefined reference to &#39;xxx&#39;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;函数声明了但没找到定义&lt;/td&gt;
					&lt;td&gt;检查是否漏链接库文件&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;multiple definition of &#39;xxx&#39;&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全局变量/函数在多个文件定义&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;extern&lt;/code&gt; 声明，只在一个 &lt;code&gt;.c&lt;/code&gt; 中定义&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;cannot find -lxxx&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;链接器找不到库&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;-L&lt;/code&gt; 指定库路径&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：头文件只放声明，定义放在 &lt;code&gt;.c&lt;/code&gt; 文件里；全局变量尽量用 &lt;code&gt;static&lt;/code&gt; 限制作用域。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七异常控制流进程与信号&#34;&gt;七、异常控制流：进程与信号&lt;/h2&gt;
&lt;h3 id=&#34;71-进程是什么&#34;&gt;7.1 进程是什么？&lt;/h3&gt;
&lt;p&gt;进程是操作系统&lt;strong&gt;资源分配的基本单位&lt;/strong&gt;。每个进程有自己的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;地址空间（代码、数据、堆、栈）&lt;/li&gt;
&lt;li&gt;打开的文件描述符&lt;/li&gt;
&lt;li&gt;寄存器状态&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 进程地址空间[&amp;#34;进程地址空间&amp;#34;]
        A[内核空间] 
        B[栈]
        C[堆]
        D[数据段]
        E[代码段]
    end
    A --&amp;gt; B --&amp;gt; C --&amp;gt; D --&amp;gt; E&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;72-fork--exec--waitunix-进程三件套&#34;&gt;7.2 fork / exec / wait：Unix 进程三件套&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;sequenceDiagram
    participant P as 父进程
    participant C as 子进程
    participant K as 内核
    P-&amp;gt;&amp;gt;K: fork()
    K--&amp;gt;&amp;gt;P: 返回子进程 PID
    K--&amp;gt;&amp;gt;C: 返回 0
    C-&amp;gt;&amp;gt;K: execve(&amp;#34;/bin/ls&amp;#34;, ...)
    K-&amp;gt;&amp;gt;C: 替换为 ls 程序
    C--&amp;gt;&amp;gt;P: 退出状态
    P-&amp;gt;&amp;gt;K: waitpid()
    K--&amp;gt;&amp;gt;P: 回收子进程&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键注意点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fork()&lt;/code&gt; 后，父进程和子进程&lt;strong&gt;独立运行&lt;/strong&gt;，各自有独立地址空间。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exec()&lt;/code&gt; 会替换当前进程的代码和数据，PID 不变。&lt;/li&gt;
&lt;li&gt;父进程必须用 &lt;code&gt;wait()&lt;/code&gt;/&lt;code&gt;waitpid()&lt;/code&gt; 回收子进程，否则会产生&lt;strong&gt;僵尸进程&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-僵尸进程与孤儿进程&#34;&gt;7.3 僵尸进程与孤儿进程&lt;/h3&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;code&gt;init&lt;/code&gt; 进程收养。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;父进程捕获 &lt;code&gt;SIGCHLD&lt;/code&gt; 信号，在信号处理函数中调用 &lt;code&gt;waitpid()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;或者用 &lt;code&gt;signal(SIGCHLD, SIG_IGN)&lt;/code&gt;（在 Linux 上可自动回收）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;74-信号&#34;&gt;7.4 信号&lt;/h3&gt;
&lt;p&gt;信号是 Unix 的异步通知机制：&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;SIGINT&lt;/td&gt;
					&lt;td&gt;中断（Ctrl+C）&lt;/td&gt;
					&lt;td&gt;终止前台进程&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SIGSEGV&lt;/td&gt;
					&lt;td&gt;段错误&lt;/td&gt;
					&lt;td&gt;访问非法内存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SIGKILL&lt;/td&gt;
					&lt;td&gt;强制终止&lt;/td&gt;
					&lt;td&gt;无法捕获、无法忽略&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;SIGTERM&lt;/td&gt;
					&lt;td&gt;正常终止&lt;/td&gt;
					&lt;td&gt;可以被捕获处理&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：&lt;code&gt;SIGKILL&lt;/code&gt; 和 &lt;code&gt;SIGSTOP&lt;/code&gt; 不能被捕获或忽略，不要试图拦截它们。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八虚拟内存&#34;&gt;八、虚拟内存&lt;/h2&gt;
&lt;h3 id=&#34;81-为什么需要虚拟内存&#34;&gt;8.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 LR
    A[进程A虚拟地址] --&amp;gt;|页表| C[物理内存]
    B[进程B虚拟地址] --&amp;gt;|页表| C
    C --&amp;gt; D[磁盘交换区]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;虚拟内存的好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;隔离&lt;/strong&gt;：每个进程以为自己在独占内存，互不干扰。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简化&lt;/strong&gt;：程序使用连续虚拟地址，实际物理内存可以离散。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享&lt;/strong&gt;：不同进程可以映射同一块物理内存（如共享库）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;82-地址翻译页表与-tlb&#34;&gt;8.2 地址翻译：页表与 TLB&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 LR
    A[虚拟地址] --&amp;gt; B[页号 + 页内偏移]
    B --&amp;gt; C{TLB 命中?}
    C --&amp;gt;|是| D[直接得到物理地址]
    C --&amp;gt;|否| E[查页表]
    E --&amp;gt; F[得到物理地址]&lt;/code&gt;&lt;/pre&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;TLB&lt;/strong&gt;：页表的缓存，加速地址翻译。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;避坑技巧&lt;/strong&gt;：频繁访问大量不连续内存会导致大量 TLB Miss，性能骤降。&lt;/p&gt;
&lt;h3 id=&#34;83-malloc--free-的底层&#34;&gt;8.3 malloc / free 的底层&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;malloc&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1024&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:#a6e22e&#34;&gt;free&lt;/span&gt;(p);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;malloc&lt;/code&gt; 不一定每次都向操作系统申请内存，它通常先向 &lt;strong&gt;ptmalloc / jemalloc&lt;/strong&gt; 等内存分配器请求，分配器管理一个堆内存池。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见内存问题&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;表现&lt;/th&gt;
					&lt;th&gt;解决&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;内存泄漏&lt;/td&gt;
					&lt;td&gt;程序运行越久内存越大&lt;/td&gt;
					&lt;td&gt;配对使用 malloc/free，用 Valgrind/ASan 检测&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;重复释放&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;double free&lt;/code&gt; 崩溃&lt;/td&gt;
					&lt;td&gt;free 后将指针置 NULL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;越界写&lt;/td&gt;
					&lt;td&gt;破坏堆结构，后续 malloc/free 崩溃&lt;/td&gt;
					&lt;td&gt;边界检查，用 ASan&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;使用已释放内存&lt;/td&gt;
					&lt;td&gt;use-after-free&lt;/td&gt;
					&lt;td&gt;释放后置 NULL，或换智能指针&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九系统级-io&#34;&gt;九、系统级 I/O&lt;/h2&gt;
&lt;h3 id=&#34;91-文件描述符&#34;&gt;9.1 文件描述符&lt;/h3&gt;
&lt;p&gt;Unix 中一切都是文件：普通文件、管道、socket、设备，都用一个整数 &lt;strong&gt;文件描述符（fd）&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;file.txt&amp;#34;&lt;/span&gt;, O_RDONLY);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(fd, buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(buf));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(fd);&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;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;fd&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;0&lt;/td&gt;
					&lt;td&gt;标准输入 stdin&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;标准输出 stdout&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;标准错误 stderr&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;92-rio-包健壮的-io&#34;&gt;9.2 RIO 包：健壮的 I/O&lt;/h3&gt;
&lt;p&gt;CSAPP 推荐用 RIO（Robust I/O）处理短计数和网络读写：&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&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:#a6e22e&#34;&gt;rio_readn&lt;/span&gt;(fd, buf, n);   &lt;span style=&#34;color:#75715e&#34;&gt;// 读到 n 字节或 EOF
&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:#a6e22e&#34;&gt;rio_readlineb&lt;/span&gt;(fd, buf, maxlen);  &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:#a6e22e&#34;&gt;rio_writen&lt;/span&gt;(fd, buf, n);  &lt;span style=&#34;color:#75715e&#34;&gt;// 写 n 字节
&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;read()&lt;/code&gt;/&lt;code&gt;write()&lt;/code&gt; 不保证一次读写完请求的字节数，要处理&lt;strong&gt;短计数&lt;/strong&gt;（short count）。&lt;/li&gt;
&lt;li&gt;网络编程中尤其要注意：TCP 是字节流，没有消息边界。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十unix-网络编程核心&#34;&gt;十、Unix 网络编程核心&lt;/h2&gt;
&lt;h3 id=&#34;101-客户端--服务器模型&#34;&gt;10.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 LR
    A[客户端] --&amp;gt;|请求| B[服务器]
    B --&amp;gt;|响应| A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;102-socket-编程流程&#34;&gt;10.2 Socket 编程流程&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;sequenceDiagram
    participant S as 服务器
    participant C as 客户端
    participant K as 内核
    
    S-&amp;gt;&amp;gt;K: socket()
    S-&amp;gt;&amp;gt;K: bind()
    S-&amp;gt;&amp;gt;K: listen()
    C-&amp;gt;&amp;gt;K: socket()
    C-&amp;gt;&amp;gt;K: connect()
    K--&amp;gt;&amp;gt;S: 连接请求
    S-&amp;gt;&amp;gt;K: accept()
    S-&amp;gt;&amp;gt;K: read/write
    C-&amp;gt;&amp;gt;K: read/write
    S-&amp;gt;&amp;gt;K: close()
    C-&amp;gt;&amp;gt;K: close()&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;103-tcp-三次握手与四次挥手&#34;&gt;10.3 TCP 三次握手与四次挥手&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;sequenceDiagram
    participant C as 客户端
    participant S as 服务器
    C-&amp;gt;&amp;gt;S: SYN
    S-&amp;gt;&amp;gt;C: SYN + ACK
    C-&amp;gt;&amp;gt;S: ACK
    Note over C,S: 连接建立，可以传数据
    C-&amp;gt;&amp;gt;S: FIN
    S-&amp;gt;&amp;gt;C: ACK
    S-&amp;gt;&amp;gt;C: FIN
    C-&amp;gt;&amp;gt;S: ACK
    Note over C,S: 连接关闭&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;为什么是三次握手？&lt;/strong&gt; 防止历史重复连接初始化造成混乱，同时确认双方收发能力正常。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么是四次挥手？&lt;/strong&gt; TCP 是全双工的，双方都要各自关闭发送通道，所以多了一次。&lt;/p&gt;
&lt;h3 id=&#34;104-echo-服务器示例&#34;&gt;10.4 Echo 服务器示例&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-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdio.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;string.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;unistd.h&amp;gt;&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:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;arpa/inet.h&amp;gt;&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 style=&#34;color:#75715e&#34;&gt;#define MAXLINE 8192
&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;#define PORT 12345
&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;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;echo&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; connfd) {
&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;size_t&lt;/span&gt; n;
&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;char&lt;/span&gt; buf[MAXLINE];
&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; ((n &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;(connfd, buf, MAXLINE)) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;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 style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(connfd, buf, n);  &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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&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;int&lt;/span&gt; listenfd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;socket&lt;/span&gt;(AF_INET, SOCK_STREAM, &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;struct&lt;/span&gt; sockaddr_in serv_addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memset&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;serv_addr, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(serv_addr));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    serv_addr.sin_family &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; AF_INET;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    serv_addr.sin_addr.s_addr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;htonl&lt;/span&gt;(INADDR_ANY);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    serv_addr.sin_port &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;htons&lt;/span&gt;(PORT);
&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:#a6e22e&#34;&gt;bind&lt;/span&gt;(listenfd, (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; sockaddr &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;serv_addr, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(serv_addr));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;listen&lt;/span&gt;(listenfd, &lt;span style=&#34;color:#ae81ff&#34;&gt;1024&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:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Server listening on port %d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, PORT);
&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;while&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;struct&lt;/span&gt; sockaddr_in cli_addr;
&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;socklen_t&lt;/span&gt; len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(cli_addr);
&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;int&lt;/span&gt; connfd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;accept&lt;/span&gt;(listenfd, (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; sockaddr &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cli_addr, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;echo&lt;/span&gt;(connfd);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;close&lt;/span&gt;(connfd);
&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;return&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：上面的代码是&lt;strong&gt;迭代服务器&lt;/strong&gt;，一次只能处理一个客户端。实际生产环境要用多进程、多线程或 I/O 多路复用。&lt;/p&gt;
&lt;h3 id=&#34;105-网络编程避坑清单&#34;&gt;10.5 网络编程避坑清单&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;坑&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;避免方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;忘记字节序转换&lt;/td&gt;
					&lt;td&gt;大端小端混用&lt;/td&gt;
					&lt;td&gt;网络传输前用 &lt;code&gt;htonl&lt;/code&gt;/&lt;code&gt;htons&lt;/code&gt;，接收后用 &lt;code&gt;ntohl&lt;/code&gt;/&lt;code&gt;ntohs&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;忽略 &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt; 返回值&lt;/td&gt;
					&lt;td&gt;可能只读写了一部分&lt;/td&gt;
					&lt;td&gt;循环处理，直到处理完所有字节&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;没有处理 &lt;code&gt;SIGPIPE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;向已关闭的 socket 写会触发信号&lt;/td&gt;
					&lt;td&gt;忽略 &lt;code&gt;SIGPIPE&lt;/code&gt;，或检查返回值&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;端口复用问题&lt;/td&gt;
					&lt;td&gt;服务器重启时 bind 失败&lt;/td&gt;
					&lt;td&gt;设置 &lt;code&gt;SO_REUSEADDR&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;长连接粘包/拆包&lt;/td&gt;
					&lt;td&gt;TCP 无消息边界&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;111-三种并发模型&#34;&gt;11.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
    A[并发模型] --&amp;gt; B[多进程]
    A --&amp;gt; C[多线程]
    A --&amp;gt; D[I/O 多路复用]
    
    B --&amp;gt; B1[进程独立 / 安全]
    B --&amp;gt; B2[切换开销大 / 共享数据麻烦]
    
    C --&amp;gt; C1[共享地址空间 / 切换快]
    C --&amp;gt; C2[需要同步 / 容易出 bug]
    
    D --&amp;gt; D1[单线程处理多个 fd]
    D --&amp;gt; D2[事件驱动 / 编程复杂]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;112-线程同步三件套&#34;&gt;11.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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[互斥锁 Mutex] --&amp;gt;|保护临界区| B[保证同一时间只有一个线程访问]
    C[条件变量 Condition Variable] --&amp;gt;|等待某个条件| D[配合 mutex 使用]
    E[信号量 Semaphore] --&amp;gt;|计数器| F[控制同时访问的线程数量]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;113-生产者---消费者模型&#34;&gt;11.3 生产者 - 消费者模型&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 LR
    A[生产者线程] --&amp;gt;|写入缓冲区| B[共享缓冲区]
    B --&amp;gt;|读取| C[消费者线程]
    D[Mutex] --&amp;gt;|保护| B
    E[条件变量&amp;lt;br/&amp;gt;not_full] --&amp;gt;|通知| A
    F[条件变量&amp;lt;br/&amp;gt;not_empty] --&amp;gt;|通知| C&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;114-死锁并发程序的噩梦&#34;&gt;11.4 死锁：并发程序的噩梦&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;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    A[死锁四条件] --&amp;gt; B[互斥]
    A --&amp;gt; C[占有且等待]
    A --&amp;gt; D[不可抢占]
    A --&amp;gt; E[循环等待]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;避免死锁的方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按固定顺序获取锁。&lt;/li&gt;
&lt;li&gt;使用超时机制（try_lock）。&lt;/li&gt;
&lt;li&gt;尽量减少锁的粒度。&lt;/li&gt;
&lt;li&gt;避免在持有锁时调用可能阻塞的函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;115-并发编程避坑清单&#34;&gt;11.5 并发编程避坑清单&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;坑&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;避免方法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;数据竞争&lt;/td&gt;
					&lt;td&gt;多个线程同时读写共享数据&lt;/td&gt;
					&lt;td&gt;加锁、原子操作、线程局部存储&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;死锁&lt;/td&gt;
					&lt;td&gt;互相等待对方释放锁&lt;/td&gt;
					&lt;td&gt;统一加锁顺序、超时、死锁检测&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;竞态条件&lt;/td&gt;
					&lt;td&gt;执行顺序影响结果&lt;/td&gt;
					&lt;td&gt;用同步原语保护关键区域&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;伪共享&lt;/td&gt;
					&lt;td&gt;两个变量在同一缓存行被不同 CPU 修改&lt;/td&gt;
					&lt;td&gt;按缓存行对齐变量&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;线程泄露&lt;/td&gt;
					&lt;td&gt;创建线程不 join 或 detach&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;121-段错误segmentation-fault&#34;&gt;12.1 段错误（Segmentation Fault）&lt;/h3&gt;
&lt;p&gt;常见原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;访问 NULL 指针&lt;/li&gt;
&lt;li&gt;数组越界&lt;/li&gt;
&lt;li&gt;使用已释放的内存&lt;/li&gt;
&lt;li&gt;栈溢出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&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
    A[程序崩溃 / 段错误] --&amp;gt; B[用 gdb 启动或 attach]
    B --&amp;gt; C[查看 backtrace]
    C --&amp;gt; D{能否定位?}
    D --&amp;gt;|能| E[修复代码]
    D --&amp;gt;|不能| F[用 ASan / Valgrind 检测]
    F --&amp;gt; G[定位内存错误]
    G --&amp;gt; E&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;122-内存泄漏检测&#34;&gt;12.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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 用 AddressSanitizer 编译&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gcc -fsanitize&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;address -g program.c -o program
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./program
&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;# 或用 Valgrind&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;valgrind --leak-check&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;full ./program&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;123-死锁排查&#34;&gt;12.3 死锁排查&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Linux 下查看线程堆栈&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pstack &amp;lt;pid&amp;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;# 或&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gdb -p &amp;lt;pid&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;info threads
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;thread apply all bt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;124-性能分析&#34;&gt;12.4 性能分析&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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# CPU 热点分析&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perf top -p &amp;lt;pid&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perf record -g ./program
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perf report
&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;# 火焰图&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perf script | stackcollapse-perf.pl | flamegraph.pl &amp;gt; flame.svg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;125-常见面试题速查&#34;&gt;12.5 常见面试题速查&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;核心要点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&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;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TCP 为什么三次握手&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;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;局部性原理&lt;/td&gt;
					&lt;td&gt;时间局部性、空间局部性&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;静态链接 vs 动态链接&lt;/td&gt;
					&lt;td&gt;独立运行 vs 共享节省&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;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[C 语言基础] --&amp;gt; B[CSAPP 第 1-3 章&amp;lt;br/&amp;gt;信息表示与汇编]
    B --&amp;gt; C[第 4-6 章&amp;lt;br/&amp;gt;处理器 / 缓存 / 链接]
    C --&amp;gt; D[第 7-9 章&amp;lt;br/&amp;gt;进程 / 信号 / 虚拟内存]
    D --&amp;gt; E[第 10-12 章&amp;lt;br/&amp;gt;I/O / 网络 / 并发]
    E --&amp;gt; F[写一个小 Web 服务器]
    F --&amp;gt; G[看 Linux 内核 / 源码]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;配套练习&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用 C 写一个简单的 shell（支持 fork/exec/pipe）。&lt;/li&gt;
&lt;li&gt;实现一个基于线程池的 HTTP 服务器。&lt;/li&gt;
&lt;li&gt;用 epoll 实现高并发 echo 服务器。&lt;/li&gt;
&lt;li&gt;用 Valgrind 和 ASan 检查自己的项目。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十四总结&#34;&gt;十四、总结&lt;/h2&gt;
&lt;p&gt;CSAPP 不是一本&amp;quot;看完就忘&amp;quot;的书，它提供的是理解计算机系统的&lt;strong&gt;思维框架&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据最终都是位，理解编码才能避免溢出和精度问题。&lt;/li&gt;
&lt;li&gt;程序跑在 CPU 上，理解汇编和栈才能避免安全漏洞。&lt;/li&gt;
&lt;li&gt;内存是分层的，理解缓存和局部性才能写出高性能代码。&lt;/li&gt;
&lt;li&gt;操作系统通过进程、虚拟内存、文件系统管理资源。&lt;/li&gt;
&lt;li&gt;网络编程要理解 TCP 字节流和 socket 生命周期。&lt;/li&gt;
&lt;li&gt;并发编程的核心是同步与避免竞态条件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最重要的学习方法是：&lt;strong&gt;边看书边写代码&lt;/strong&gt;。把书中的例子跑起来，改一改，看看会发生什么。只有亲手踩过坑，才能真正理解这些知识。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;这篇文章是笔者学习 CSAPP 和实践中的总结，难免有疏漏。如果有疑问或建议，欢迎留言交流。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>
