<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Keep It Simple &amp; Stupid</title>
  
  
  <link href="http://blog.fooo.in/atom.xml" rel="self"/>
  
  <link href="http://blog.fooo.in/"/>
  <updated>2025-10-29T00:00:00.000Z</updated>
  <id>http://blog.fooo.in/</id>
  
  <author>
    <name>xun</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>TypeScript 函数定义</title>
    <link href="http://blog.fooo.in/2025/10/29/typescript/function/"/>
    <id>http://blog.fooo.in/2025/10/29/typescript/function/</id>
    <published>2025-10-29T00:00:00.000Z</published>
    <updated>2025-10-29T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>写 ts 写得比较少，也不是持续的写，外加上 LLM 的帮助，对于 ts 的语法是有些怠惰了，这里整理下 ts 的函数定义。</p><blockquote><p>加上自己对于各种东西都是够用就行，所以…</p></blockquote><h2 id="基础函数和常量">基础函数和常量</h2><p><strong>方式一：函数声明</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a: <span class="built_in">number</span>, b: <span class="built_in">number</span></span>): <span class="built_in">number</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最易懂的方式，函数名是 <code>add</code>。</p><p><strong>方式二：函数表达式 / 箭头函数</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">const</span> add = (<span class="attr">a</span>: <span class="built_in">number</span>, <span class="attr">b</span>: <span class="built_in">number</span>): <span class="function"><span class="params">number</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在这里，定义了一个常量 <code>add</code>。它的值是一个匿名函数（这里是箭头函数）。函数本身没有名字，是 <code>add</code> 这个常量指向了它。这里稍微看一下也没有阅读障碍。</p><p><strong>方式三：对象方法</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> calculator = &#123;</span><br><span class="line">  <span class="title function_">add</span>(<span class="attr">x</span>: <span class="built_in">number</span>, <span class="attr">y</span>: <span class="built_in">number</span>): <span class="built_in">number</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> x + y;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">subtract</span>(<span class="attr">x</span>: <span class="built_in">number</span>, <span class="attr">y</span>: <span class="built_in">number</span>): <span class="built_in">number</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> x - y;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>省略了方式一的 <code>function</code> 关键字，也没有阅读障碍。</p><p><strong>方法四：函数类型</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义一个函数类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">GreetFunc</span> = <span class="function">(<span class="params">name: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 应用这个类型</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">greet</span>: <span class="title class_">GreetFunc</span> = <span class="function">(<span class="params">name</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`Hello, <span class="subst">$&#123;name&#125;</span>`</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="给四种函数加上泛型">给四种函数加上泛型</h2><p>现在，给这四种函数加上泛型 <code>&lt;T&gt;</code>。</p><p><strong>方式一：函数声明 + 泛型</strong></p><p>对于函数声明，泛型 <code>&lt;T&gt;</code> 紧跟在函数名后面。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">function</span> createArray&lt;T&gt;(<span class="attr">item</span>: T): T[] &#123;</span><br><span class="line">  <span class="keyword">return</span> [item];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个语法很直观，<code>createArray</code> 是一个泛型函数。</p><p><strong>方式二：箭头函数 + 泛型</strong></p><p>对于一个被赋值给常量的箭头函数，它的“函数”部分其实是从 <code>=</code> 号后面才开始的。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 分解来看</span></span><br><span class="line"><span class="keyword">const</span> createEntity =      <span class="comment">// 这是在定义一个常量</span></span><br><span class="line">  &lt;T <span class="keyword">extends</span> ...&gt;<span class="function">(<span class="params">...</span>) =&gt;</span> &#123; ... &#125; <span class="comment">// 这才是一个完整的、匿名的、带有泛型的箭头函数</span></span><br></pre></td></tr></table></figure><p>所以，泛型 <code>&lt;T extends ...&gt;</code> 必须作为这个箭头函数定义的一部分，放在参数列表 <code>(...)</code> 的前面。</p><p>正确的语法：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// const [变量名] = [泛型定义][参数列表] =&gt; [函数体]</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createEntity = &lt;T <span class="keyword">extends</span> <span class="title class_">Record</span>&lt;<span class="built_in">string</span>, <span class="built_in">any</span>&gt;&gt;(</span><br><span class="line">  <span class="attr">data</span>: <span class="title class_">Omit</span>&lt;T, <span class="string">&#x27;id&#x27;</span> | <span class="string">&#x27;createdAt&#x27;</span> | <span class="string">&#x27;updatedAt&#x27;</span>&gt;</span><br><span class="line">): T &amp; &#123; <span class="attr">id</span>: <span class="built_in">string</span>; <span class="attr">createdAt</span>: <span class="title class_">Date</span>; <span class="attr">updatedAt</span>: <span class="title class_">Date</span> &#125; =&gt; &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>对我来说，这种写法已经开始让我迷糊了。</p></blockquote><p>如果试图把泛型放在常量名后面，就会出现语法错误：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// ❌ 错误的语法</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createEntity&lt;T <span class="keyword">extends</span> ...&gt; = <span class="function">(<span class="params">data: ...</span>) =&gt;</span> &#123; ... &#125;;</span><br><span class="line"><span class="comment">// SyntaxError: &#x27;=&#x27; expected.</span></span><br><span class="line"><span class="comment">// 因为 TypeScript 会认为 createEntity&lt;...&gt; 是一个类型或特殊语法，而不是变量名。</span></span><br></pre></td></tr></table></figure><p><strong>方式三：对象方法 + 泛型</strong></p><p>泛型语法：泛型 <code>&lt;T&gt;</code> 放在方法名之后，参数列表之前。</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">const</span> collection = &#123;</span><br><span class="line">  <span class="attr">items</span>: [],</span><br><span class="line">  add&lt;T&gt;(<span class="attr">item</span>: T): T[] &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">items</span>.<span class="title function_">push</span>(item);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">items</span>;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p><strong>方法四：函数类型 + 泛型</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义一个泛型函数类型</span></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">IdentityFunc</span>&lt;T&gt; = <span class="function">(<span class="params">arg: T</span>) =&gt;</span> T;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 应用这个类型</span></span><br><span class="line"><span class="keyword">const</span> <span class="attr">identity</span>: <span class="title class_">IdentityFunc</span>&lt;<span class="built_in">number</span>&gt; = <span class="function">(<span class="params">arg</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> arg;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>泛型的位置都是在参数列表前。</p><p>其实 TypeScript 的文档肯定很清晰且详细的解释并说明了这些，但是真的够用就行了，遇到不会的再看吧。</p><blockquote><p><a href="https://www.typescriptlang.org/docs/handbook/2/functions.html">https://www.typescriptlang.org/docs/handbook/2/functions.html</a></p></blockquote>]]></content>
    
    
    <summary type="html">写 ts 写得比较少，也不是持续的写，外加上 LLM 的帮助，对于 ts 的语法是有些怠惰了，这里整理下 ts 的函数定义。 加上自己对于各种东西都是够用就行，所以...</summary>
    
    
    
    <category term="typescript" scheme="http://blog.fooo.in/categories/typescript/"/>
    
    
  </entry>
  
  <entry>
    <title>Samsung S24 和 Samsung Watch 5 国行刷外版系统（可能通用）</title>
    <link href="http://blog.fooo.in/2024/12/16/phone/samsung-change-region/"/>
    <id>http://blog.fooo.in/2024/12/16/phone/samsung-change-region/</id>
    <published>2024-12-16T00:00:00.000Z</published>
    <updated>2024-12-16T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>Samsung 设备更改其他地区系统时需要退出 Google 账号和 Samsung 账号。</p><h2 id="Samsung-S24">Samsung S24</h2><ol><li>需要找到想要刷的指定地区版本的固件，一般通过以下两个网站查找：</li></ol><ul><li><a href="https://samfw.com">https://samfw.com</a></li><li><a href="https://sxrom.cn">https://sxrom.cn</a></li></ul><p>比如 S24 国行版系统固件：<a href="https://sxrom.cn/Find.aspx?MODEL=SM-S9210&amp;CSC=CHC">https://sxrom.cn/Find.aspx?MODEL=SM-S9210&amp;CSC=CHC</a></p><blockquote><p>S24 型号为 SM-S9210，CSC 是国家代码，这里的 CHC 是中国，港版为 TGY，台版为 BRI。</p></blockquote><p><strong>注意</strong>：确保互刷的系统版本<strong>安全策略</strong>版本对应正确：</p><blockquote><ul><li>ROM（刷机包）的倒数第 5 位，即是“安全政策”版本。这个数字，不能小于手机上“安全政策”版本，但可以等于。如有版本号为 N9760ZCS6HWH1 的“安全政策”版本就是 6。</li><li>手机上“安全政策”版本查找方式：手机设置 - 关于手机 - 软件信息 - 基带版本。</li><li>对比它们倒数第 5 位的数字（如下图）：如果 ROM 的版本低于手机，那么该 ROM 无法刷入手机，但也不会损坏手机，会在刷机一开始就报错。</li><li>如果该机型的国行和港版 ROM 可以互刷，也要遵循这个规则。</li><li>官方一般每隔几个月，在随 ROM 的更新时，更新一次这个数字，官方称之为“安全政策”更新。安全政策的更新之后，将无法退回。</li><li>港版的更新有时快于国行，如果恰好手机上港版已经升级到新的“安全政策”，但国行 ROM 还是老的“安全政策”版本，那么此时就无法刷到国行，请等待数日，国行 ROM 更新后即可刷回国行。</li><li>因为“安全政策”只用一个字符来表示，所以 9 之后就是 A，A 之后又是 B。</li></ul><p>源自：<a href="https://sxrom.cn/help4.aspx">https://sxrom.cn/help4.aspx</a></p></blockquote><ol start="2"><li>下载 Samsung Odin</li></ol><p>Samsung Odin 是适用于 Samsung Android 智能手机和平板电脑设备的刷机工具。</p><ul><li>Odin 下载地址：<a href="https://odindownload.com">https://odindownload.com</a>。</li><li>如果 Odin 连接不上设备（设备需要在 Download 模式下，参见后面步骤），可能还需要下载驱动：<a href="https://developer.samsung.com/android-usb-driver%E3%80%82">https://developer.samsung.com/android-usb-driver。</a></li></ul><ol start="3"><li><p>解压在第 1 步下载的固件，会发现有 AP, BL, CP, CSC, HOME-CSC 开头的这几个文件。</p></li><li><p>将手机进入 Download 模式（手机插上线，关机状态下同时按音量加减）看到警告界面后按音量上。解压官方四件套之后，使用 Odin 刷入。只需要将 BL、AP、CP、CSC 开头的 tarball 放入 Odin 的对应槽位刷入即可。</p></li><li><p>PASS，结束。</p></li></ol><p>刷回步骤应该也一样，没有测试过。</p><h2 id="Samsung-Watch-5">Samsung Watch 5</h2><ol><li>下载固件以及 NetOdin，但是 Watch 5 的固件比较少。</li></ol><blockquote><ul><li>NetOdin - <a href="https://t.me/swapplication/258">https://t.me/swapplication/258</a></li><li>Firmware for Galaxy Watch 5</li><li>Galaxy Watch 5 SM-R900 40 mm - <a href="https://t.me/swapplication/296">https://t.me/swapplication/296</a></li><li>Galaxy Watch 5 SM-R910 44 mm - <a href="https://t.me/swapplication/297">https://t.me/swapplication/297</a></li><li>Galaxy Watch 5 PRO SM - R920 45mm - <a href="https://t.me/swapplication/298">https://t.me/swapplication/298</a></li></ul><p>源自：<a href="https://xdaforums.com/t/flash-sgw5-with-netodin-succeed.4551903/post-89809935">https://xdaforums.com/t/flash-sgw5-with-netodin-succeed.4551903/post-89809935</a></p></blockquote><blockquote><p>我手上的是 44 mm。</p></blockquote><ol start="2"><li>刷写步骤。<ol><li>手表开机状态下，长按右侧两个按键，重启直到出现 SAMSUNG logo 且下方有 Rehbooting 字样时连按三次上键。</li><li>此时会进入一个界面（可能是 fastboot 模式？），这个模式下<strong>上键短按</strong>是切换，长按是选择。选择 Download(Wireless)。</li><li>电脑搜索连接手表的开启的热点，名称应该是手表的型号。</li><li>打开 NetOdin, 在 AP 栏选择指定的固件包，刷入即可。（似乎只支持 tar 包，如果下载的固件不是 tar 格式可以先解压再转成 tar 包)</li><li>PASS，结束。</li></ol></li></ol><h2 id="参考">参考</h2><ul><li><a href="https://www.coolapk.com/feed/48366338?shareKey=ODg3ZDg1YTE1OGMzNjVlZWQ3ZDU">https://www.coolapk.com/feed/48366338?shareKey=ODg3ZDg1YTE1OGMzNjVlZWQ3ZDU</a></li><li><a href="https://samfw.com">https://samfw.com</a></li><li><a href="https://sxrom.cn/help4.aspx">https://sxrom.cn/help4.aspx</a></li><li><a href="https://xdaforums.com/t/flash-sgw5-with-netodin-succeed.4551903/">https://xdaforums.com/t/flash-sgw5-with-netodin-succeed.4551903/</a></li><li><a href="https://xdaforums.com/t/flash-sgw5-with-netodin-succeed.4551903/post-89809935">https://xdaforums.com/t/flash-sgw5-with-netodin-succeed.4551903/post-89809935</a></li></ul>]]></content>
    
    
    <summary type="html">Samsung 设备更改其他地区系统时需要退出 Google 账号和 Samsung 账号。</summary>
    
    
    
    <category term="phone" scheme="http://blog.fooo.in/categories/phone/"/>
    
    
  </entry>
  
  <entry>
    <title>使用 Pandoc 把 Markdown 转为 PDF 文件</title>
    <link href="http://blog.fooo.in/2024/11/24/linux/pandoc-md-to-pdf-with-chinese/"/>
    <id>http://blog.fooo.in/2024/11/24/linux/pandoc-md-to-pdf-with-chinese/</id>
    <published>2024-11-24T00:00:00.000Z</published>
    <updated>2025-10-29T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>在 Arch Linux 上使用 Pandoc 把带中文的 Markdown 转换成 PDF。</p><h2 id="必要的软件和字体">必要的软件和字体</h2><p>Pandoc 我选择安装 bin 包，我可不想看到更新的时候一堆 Haskell 依赖。</p><figure class="highlight console"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pandoc-bin</span><br></pre></td></tr></table></figure><p>Tex Live 环境</p><figure class="highlight console"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">texlive-core texlive-langchinese texlive-latexextra texlive-bin</span><br></pre></td></tr></table></figure><h2 id="转换-Markdown-为-PDF">转换 Markdown 为 PDF</h2><p>Pandoc 默认使用的 pdflatex 命令无法处理 Unicode 字符，如果要把包含中文的 Markdown 文件转为 PDF，生成的 PDF 文件中文位置会是空白 <sup id="fnref:1"><a href="#fn:1" rel="footnote"><span class="hint--top hint--error hint--medium hint--rounded hint--bounce" aria-label="[pandoc FAQ](https://pandoc.org/faqs.html#i-get-a-blank-document-when-i-try-to-convert-a-markdown-document-in-chinese-to-pdf)">[1]</span></a></sup>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pandoc --pdf-engine=xelatex -V CJKmainfont=<span class="string">&quot;Source Han Sans CN&quot;</span> file.md -o file.pdf</span><br></pre></td></tr></table></figure><blockquote><p><code>--pdf-engine=xelatex</code>：使用 XeLaTeX 作为 PDF 生成引擎，支持中文。<code>-V CJKmainfont=&quot;Source Han Sans CN&quot;</code>：指定主中文字体，我这里使用思源。</p></blockquote><h2 id="参考资料">参考资料</h2><div id="footnotes"><hr><div id="footnotelist"><ol style="list-style: none; padding-left: 0; margin-left: 40px"><li id="fn:1"><span style="display: inline-block; vertical-align: top; padding-right: 10px; margin-left: -40px">1.</span><span style="display: inline-block; vertical-align: top; margin-left: 10px;"><a href="https://pandoc.org/faqs.html#i-get-a-blank-document-when-i-try-to-convert-a-markdown-document-in-chinese-to-pdf">pandoc FAQ</a><a href="#fnref:1" rev="footnote"> ↩</a></span></li></ol></div></div>]]></content>
    
    
    <summary type="html">在 Arch Linux 上使用 Pandoc 把带中文的 Markdown 转换成 PDF。</summary>
    
    
    
    <category term="linux" scheme="http://blog.fooo.in/categories/linux/"/>
    
    
  </entry>
  
  <entry>
    <title>MSS、MTU 和 TCP 拥塞堵塞</title>
    <link href="http://blog.fooo.in/2024/08/30/network/mss-mtu-tcp/"/>
    <id>http://blog.fooo.in/2024/08/30/network/mss-mtu-tcp/</id>
    <published>2024-08-30T00:00:00.000Z</published>
    <updated>2024-08-30T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h2 id="MTU">MTU</h2><p><a href="https://www.cloudflare.com/zh-cn/learning/network-layer/what-is-mtu/" title="什么是 MTU (最大传输单元)？">MTU</a>(Maximum Transmission Unit) 最大传输单元，是指数据链路层上面所能通过的最大数据包大小（以字节为单位）。这一分片过程发生在 IP 层（OSI 模型的第 3 层，即网络层）。</p><p>关于 <a href="#MTU">MTU</a> 和 <a href="#MSS">MSS</a>，<a href="https://www.cloudflare.com/zh-cn/learning/network-layer/what-is-mtu/" title="什么是 MTU (最大传输单元)？">cloudflare 文章中提供了一张非常好理解的图</a>：</p><p><img src="https://www.cloudflare.com/resources/images/slt3lc6tev37/29tC841gKxTb6c2fUFJro6/9c49654618fe84f3c00700629f30a6e4/MSS_TCP_segment_packet_diagram.png" alt="Data packet"></p><p>一个 MTU 最大 1500 bytes，如果超过这个数量则会被分片。</p><h2 id="MSS">MSS</h2><p><a href="https://www.cloudflare.com/zh-cn/learning/network-layer/what-is-mss/" title="什么是 MSS（最大分段大小）？">MSS</a>(Maximum Segment Size) 最大分段大小，限制通过网络（例如互联 网）传输的数据包或小数据块的大小。通过网络传输的所有数据都被分解成数据包。数据包附有几个标头，其中包含有关其内容和目的地的信息。MSS 测量数据包的非标头部分，称为有效负载。它只是一个参数，一个限制。</p><p>如果将数据包比作运输卡车，标头是卡车本身，有效负载是拖车和货物，那么 MSS 就像只测量拖车的秤。如果拖车过重，则不允许卡车继续前往目的地。</p><p>MSS 是第 4 层（传输层）的参数，与 TCP 一起使用。</p><p><code>MTU - (TCP 标头 + IP 标头) = MSS</code></p><p>IP 标头 和 TCP 标头 分别占用 20 bytes。所以一个 MSS 最大 1460 bytes。TCP 一个包最大是可以发送 1460 bytes。</p><h2 id="TCP-拥塞堵塞">TCP 拥塞堵塞</h2><p>TCP 发送数据包并不是每次只发送一个，而是一次发送一组包，然后等待 ACK，接着发送下一组包。这里就涉及到拥塞控制窗口 cwnd 与 TCP 的拥塞控制机制。</p><p>TCP 拥塞堵塞就是根据 MSS 的大小来控制。比如拥塞控制窗口 cwnd 大小为 10 的时候，TCP 连接建立起来之后会发 10 个 MSS 数据（TCP 的单位一般都是 MSS 计数），等待对等方的 ACK 之后再进行下一轮的数据发送，并且逐渐增大 cwnd 的值。<br>当发生拥塞堵塞时，会从慢启动切换到拥塞控制算法。关于这两种算法详细参见<a href="https://en.wikipedia.org/wiki/TCP_congestion_control" title="TCP congestion control">维基百科的解释</a>。</p><p>搭建了一个 100ms，关闭了 <code>tcp-segmentation-offload</code> 参数环境，在这里面测试的一段抓包数据：</p><figure class="highlight console"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"> 1   0.000000  192.168.1.2 → 192.168.1.1  TCP 74 43218 → 5001 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM TSval=984956032 TSecr=0 WS=128</span><br><span class="line"> 2   0.100707  192.168.1.1 → 192.168.1.2  TCP 74 5001 → 43218 [SYN, ACK] Seq=0 Ack=1 Win=65160 Len=0 MSS=1460 SACK_PERM TSval=3503908013 TSecr=984956032 WS=128</span><br><span class="line"> 3   0.100771  192.168.1.2 → 192.168.1.1  TCP 66 43218 → 5001 [ACK] Seq=1 Ack=1 Win=64256 Len=0 TSval=984956133 TSecr=3503908013</span><br><span class="line"> 4   0.102053  192.168.1.2 → 192.168.1.1  TCP 130 43218 → 5001 [PSH, ACK] Seq=1 Ack=1 Win=64256 Len=64 TSval=984956134 TSecr=3503908013</span><br><span class="line"> 5   0.102122  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [ACK] Seq=65 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line"> 6   0.102130  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [PSH, ACK] Seq=1513 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line"> 7   0.102161  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [ACK] Seq=2961 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line"> 8   0.102165  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [PSH, ACK] Seq=4409 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line"> 9   0.102176  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [ACK] Seq=5857 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line">10   0.102179  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [PSH, ACK] Seq=7305 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line">11   0.102187  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [ACK] Seq=8753 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line">12   0.102189  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [PSH, ACK] Seq=10201 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line">13   0.102196  192.168.1.2 → 192.168.1.1  TCP 1514 43218 → 5001 [ACK] Seq=11649 Ack=1 Win=64256 Len=1448 TSval=984956134 TSecr=3503908013</span><br><span class="line">14   0.202685  192.168.1.1 → 192.168.1.2  TCP 66 5001 → 43218 [ACK] Seq=1 Ack=65 Win=65152 Len=0 TSval=3503908115 TSecr=984956134</span><br></pre></td></tr></table></figure><blockquote><p>第二个字段是时间。</p></blockquote><p>发现 1-2 之间花费了 1 个 RTT(100ms)，13-14 之间也花费了 1 个 RTT，第一个 RTT 用于建立连接，第二个 RTT 传输数据。4-13 则是 cwnd 的大小。</p><p>可以尝试修改 cwnd 的值，查看慢启动时的变化。尝试使用 <code>sudo ip netns exec ns2 ip route add default via 192.168.1.1 dev veth1 initcwnd 20</code> 但修改未生效。</p><h2 id="TSO-tcp-segmentation-offload">TSO(tcp-segmentation-offload)</h2><p>我们抓包会发现实际上的 tcp payload 大小会超过 MTU(1500) 限制，其中一个原因（其他未了解）是开启了 <a href="https://www.kernel.org/doc/html/latest/networking/segmentation-offloads.html#tcp-segmentation-offload" title="TCP Segmentation Offload">tcp-segmentation-offload 参数</a>，<br>这个参数利用 NIC（网络接收卡）接收较大的数据包，并在硬件层面将其分成适合 MTU 的较小段发送到网络。所以我们抓到的包实际上会显示成更大的包，<br>但是在网络上传输的是被分段的小包。</p><p>关闭 <code>tcp-segmentation-offload</code>，以后面测试环境为例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo ip netns <span class="built_in">exec</span> ns2 ethtool --show-offload veth1 | grep offload</span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns2 ethtool -K veth1 tso off</span><br></pre></td></tr></table></figure><h2 id="测试环境构建">测试环境构建</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">sudo ip <span class="built_in">link</span> add veth0 <span class="built_in">type</span> veth peer name veth1</span><br><span class="line">ip <span class="built_in">link</span></span><br><span class="line"></span><br><span class="line">sudo ip netns add ns1</span><br><span class="line">sudo ip netns add ns5</span><br><span class="line">sudo ip netns list</span><br><span class="line"></span><br><span class="line">sudo ip <span class="built_in">link</span> <span class="built_in">set</span> veth0 netns ns1</span><br><span class="line">sudo ip <span class="built_in">link</span> <span class="built_in">set</span> veth1 netns ns2</span><br><span class="line"></span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 ip <span class="built_in">link</span></span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns2 ip <span class="built_in">link</span></span><br><span class="line"></span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 ip addr add 192.168.1.1/24 dev veth0</span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 ip <span class="built_in">link</span> <span class="built_in">set</span> veth0 up</span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns2 ip addr add 192.168.1.2/24 dev veth1</span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns2 ip <span class="built_in">link</span> <span class="built_in">set</span> veth1 up</span><br><span class="line"></span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 ping 192.168.1.2</span><br><span class="line"></span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 tc qdisc add dev veth0 root netem rate 1mbit delay 100ms</span><br><span class="line"></span><br><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 ping 192.168.1.2 <span class="comment"># 会发现延迟为 100 ms 以上</span></span><br></pre></td></tr></table></figure><p>在 ns1 中启动 <code>iperf</code> 服务器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 iperf -s -B 192.168.1.1</span><br></pre></td></tr></table></figure><p>tcpdump 抓包：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ip netns <span class="built_in">exec</span> ns1 tcpdump -i veth0 -w iperf_traffic_ns1.pcap</span><br></pre></td></tr></table></figure><p>同样，可以在 ns2 中抓取 veth1 接口的数据包：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ip netns <span class="built_in">exec</span> ns2 tcpdump -i veth1 -w iperf_traffic_ns2.pcap</span><br></pre></td></tr></table></figure><p>在 ns2 中启动 <code>iperf</code> 客户端：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ip netns <span class="built_in">exec</span> ns2 iperf -c 192.168.1.1 -B 192.168.1.2 -t 30</span><br></pre></td></tr></table></figure><p>这里测试 30 秒。</p><p>清理环境：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo ip netns del ns1</span><br><span class="line">sudo ip netns del ns2</span><br></pre></td></tr></table></figure><hr>]]></content>
    
    
    <summary type="html">一个 MTU 最大 1500 bytes，如果超过这个数量则会被分片。如果将数据包比作运输卡车，标头是卡车本身，有效负载是拖车和货物，那么 MSS 就像只测量拖车的秤。如果拖车过重，则不允许卡车继续前往目的地。</summary>
    
    
    
    <category term="network" scheme="http://blog.fooo.in/categories/network/"/>
    
    
  </entry>
  
  <entry>
    <title>网络分层模型</title>
    <link href="http://blog.fooo.in/2024/08/28/network/network-models/"/>
    <id>http://blog.fooo.in/2024/08/28/network/network-models/</id>
    <published>2024-08-28T00:00:00.000Z</published>
    <updated>2025-10-29T16:32:04.110Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>模型这些抽象的概念，用于帮助自己理解一些网络架构，但是发现面对一些名词或者概念时总是无法对上，想整理记录一下。</p><h2 id="OSI-模型">OSI 模型</h2><p>OSI 模型的七层包括：</p><ul><li><ol start="7"><li>应用程序层：由软件应用程序生成并可由其使用的数据。该层使用的主 要协议是 HTTP。</li></ol></li><li><ol start="6"><li>表示层：将数据转换为应用程序可以接受的形式。一些机构认为 HTTPS 加密和解密在该层进行。</li></ol></li><li><ol start="5"><li>会话层：控制计算机之间的连接（这也可以由 TCP 协议在第 4 层处理）。</li></ol></li><li><ol start="4"><li>传输层：提供在两个连接设备之间传输数据以及控制服务质量的方法。这里使用的主要协议是 TCP 和 UDP。</li></ol></li><li><ol start="3"><li>网络层：处理不同网络之间的数据路由和发送。该层使用的最重要协议 是 IP 和 ICMP。</li></ol></li><li><ol start="2"><li>数据链路层：处理同一网络中不同设备之间的通信。如果第 3 层比作一封邮件上的地址，那么第 2 层就指明了该地址的办公室号码或公寓号码。以 太网是这里最常用的协议。</li></ol></li><li><ol><li>物理层：数据包被转换为电脉冲、无线电脉冲或光脉冲，并以比特（信 息的最小单位）的形式通过电线、无线电波或电缆进行传输。</li></ol></li></ul><blockquote><p>想起考试时背的一串 <code>应表会传网数物</code>。</p></blockquote><p><a href="https://www.cloudflare.com/zh-cn/learning/network-layer/what-is-the-network-layer/" title="什么是网络层？|网络与 Internet 层">Cloudflare 的一篇文章</a> 中提到：</p><blockquote><p>请务必记住，OSI 模型是对使得 Internet 正常工作的众多流程的概念化的抽象描述，阐述模型以及将其应用到现实世界中的 Internet 时，有时是主观的。OSI 模型有助于人们讨论网络设备和协议，确定哪些协议由哪些软件和硬件使用，并大致显示 Internet 的工作方式。但它并未严格地分步定义 Internet 连接的普遍运作方式。</p></blockquote><p>没必要去严格的根据分层模型去学习 Internet 的工作方式，对其有一个大致的了解即可。比如，经常会被询问到 xxx 协议属于哪一层？<br>我觉得这个问题不太好回答，引用<a href="https://www.kawabangga.com/posts/6295" title="理解网络的分层模型">其他博客</a>中的说明：</p><blockquote><p>我觉得这种讨论没有意义。因为我们实际上在讨论两个东西而且试图将两个概念融合在一起。即：</p><pre><code>协议工作在几层（基于几层实现）？协议为几层提供服务？</code></pre><p>如果分开讨论，就清楚很多了。ARP 基于二层（意味着只用到了二层的功能，不需要三层的东西就可以工作）实现，为三层提供服务，帮助找到 IP 对应的 MAC 地址。<br>我们可以给很多有类似争议的协议定义：</p><pre><code>TLS 基于四层实现，为应用层提供服务；TCP 基于三层实现，为应用层提供服务；ICMP 工作在三层，为三层提供服务；EIGRP 和 OSPF 基于 IP 协议，为三层提供服务；BGP 基于四层实现，为三层提供服务（是不是很神奇？因为 BGP 通过 TCP 交换路由信息，为三层提供转发路由）；</code></pre></blockquote><h2 id="TCP-IP-模型">TCP/IP 模型</h2><ul><li><ol start="4"><li>应用程序层：这大致相当于 OSI 模型中的第 7 层。</li></ol></li><li><ol start="3"><li>传输层：对应于 OSI 模型中的第 4 层。</li></ol></li><li><ol start="2"><li>互联网层：对应于 OSI 模型中的第 3 层。</li></ol></li><li><ol><li>网络访问层：结合了 OSI 模型中第 1 层和第 2 层的过程。</li></ol></li></ul><h2 id="分层的意义">分层的意义</h2><ul><li><a href="https://www.kawabangga.com/posts/6295" title="理解网络的分层模型">为了让每一个协议可以处理部分的问题，不同的协议之间可以互相工作</a></li><li><a href="https://www.kawabangga.com/posts/6295" title="理解网络的分层模型">方便排查问题，可以将问题定位在某一层</a></li></ul><hr>]]></content>
    
    
    <summary type="html">模型这些抽象的概念，用于帮助自己理解一些网络架构，但是发现面对一些名词或者概念时总是无法对上，想整理记录一下。</summary>
    
    
    
    <category term="network" scheme="http://blog.fooo.in/categories/network/"/>
    
    
  </entry>
  
  <entry>
    <title>Linux Network sysctl parameters</title>
    <link href="http://blog.fooo.in/2024/08/19/network/linux-network-performance-parameters/"/>
    <id>http://blog.fooo.in/2024/08/19/network/linux-network-performance-parameters/</id>
    <published>2024-08-19T00:00:00.000Z</published>
    <updated>2024-08-20T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h2 id="In-Process（接收数据包）">In Process（接收数据包）</h2><ol><li>数据包到达 NIC:</li></ol><ul><li>MAC 验证和 FCS 检查：NIC 会检查数据包的 MAC 地址 (如果 NIC 不处于混杂模式) 和帧校验序列 (FCS)。如果 MAC 地址不匹配或 FCS 检查失败，数据包会被丢弃。</li><li>DMA 传输：如果数据包通过了验证，NIC 会使用直接内存访问 (DMA) 将数据包传输到内存中。内存区域由驱动程序预先准备并映射。</li><li>接收环形缓冲区：NIC 会将数据包的引用添加到接收环形缓冲区 (rx) 中。接收环形缓冲区是一个用于存储待处理数据包的队列。</li><li>硬件中断：NIC 会发出一个硬件中断 (IRQ) 通知 CPU 数据包已到达。</li></ul><ol start="2"><li>驱动程序处理：</li></ol><ul><li>中断处理程序：CPU 会运行中断处理程序，该程序会执行驱动程序代码。</li><li>NAPI 调度：驱动程序会调度一个 NAPI (网络协议中断)，清除硬件中断并返回。</li><li>软中断：驱动程序会发出一个软中断 (NET_RX_SOFTIRQ)。</li><li>NAPI 循环：NAPI 会从接收环形缓冲区中轮询数据包，直到达到预设的时间限制或数据包数量限制。</li></ul><ol start="3"><li>Linux 内核处理：</li></ol><ul><li>内存分配：Linux 会为数据包分配一个 sk_buff 结构体，用于存储数据包的元数据。</li><li>元数据填充：Linux 会填充 sk_buff 结构体的元数据，包括协议类型、接口信息、MAC 头部等。</li><li>内核堆栈：Linux 会将 sk_buff 传递给内核堆栈 (netif_receive_skb)。</li><li>网络头部：内核会设置网络头部，并将 sk_buff 克隆到其他地方 (例如 tcpdump) 用于调试。</li><li>流量控制：数据包会进入一个由 netdev_max_backlog 参数定义的队列调度算法 (qdisc) 中。默认的队列调度算法由 default_qdisc 参数定义。</li><li>IP 处理：数据包会被传递给 IP 层 (ip_rcv)。</li><li>网络过滤器：数据包会经过网络过滤器 (netfilter) 的 PREROUTING 阶段。</li><li>路由查找：内核会查找路由表，决定数据包是进行转发还是本地处理。</li><li>本地处理：如果数据包是本地处理的，它会经过网络过滤器 (LOCAL_IN) 阶段。</li><li>L4 处理：数据包会被传递给 L4 协议 (例如 TCP) 的处理函数 (例如 tcp_v4_rcv)。</li></ul><ol start="4"><li>应用程序处理：</li></ol><ul><li>套接字查找：内核会找到与数据包匹配的套接字。</li><li>TCP 状态机：数据包会进入 TCP 状态机。</li><li>接收缓冲区：数据包会被添加到接收缓冲区中，缓冲区大小由 tcp_rmem 规则定义。</li><li>缓冲区自适应调整：如果 tcp_moderate_rcvbuf 启用，内核会自动调整接收缓冲区的大小。</li><li>信号通知：内核会向应用程序发送信号，通知应用程序有数据可读 (例如 epoll 或其他轮询机制)。</li><li>应用程序读取：应用程序会醒来并读取数据。</li></ul><h2 id="Out-Process（发送数据包）">Out Process（发送数据包）</h2><ol><li>应用程序发送数据：</li></ol><ul><li>数据包分配：应用程序会使用 sendmsg 或其他方法发送数据包，并为数据包分配一个 sk_buff 结构体。</li><li>套接字写缓冲区：sk_buff 会被添加到套接字的写缓冲区中，缓冲区大小由 tcp_wmem 参数定义。</li></ul><ol start="2"><li>TCP 处理：</li></ol><ul><li>TCP 头部构建：TCP 会构建 TCP 头部，包括源端口、目标端口和校验和。</li><li>L3 处理：TCP 会调用 L3 处理函数 (例如 tcp_write_xmit 和 tcp_transmit_skb)，将数据包传递给 IP 层。</li></ul><ol start="3"><li>IP 处理：</li></ol><ul><li>IP 头部构建：IP 层会构建 IP 头部，并调用网络过滤器 (LOCAL_OUT) 进行处理。</li><li>路由查找：IP 层会查找路由表，决定数据包的输出路径。</li><li>网络过滤器：数据包会经过网络过滤器 (POST_ROUTING) 阶段。</li><li>数据包分片：如果数据包太大，IP 层会将数据包分片 (ip_output)。</li></ul><ol start="4"><li>L2 处理：</li></ol><ul><li>发送函数：IP 层会调用 L2 发送函数 (dev_queue_xmit)，将数据包传递给网络接口卡 (NIC)。</li></ul><ol start="5"><li>NIC 处理：</li></ol><ul><li>发送队列：数据包会进入 NIC 的发送队列 (txqueuelen)，队列调度算法由 default_qdisc 参数定义。</li><li>环形缓冲区：NIC 驱动程序会将数据包添加到发送环形缓冲区 (tx) 中。</li><li>软中断：驱动程序会发出一个软中断 (NET_TX_SOFTIRQ)，通知 CPU 数据包已准备好发送。</li><li>硬件中断使能：驱动程序会重新使能 NIC 的硬件中断。</li><li>DMA 传输：驱动程序会将数据包从内存映射到 DMA 区域，并由 NIC 通过 DMA 传输到网络。</li><li>硬件中断：NIC 发送完毕后，会发出一个硬件中断，通知 CPU 数据包已发送完成。</li></ul><ol start="6"><li>驱动程序处理：</li></ol><ul><li>中断处理：驱动程序会处理硬件中断，并关闭中断。</li><li>NAPI 轮询：驱动程序会调度 NAPI 轮询系统，处理接收数据包并释放内存。</li></ul><h2 id="环形缓冲区-Ring-Buffer-tx-rx">环形缓冲区 (Ring Buffer - tx,rx)</h2><p>接收环形缓冲区 (rx):</p><ul><li>作用：接收环形缓冲区用于缓冲从网络接收到的数据包。当 NIC 接收数据包的速度超过内核处理数据包的速度时，数据包会被放入接收环形缓冲区中等待处理。</li><li>为什么要使用接收环形缓冲区：使用接收环形缓冲区可以避免数据包丢失，特别是当网络流量突然增加时。</li><li>调整大小：如果出现数据包丢失或溢出 (即接收数据包的速度超过内核处理速度)，可以尝试增加接收环形缓冲区的大小。</li><li>副作用：增加接收环形缓冲区的大小可能会增加延迟。</li></ul><p>发送环形缓冲区 (tx):</p><ul><li>作用：发送环形缓冲区用于存储待发送的数据包。当应用程序发送数据包的速度超过 NIC 处理数据包的速度时，数据包会被放入发送环形缓冲区中等待发送。</li><li>为什么要使用发送环形缓冲区：使用发送环形缓冲区可以避免数据包丢失，并提高网络性能。</li><li>调整大小：如果出现数据包丢失或溢出，可以尝试增加发送环形缓冲区的大小。</li><li>副作用：增加发送环形缓冲区的大小可能会增加延迟。</li></ul><p>如何查看和调整环形缓冲区大小：</p><ul><li>查看命令：<code>ethtool -g ethX</code> | --show-ring</li><li>调整命令：<code>ethtool -G ethX rx value tx value</code> | --set-ring (其中 value 是新的环形缓冲区大小)</li></ul><p>如何监控环形缓冲区状态：</p><ul><li>监控命令：<code>ethtool -S ethX | grep -e &quot;err&quot; -e &quot;drop&quot; -e &quot;over&quot; -e &quot;miss&quot; -e &quot;timeout&quot; -e &quot;reset&quot; -e &quot;restar&quot; -e &quot;collis&quot; -e &quot;over&quot; | grep -v &quot;\: 0&quot;</code> | --statistics</li><li>监控指标：该命令会显示一些错误、丢包、溢出、超时等指标，可以用来监控环形缓冲区的状态。</li></ul><h2 id="中断合并-Interrupt-Coalescence-IC">中断合并 (Interrupt Coalescence, IC)</h2><ul><li>定义：中断合并技术是指将多个硬件中断合并成一个中断，以减少 CPU 中断次数，从而提高 CPU 利用率和网络性能。</li><li>工作原理：NIC 会在收到一定数量的数据包或经过一定时间后，才会发出一个硬件中断。</li><li>参数：中断合并技术通常使用以下参数来控制：<br>rx-usecs: 接收数据包时，NIC 等待的时间 (微秒)，超过该时间才会发出中断。<br>tx-usecs: 发送数据包时，NIC 等待的时间 (微秒)，超过该时间才会发出中断。<br>rx-frames: 接收数据包时，NIC 等待的帧数，超过该帧数才会发出中断。<br>tx-frames: 发送数据包时，NIC 等待的帧数，超过该帧数才会发出中断。</li></ul><p>为什么要使用中断合并：</p><ul><li>减少 CPU 使用率：中断合并可以减少 CPU 处理中断的次数，从而降低 CPU 使用率。</li><li>提高网络吞吐量：减少中断次数可以提高网络吞吐量，因为 CPU 可以花更多时间处理数据包，而不是处理中断。</li></ul><p>中断合并的副作用：</p><ul><li>增加延迟：中断合并可能会增加延迟，因为 NIC 需要等待更长时间才能发出中断。</li></ul><p>如何查看和调整中断合并参数：</p><ul><li>查看命令：<code>ethtool -c ethX</code> | --show-coalesce</li><li>调整命令：<code>ethtool -C ethX rx-usecs value tx-usecs value</code> | --coalesce (其中 value 是新的中断合并参数值)</li></ul><p>如何监控中断状态：</p><ul><li>监控命令：cat /proc/interrupts</li><li>监控指标：该命令会显示每个中断的触发次数，可以用来监控中断合并的效果。</li></ul><h2 id="软中断合并-Interrupt-Coalescing-soft-IRQ-和入站队列-ingress-qdisc">软中断合并 (Interrupt Coalescing, soft IRQ) 和入站队列 (ingress qdisc)</h2><ul><li><code>netdev_budget_usecs</code>: 定义了 NAPI (网络协议中断) 轮询周期中允许的最大时间长度 (微秒)。NAPI 轮询会持续进行，直到时间超过 netdev_budget_usecs 或处理的数据包数量达到 netdev_budget。</li><li>作用：软中断合并可以减少 CPU 处理软中断的次数，提高 CPU 利用率和网络性能。</li><li>监控指标：<code>cat /proc/net/softnet_stat</code> 可以查看软中断合并的统计信息，包括 dropped (由于 netdev_max_backlog 溢出而丢弃的数据包数量) 和 squeezed (由于 netdev_budget 或时间限制而无法处理完的数据包数量)。</li><li>调整命令：<code>sysctl -w net.core.netdev_budget_usecs value</code> 可以调整 netdev_budget_usecs 的值。</li></ul><blockquote><p>可以使用 <code>sysctl parameter</code> 来查看值，包括后面。</p></blockquote><h3 id="NAPI-轮询预算-netdev-budget">NAPI 轮询预算 (netdev_budget):</h3><ul><li><code>netdev_budget</code>: 定义了 NAPI 轮询周期中允许从所有网络接口处理的最大数据包数量。在一次轮询周期中，所有注册了 NAPI 轮询的网络接口会以循环方式进行轮询。</li><li>作用：<code>netdev_budget</code> 限制了 NAPI 轮询周期中处理的数据包数量，避免单个网络接口占用过多 CPU 资源。</li><li>监控指标：<code>cat /proc/net/softnet_stat</code> 可以查看 NAPI 轮询的统计信息。</li><li>调整命令：<code>sysctl -w net.core.netdev_budget value</code> 可以调整 netdev_budget 的值。</li></ul><h3 id="网络接口权重-dev-weight">网络接口权重 (dev_weight):</h3><ul><li><code>dev_weight</code>: 定义了内核在 NAPI 中断中处理单个网络接口的最大数据包数量，这是一个每个 CPU 的变量。对于支持 LRO (大型接收卸载) 或 GRO_HW (硬件聚合接收) 的驱动程序，一个硬件聚合的数据包会被计为一个数据包。</li><li>作用：<code>dev_weight</code> 限制了 NAPI 中断中处理单个网络接口的数据包数量，避免单个网络接口占用过多 CPU 资源。</li><li>监控指标：<code>cat /proc/net/softnet_stat</code> 可以查看 NAPI 轮询的统计信息。</li><li>调整命令：<code>sysctl -w net.core.dev_weight value</code> 可以调整 dev_weight 的值。</li></ul><h3 id="入站队列最大长度-netdev-max-backlog">入站队列最大长度 (netdev_max_backlog):</h3><ul><li><code>netdev_max_backlog</code>: 定义了网络接口接收数据包的速度超过内核处理速度时，在入站队列 (ingress qdisc) 中允许的最大数据包数量。</li><li>作用：<code>netdev_max_backlog</code> 限制了入站队列的大小，避免数据包溢出导致丢包。</li><li>监控指标：<code>cat /proc/net/softnet_stat</code> 可以查看入站队列的统计信息。</li><li>调整命令：<code>sysctl -w net.core.netdev_max_backlog value</code> 可以调整 <code>netdev_max_backlog</code> 的值。</li></ul><h3 id="softnet-stat">softnet_stat</h3><p><a href="https://insights-core.readthedocs.io/en/latest/shared_parsers_catalog/softnet_stat.html">https://insights-core.readthedocs.io/en/latest/shared_parsers_catalog/softnet_stat.html</a></p><h2 id="出站队列长度-txqueuelen-和-默认队列调度器-default-qdisc">出站队列长度 (txqueuelen) 和 默认队列调度器 (default_qdisc)</h2><h3 id="出站队列长度-txqueuelen">出站队列长度 (txqueuelen):</h3><ul><li><code>txqueuelen</code>: 定义了网络接口发送数据包的速度超过网络处理速度时，在出站队列 (egress qdisc) 中允许的最大数据包数量。</li><li>作用：<code>txqueuelen</code> 限制了出站队列的大小，避免数据包溢出导致丢包。它也为流量控制 (Traffic Control, TC) 提供了一个缓冲区，可以用来应对连接突发和流量整形。</li><li>监控指标：<code>ip -s link</code> 可以查看出站队列的统计信息。</li><li>调整命令：<code>ip link set dev ethX txqueuelen N</code> 可以调整 txqueuelen 的值，其中 ethX 是网络接口名称，N 是新的队列长度。</li></ul><h3 id="默认队列调度器-default-qdisc">默认队列调度器 (default_qdisc):</h3><ul><li><code>default_qdisc</code>: 定义了内核为网络设备使用的默认队列调度器。</li><li>作用：队列调度器负责管理出站队列中的数据包，并决定数据包发送的顺序。不同的队列调度器有不同的算法，可以用来优化网络性能，例如减少延迟、提高吞吐量、防止缓冲区膨胀 (bufferbloat) 等。</li><li>监控指标：<code>tc -s qdisc ls dev ethX</code> 可以查看当前使用的队列调度器。</li><li>调整命令：<code>sysctl -w net.core.default_qdisc value</code> 可以调整 default_qdisc 的值，其中 value 是新的队列调度器名称。</li></ul><p>常见队列调度器：</p><ul><li>pfifo_fast: 先入先出 (FIFO) 队列调度器，简单高效，但无法进行流量控制。</li><li>pfifo_backlog: 与 pfifo_fast 相似，但会记录每个连接的已发送数据包数量，可以用来防止连接突发。</li><li>codel: 一种基于延迟的队列调度器，可以有效地防止缓冲区膨胀。</li><li>fq_codel: 一种基于公平度的队列调度器，可以保证不同连接的公平性。</li><li>htb: 一种基于层次结构的队列调度器，可以用来进行复杂的流量控制。</li></ul><h2 id="TCP-Read-and-Write-Buffers-Queues">TCP Read and Write Buffers/Queues</h2><p><strong>内存压力策略由 <code>tcp_mem</code> 和 <code>tcp_moderate_rcvbuf</code> 定义。</strong></p><h3 id="接收缓冲区-tcp-rmem">接收缓冲区 (<code>tcp_rmem</code>)</h3><ul><li><strong>作用：</strong> <code>tcp_rmem</code> 定义了 TCP 套接字接收缓冲区的大小，用来存储从网络接收到的数据。接收缓冲区可以帮助应用程序应对网络延迟和突发流量，并提高数据传输效率。</li><li><strong>参数：</strong> <code>tcp_rmem</code> 包含三个值：<ul><li><code>min</code>: 在系统内存压力较大的情况下，接收缓冲区会缩小到 <code>min</code> 的大小。</li><li><code>default</code>: TCP 套接字创建时，接收缓冲区的大小会初始化为 <code>default</code> 的大小。</li><li><code>max</code>: 接收缓冲区的大小不会超过 <code>max</code> 的大小。</li></ul></li><li><strong>监控指标：</strong> <code>cat /proc/net/sockstat</code> 可以查看 TCP 套接字的接收缓冲区使用情况。</li><li><strong>调整命令：</strong> <code>sysctl -w net.ipv4.tcp_rmem=&quot;min default max&quot;</code> 可以调整 <code>tcp_rmem</code> 的值。</li></ul><h3 id="发送缓冲区-tcp-wmem">发送缓冲区 (<code>tcp_wmem</code>)</h3><ul><li><strong>作用：</strong> <code>tcp_wmem</code> 定义了 TCP 套接字发送缓冲区的大小，用来存储要发送到网络的数据。发送缓冲区可以帮助应用程序应对网络延迟和突发流量，并提高数据传输效率。</li><li><strong>参数：</strong> <code>tcp_wmem</code> 包含三个值：<ul><li><code>min</code>: 在系统内存压力较大的情况下，发送缓冲区会缩小到 <code>min</code> 的大小。</li><li><code>default</code>: TCP 套接字创建时，发送缓冲区的大小会初始化为 <code>default</code> 的大小。</li><li><code>max</code>: 发送缓冲区的大小不会超过 <code>max</code> 的大小。</li></ul></li><li><strong>监控指标：</strong> <code>cat /proc/net/sockstat</code> 可以查看 TCP 套接字的发送缓冲区使用情况。</li><li><strong>调整命令：</strong> <code>sysctl -w net.ipv4.tcp_wmem=&quot;min default max&quot;</code> 可以调整 <code>tcp_wmem</code> 的值。</li></ul><h3 id="接收缓冲区自动调整-tcp-moderate-rcvbuf">接收缓冲区自动调整 (<code>tcp_moderate_rcvbuf</code>)</h3><ul><li><strong>作用：</strong> <code>tcp_moderate_rcvbuf</code> 启用后，TCP 会根据网络条件自动调整接收缓冲区的大小，以优化数据传输效率。</li><li><strong>监控指标：</strong> <code>cat /proc/net/sockstat</code> 可以查看 TCP 套接字的接收缓冲区使用情况。</li><li><strong>调整命令：</strong> <code>sysctl -w net.ipv4.tcp_moderate_rcvbuf value</code> 可以启用或禁用接收缓冲区自动调整功能。</li></ul><p><strong>注意：</strong></p><ul><li>调整 <code>tcp_rmem</code> 和 <code>tcp_wmem</code> 的值可能会影响应用程序的性能，建议在调整之前进行测试。</li><li>调整 <code>tcp_rmem</code> 和 <code>tcp_wmem</code> 的值需要重启应用程序才能生效。</li><li>启用 <code>tcp_moderate_rcvbuf</code> 会让内核自动调整接收缓冲区的大小，可能会导致一些应用程序出现问题，建议谨慎使用。</li></ul><p><strong>命令汇总：</strong></p><table><thead><tr><th>参数</th><th>查看命令</th><th>调整命令</th><th>监控指标</th></tr></thead><tbody><tr><td><code>tcp_rmem</code></td><td><code>sysctl net.ipv4.tcp_rmem</code></td><td><code>sysctl -w net.ipv4.tcp_rmem=&quot;min default max&quot;</code></td><td><code>cat /proc/net/sockstat</code></td></tr><tr><td><code>tcp_wmem</code></td><td><code>sysctl net.ipv4.tcp_wmem</code></td><td><code>sysctl -w net.ipv4.tcp_wmem=&quot;min default max&quot;</code></td><td><code>cat /proc/net/sockstat</code></td></tr><tr><td><code>tcp_moderate_rcvbuf</code></td><td><code>sysctl net.ipv4.tcp_moderate_rcvbuf</code></td><td><code>sysctl -w net.ipv4.tcp_moderate_rcvbuf value</code></td><td><code>cat /proc/net/sockstat</code></td></tr></tbody></table><hr><h2 id="参考">参考</h2><ul><li><a href="https://github.com/leandromoreira/linux-network-performance-parameters">linux-network-performance-parameters</a></li></ul>]]></content>
    
    
    <summary type="html">1. 数据包到达 NIC&quot;:&quot;- MAC 验证和 FCS 检查&quot;:&quot; NIC 会检查数据包的 MAC 地址 (如果 NIC 不处于混杂模式) 和帧校验序列 (FCS)。如果 MAC 地址不匹配或 FCS 检查失败，数据包会被丢弃。</summary>
    
    
    
    <category term="network" scheme="http://blog.fooo.in/categories/network/"/>
    
    
  </entry>
  
  <entry>
    <title>EPUB 格式规范</title>
    <link href="http://blog.fooo.in/2024/08/14/interesting-things/epub-spec/"/>
    <id>http://blog.fooo.in/2024/08/14/interesting-things/epub-spec/</id>
    <published>2024-08-14T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>EPUB 格式是一种 zip 格式，大概由这些内容组成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">❯ tree</span><br><span class="line">.</span><br><span class="line">├── META-INF</span><br><span class="line">│   └── container.xml</span><br><span class="line">├── mimetype</span><br><span class="line">└── OEBPS</span><br><span class="line">    ├── content.opf</span><br><span class="line">    ├── Images</span><br><span class="line">    │   ├── book01_01.jpg</span><br><span class="line">    │   ├── book01_02.jpg</span><br><span class="line">    │   ├── book01_03.jpg</span><br><span class="line">    │   ├── book01_04.jpg</span><br><span class="line">    │   ├── book01_05.jpg</span><br><span class="line">    │   ├── book01_06.jpg</span><br><span class="line">    │   └── book01_info.jpg</span><br><span class="line">    ├── Styles</span><br><span class="line">    │   └── style.css</span><br><span class="line">    ├── Text</span><br><span class="line">    │   ├── book01_back_cover.xhtml</span><br><span class="line">    │   ├── book01_front_cover.xhtml</span><br><span class="line">    │   ├── book01_illustration01.xhtml</span><br><span class="line">    │   ├── book01_illustration02.xhtml</span><br><span class="line">    │   ├── book01_illustration03.xhtml</span><br><span class="line">    │   ├── book01_illustration04.xhtml</span><br><span class="line">    │   ├── book01_index.xhtml</span><br><span class="line">    │   ├── book01_info.xhtml</span><br><span class="line">    │   ├── by.xhtml</span><br><span class="line">    │   ├── cover.xhtml</span><br><span class="line">    │   ├── Section0001.xhtml</span><br><span class="line">    │   ├── Section0002.xhtml</span><br><span class="line">    │   ├── Section0003.xhtml</span><br><span class="line">    │   ├── Section0004.xhtml</span><br><span class="line">    │   ├── Section0005.xhtml</span><br><span class="line">    │   ├── Section0006.xhtml</span><br><span class="line">    │   ├── Section0007.xhtml</span><br><span class="line">    │   ├── Section0008.xhtml</span><br><span class="line">    │   ├── Section0009.xhtml</span><br><span class="line">    │   ├── Section0010.xhtml</span><br><span class="line">    │   ├── Section0011.xhtml</span><br><span class="line">    │   ├── Section0012.xhtml</span><br><span class="line">    │   ├── Section0013.xhtml</span><br><span class="line">    │   ├── Section0014.xhtml</span><br><span class="line">    │   └── Section0015.xhtml</span><br><span class="line">    └── toc.ncx</span><br></pre></td></tr></table></figure><ul><li>Mime Type file: application/epub+zip.</li><li>META-INF folder (container file which points to the location of the .opf file), signatures, encryption, rights, are xml files</li><li>OEBPS folder stores the book content .(opf, ncx, html, svg, png, css, etc. files)</li></ul><h2 id="META-INF">META-INF</h2><p>这个文件夹下主要存储关于 EPUB 文件本身的元数据信息和描述性信息。</p><h3 id="Container-–-META-INF-container-xml">Container – META-INF/container.xml</h3><p><code>container.xml</code> 文件必须标识容器中包含的 EPUB 出版物的根文件的媒体类型和路径。</p><p>示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot;?&gt;</span><br><span class="line">&lt;container version=&quot;1.0&quot; xmlns=&quot;urn:oasis:names:tc:opendocument:xmlns:container&quot;&gt;</span><br><span class="line">    &lt;rootfiles&gt;</span><br><span class="line">        &lt;rootfile full-path=&quot;OEBPS/My Crazy Life.opf&quot;</span><br><span class="line">            media-type=&quot;application/oebps-package+xml&quot; /&gt;</span><br><span class="line">    &lt;/rootfiles&gt;</span><br><span class="line">&lt;/container&gt;</span><br></pre></td></tr></table></figure><blockquote><p>带有根文件 <code>OEBPS/My Crazy Life.opf</code>的 EPUB 出版物</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot;?&gt;</span><br><span class="line">&lt;container version=&quot;1.0&quot; xmlns=&quot;urn:oasis:names:tc:opendocument:xmlns:container&quot;&gt;</span><br><span class="line">    &lt;rootfiles&gt;</span><br><span class="line">        &lt;rootfile full-path=&quot;SVG/Sandman.opf&quot;</span><br><span class="line">            media-type=&quot;application/oebps-package+xml&quot; /&gt;</span><br><span class="line">        &lt;rootfile full-path=&quot;XHTML/Sandman.opf&quot;</span><br><span class="line">            media-type=&quot;application/oebps-package+xml&quot; /&gt;</span><br><span class="line">    &lt;/rootfiles&gt;</span><br><span class="line">&lt;/container&gt;</span><br></pre></td></tr></table></figure><blockquote><p>将包含 SVG 和 XHTML 的《The Sandman》合并到同一个容器中</p></blockquote><h3 id="Encryption-–-META-INF-encryption-xml">Encryption – META-INF/encryption.xml</h3><h3 id="Manifest-–-META-INF-manifest-xml">Manifest – META-INF/manifest.xml</h3><h3 id="Metadata-–-META-INF-metadata-xml">Metadata – META-INF/metadata.xml</h3><h3 id="Rights-Management-–-META-INF-rights-xml">Rights Management – META-INF/rights.xml</h3><h3 id="Digital-Signatures-–-META-INF-signatures-xml">Digital Signatures – META-INF/signatures.xml</h3><h2 id="OEBPS">OEBPS</h2><p>在 OEBPS 中包含两个必要文件：<code>*.opf</code> 和 <code>*.ncx</code>。</p><h3 id="OPF">OPF</h3><p>OPF 包含四个子元素：metadata, manifest, spine, guide.</p><h4 id="Metadata">Metadata</h4><p>EPUB 的元数据，比如 title, language, identifier, cover, etc. title 和 identifier 是必要的。</p><p>identifier 由数字图书的创建者定义，必须唯一。对于图书出版商来说，<br>这个字段一般包括 ISBN 或者 Library of Congress 编号；也可以使用 URL 或者随机生成的唯一用户 ID。</p><p>示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&lt;metadata xmlns:opf=&quot;http://www.idpf.org/2007/opf&quot; xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot;&gt;</span><br><span class="line">  &lt;dc:identifier id=&quot;BookId&quot; opf:scheme=&quot;UUID&quot;&gt;urn:uuid:53a846f0-302c-4f90-9798-bb219328309f&lt;/dc:identifier&gt;</span><br><span class="line">  &lt;dc:title&gt;三毛全集1.撒哈拉的故事&lt;/dc:title&gt;</span><br><span class="line">  &lt;dc:creator opf:role=&quot;aut&quot;&gt;三毛&lt;/dc:creator&gt;</span><br><span class="line">  &lt;dc:language&gt;zh&lt;/dc:language&gt;</span><br><span class="line">  &lt;dc:date opf:event=&quot;modification&quot;&gt;2016-08-18&lt;/dc:date&gt;</span><br><span class="line">  &lt;dc:publisher&gt;哈尔滨出版社&lt;/dc:publisher&gt;</span><br><span class="line">  &lt;dc:date opf:event=&quot;publication&quot;&gt;2003-07-01&lt;/dc:date&gt;</span><br><span class="line">  &lt;dc:identifier opf:scheme=&quot;ISBN&quot;&gt;7-80639-879-1/I·235&lt;/dc:identifier&gt;</span><br><span class="line">  &lt;meta content=&quot;0.9.6&quot; name=&quot;Sigil version&quot; /&gt;</span><br><span class="line">  &lt;meta name=&quot;cover&quot; content=&quot;book01_01.jpg&quot; /&gt;</span><br><span class="line">&lt;/metadata&gt;</span><br></pre></td></tr></table></figure><h4 id="Manifest">Manifest</h4><p>列出了 package 中所包含的所有文件（包括 xhtml, css, png, ncx, etc）。</p><p>示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">&lt;manifest&gt;</span><br><span class="line">  &lt;item id=&quot;ncx&quot; href=&quot;toc.ncx&quot; media-type=&quot;application/x-dtbncx+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;style.css&quot; href=&quot;Styles/style.css&quot; media-type=&quot;text/css&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_front_cover.xhtml&quot; href=&quot;Text/book01_front_cover.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_back_cover.xhtml&quot; href=&quot;Text/book01_back_cover.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_index.xhtml&quot; href=&quot;Text/book01_index.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0001.xhtml&quot; href=&quot;Text/Section0001.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0002.xhtml&quot; href=&quot;Text/Section0002.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0003.xhtml&quot; href=&quot;Text/Section0003.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0004.xhtml&quot; href=&quot;Text/Section0004.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0005.xhtml&quot; href=&quot;Text/Section0005.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0006.xhtml&quot; href=&quot;Text/Section0006.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0007.xhtml&quot; href=&quot;Text/Section0007.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0008.xhtml&quot; href=&quot;Text/Section0008.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0009.xhtml&quot; href=&quot;Text/Section0009.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0010.xhtml&quot; href=&quot;Text/Section0010.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0011.xhtml&quot; href=&quot;Text/Section0011.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0012.xhtml&quot; href=&quot;Text/Section0012.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0013.xhtml&quot; href=&quot;Text/Section0013.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0014.xhtml&quot; href=&quot;Text/Section0014.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;Section0015.xhtml&quot; href=&quot;Text/Section0015.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;by.xhtml&quot; href=&quot;Text/by.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_01.jpg&quot; href=&quot;Images/book01_01.jpg&quot; media-type=&quot;image/jpeg&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_02.jpg&quot; href=&quot;Images/book01_02.jpg&quot; media-type=&quot;image/jpeg&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_03.jpg&quot; href=&quot;Images/book01_03.jpg&quot; media-type=&quot;image/jpeg&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_04.jpg&quot; href=&quot;Images/book01_04.jpg&quot; media-type=&quot;image/jpeg&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_05.jpg&quot; href=&quot;Images/book01_05.jpg&quot; media-type=&quot;image/jpeg&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_06.jpg&quot; href=&quot;Images/book01_06.jpg&quot; media-type=&quot;image/jpeg&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_illustration01.xhtml&quot; href=&quot;Text/book01_illustration01.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_illustration02.xhtml&quot; href=&quot;Text/book01_illustration02.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_illustration04.xhtml&quot; href=&quot;Text/book01_illustration04.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_illustration03.xhtml&quot; href=&quot;Text/book01_illustration03.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_info.xhtml&quot; href=&quot;Text/book01_info.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;book01_info.jpg&quot; href=&quot;Images/book01_info.jpg&quot; media-type=&quot;image/jpeg&quot;/&gt;</span><br><span class="line">  &lt;item id=&quot;cover.xhtml&quot; href=&quot;Text/cover.xhtml&quot; media-type=&quot;application/xhtml+xml&quot;/&gt;</span><br><span class="line">&lt;/manifest&gt;</span><br></pre></td></tr></table></figure><h4 id="Spine">Spine</h4><p>所有 xhtml 文档的阅读顺序，可以将 OPF spine 理解为是书中“页面”的顺序，<br>解析的时候按照文档顺序从上到下依次读取 spine。</p><p>在 spine 中的每个 itemref 元素都需要有一个 idref 属性，这个属性和 manifest 中的某个 ID 匹配。</p><p>spine 中的 linear 属性表明该项是作为线性阅读顺序中的一项，还是和先后次序无关。</p><p>示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">&lt;spine toc=&quot;ncx&quot;&gt;</span><br><span class="line">  &lt;itemref idref=&quot;cover.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_front_cover.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_back_cover.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_info.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_illustration01.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_illustration02.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_illustration03.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_illustration04.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;book01_index.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0001.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0002.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0003.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0004.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0005.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0006.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0007.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0008.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0009.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0010.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0011.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0012.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0013.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0014.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;Section0015.xhtml&quot;/&gt;</span><br><span class="line">  &lt;itemref idref=&quot;by.xhtml&quot;/&gt;</span><br><span class="line">&lt;/spine&gt;</span><br></pre></td></tr></table></figure><h4 id="Guide">Guide</h4><p>出版物基本结构特征的一组参考，例如目录、前言、参考书目等。</p><h3 id="NCX-file">NCX file</h3><p>NCX file 是 Navigation Control file for XML 的缩写，通常命名为 toc.ncx。<br>该文件由 EPUB 文件的分层目录组成。</p><p>NCX 的 <head> 标记中包含四个 meta 元素：</p><ul><li>uid：数字图书的惟一 ID。该元素应该和 OPF 文件中的 dc:identifier 对应。</li><li>depth：反映目录表中层次的深度。</li><li>totalPageCount 和 maxPageNumber：仅用于纸质图书，保留 0 即可。</li></ul><p><code>docTitle/text</code> 的内容是图书的标题，和 OPF 中的 dc:title 匹配。</p><p><code>navMap</code> 定义了图书的目录。<code>navMap</code> 包含一个或多个 <code>navPoint</code> 元素，每个 <code>navPoint</code><br>都要包含下列元素：</p><ul><li><code>playOrder</code>: 说明文档的阅读顺序。和 OPF spine 中 <code>itemref</code> 元素的顺序相同。</li><li><code>navLabel/text</code>: 该章节的标题。通常是本章的标题或者数字。</li><li><code>content</code>: 其中的 <code>src</code> 属性指向包含这些内容的物理资源。为 OPF manifest 中声明的文件。</li><li>可以拥有多个 <code>navPoint</code> 元素。</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;</span><br><span class="line">&lt;!DOCTYPE ncx PUBLIC &quot;-//NISO//DTD ncx 2005-1//EN&quot;</span><br><span class="line"> &quot;http://www.daisy.org/z3986/2005/ncx-2005-1.dtd&quot;&gt;&lt;ncx version=&quot;2005-1&quot; xmlns=&quot;http://www.daisy.org/z3986/2005/ncx/&quot;&gt;</span><br><span class="line">  &lt;head&gt;</span><br><span class="line">    &lt;meta content=&quot;urn:uuid:53a846f0-302c-4f90-9798-bb219328309f&quot; name=&quot;dtb:uid&quot;/&gt;</span><br><span class="line">    &lt;meta content=&quot;2&quot; name=&quot;dtb:depth&quot;/&gt;</span><br><span class="line">    &lt;meta content=&quot;0&quot; name=&quot;dtb:totalPageCount&quot;/&gt;</span><br><span class="line">    &lt;meta content=&quot;0&quot; name=&quot;dtb:maxPageNumber&quot;/&gt;</span><br><span class="line">  &lt;/head&gt;</span><br><span class="line">  &lt;docTitle&gt;</span><br><span class="line">    &lt;text&gt;三毛全集&lt;/text&gt;</span><br><span class="line">  &lt;/docTitle&gt;</span><br><span class="line">  &lt;navMap&gt;</span><br><span class="line">    &lt;navPoint id=&quot;navPoint-1&quot; playOrder=&quot;1&quot;&gt;</span><br><span class="line">      &lt;navLabel&gt;</span><br><span class="line">        &lt;text&gt;撒哈拉的故事&lt;/text&gt;</span><br><span class="line">      &lt;/navLabel&gt;</span><br><span class="line">      &lt;content src=&quot;Text/book01_front_cover.xhtml&quot;/&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-2&quot; playOrder=&quot;2&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;目录&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/book01_index.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-3&quot; playOrder=&quot;3&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;妈妈的一封信（代序）&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0001.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-4&quot; playOrder=&quot;4&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;回乡小笺（四版代序）&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0002.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-5&quot; playOrder=&quot;5&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;沙漠中的饭店&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0003.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-6&quot; playOrder=&quot;6&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;结婚记&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0004.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-7&quot; playOrder=&quot;7&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;悬壶济世&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0005.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-8&quot; playOrder=&quot;8&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;娃娃新娘&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0006.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-9&quot; playOrder=&quot;9&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;荒山之夜&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0007.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-10&quot; playOrder=&quot;10&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;沙漠观浴记&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0008.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-11&quot; playOrder=&quot;11&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;爱的寻求&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0009.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-12&quot; playOrder=&quot;12&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;芳邻&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0010.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-13&quot; playOrder=&quot;13&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;素人渔夫&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0011.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-14&quot; playOrder=&quot;14&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;死果&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0012.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-15&quot; playOrder=&quot;15&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;天梯&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0013.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-16&quot; playOrder=&quot;16&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;白手成家&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0014.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">      &lt;navPoint id=&quot;navPoint-17&quot; playOrder=&quot;17&quot;&gt;</span><br><span class="line">        &lt;navLabel&gt;</span><br><span class="line">          &lt;text&gt;三毛一生大事记&lt;/text&gt;</span><br><span class="line">        &lt;/navLabel&gt;</span><br><span class="line">        &lt;content src=&quot;Text/Section0015.xhtml&quot;/&gt;</span><br><span class="line">      &lt;/navPoint&gt;</span><br><span class="line">    &lt;/navPoint&gt;</span><br><span class="line">    &lt;navPoint id=&quot;navPoint-18&quot; playOrder=&quot;18&quot;&gt;</span><br><span class="line">      &lt;navLabel&gt;</span><br><span class="line">        &lt;text&gt;制作说明&lt;/text&gt;</span><br><span class="line">      &lt;/navLabel&gt;</span><br><span class="line">      &lt;content src=&quot;Text/by.xhtml&quot;/&gt;</span><br><span class="line">    &lt;/navPoint&gt;</span><br><span class="line">  &lt;/navMap&gt;</span><br><span class="line">&lt;/ncx&gt;</span><br></pre></td></tr></table></figure><h3 id="NCX-vs-OPF-spine">NCX vs OPF spine</h3><p>NCX 中有章节内容，OPF spine 主要描述章节顺序。NCX 更详细。</p><p>参考链接：</p><p><a href="https://www.w3.org/AudioVideo/ebook/">epub 文件结构</a></p><p><a href="https://idpf.org/epub/30/spec/epub30-ocf.html#sec-container-metainf">OCF 文件结构</a></p><p><a href="https://idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.0">OPF 文件结构</a></p><p><a href="https://www.cnblogs.com/Alex80/p/5127104.html">Epub 基础知识介绍</a></p>]]></content>
    
    
    <summary type="html">EPUB 格式是一种 zip 格式，大概由这些内容组成：</summary>
    
    
    
    <category term="interesting-things" scheme="http://blog.fooo.in/categories/interesting-things/"/>
    
    
  </entry>
  
  <entry>
    <title>TCP 握手与挥手</title>
    <link href="http://blog.fooo.in/2024/08/14/network/tcp_handshaking/"/>
    <id>http://blog.fooo.in/2024/08/14/network/tcp_handshaking/</id>
    <published>2024-08-14T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>最近在看某个项目的一个功能：<a href="https://www.criu.org/TCP_connection#Checkpoint_and_restore_TCP_connection">network-lock</a>, 感觉还挺有意思的，于是写了一个 TCP Demo 来测试这个功能，但是在我手动将 server 关闭的时候发现不能马上使用上次 server 监听的端口，而是跟我说端口已被占用，有些好奇，于是就有了以下的一些折腾。顺便在这里记录一下关于 TCP 三次握手与挥手的笔记。顺带 <code>tcpdump</code> 分析。</p><h2 id="介绍">介绍</h2><p>在网络通信中，TCP（Transmission Control Protocol）是一种可靠的传输协议，而 TCP 三次握手是建立 TCP 连接时的重要过程之一。</p><h2 id="概述">概述</h2><p>TCP 三次握手，包括三个步骤：</p><ol><li><p>客户端发送 SYN 请求：客户端向服务器发送一个 SYN（同步）标志的数据包，表明它想要建立连接。</p></li><li><p>服务器响应 ACK 和 SYN：服务器接收到客户端的 SYN 请求后，向客户端发送一个 ACK（确认）数据包，表示已收到客户端的请求，并发送自己的 SYN 标志，以示同意建立连接。</p></li><li><p>客户端发送 ACK：客户端接收到服务器的 ACK 和 SYN 后，向服务器发送一个 ACK 确认数据包，表示已收到服务器的响应，连接建立完成。</p></li></ol><h2 id="代码示例与-tcpdump-分析">代码示例与 tcpdump 分析</h2><p>下面是一个 client 和 server 建立 TCP 连接的代码。我最开始是用 C 写的，但是后面发现 Python 写起来舒服多了。</p><p>这个 Server 会监听 localhost 的 8880 端口。<br><code>Server.py</code>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_address = (<span class="string">&#x27;localhost&#x27;</span>, <span class="number">8880</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;Starting up on &#123;&#125; port &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*server_address))</span><br><span class="line">server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_FASTOPEN, <span class="number">1</span>)</span><br><span class="line">server_socket.bind(server_address)</span><br><span class="line"></span><br><span class="line">server_socket.listen(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Waiting for a connection...&#x27;</span>)</span><br><span class="line">    connection, client_address = server_socket.accept()</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Connection from&#x27;</span>, client_address)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                data = connection.recv(<span class="number">1024</span>)</span><br><span class="line">                <span class="comment"># TCP_QUICKACK</span></span><br><span class="line">                <span class="comment"># connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1)</span></span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;Received:&#x27;</span>, data.decode())</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> <span class="keyword">not</span> data:</span><br><span class="line">                    <span class="built_in">print</span>(<span class="string">&#x27;No data received from&#x27;</span>, client_address)</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">except</span> ConnectionResetError:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;Connection is reset by the client&#x27;</span>)</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">except</span> KeyboardInterrupt:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;KeyboardInterrupt&#x27;</span>)</span><br><span class="line">                connection.close()</span><br><span class="line">                exit(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        connection.close()</span><br></pre></td></tr></table></figure><p>这个 Client 会与 Server 建立连接并每隔 2s 发送当前时间给 Server。<br><code>Client.py</code>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"></span><br><span class="line">server_address = (<span class="string">&#x27;localhost&#x27;</span>, <span class="number">8880</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;Connecting to &#123;&#125; port &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*server_address))</span><br><span class="line">client_socket.connect(server_address)</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        message = <span class="string">&#x27;Hello, server! Time is &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(time.ctime())</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Sending:&#x27;</span>, message)</span><br><span class="line">        <span class="comment"># client_socket.sendall(message.encode())</span></span><br><span class="line">        client_socket.sendto(message.encode(), server_address)</span><br><span class="line">        time.sleep(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">finally</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Closing the connection&#x27;</span>)</span><br><span class="line">    client_socket.close()</span><br></pre></td></tr></table></figure><p>运行这两个代码，使用 <code>tcpdump</code> 来监听 8880 端口的 tcp 数据包：</p><h3 id="三次握手：">三次握手：</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ tcpdump -i lo -n port 8880  --absolute-tcp-sequence-numbers</span><br><span class="line"></span><br><span class="line">tcpdump: verbose output suppressed, use -v[v]... for full protocol decode</span><br><span class="line">listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes</span><br><span class="line">16:18:18.333865 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [S], seq 1857514943, win 33280, options [mss 65495,sackOK,TS val 3851230309 ecr 0,nop,wscale 7], length 0</span><br><span class="line">16:18:18.333874 IP 127.0.0.1.8880 &gt; 127.0.0.1.57830: Flags [S.], seq 3256784619, ack 1857514944, win 33280, options [mss 65495,sackOK,TS val 3851230309 ecr 3851230309,nop,wscale 7], length 0</span><br><span class="line">16:18:18.333880 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [.], ack 3256784620, win 260, options [nop,nop,TS val 3851230309 ecr 3851230309], length 0</span><br><span class="line"></span><br><span class="line">16:18:18.333910 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [P.], seq 1857514944:1857514991, ack 3256784620, win 260, options [nop,nop,TS val 3851230309 ecr 3851230309], length 47</span><br><span class="line">16:18:18.333912 IP 127.0.0.1.8880 &gt; 127.0.0.1.57830: Flags [.], ack 1857514991, win 260, options [nop,nop,TS val 3851230309 ecr 3851230309], length 0</span><br></pre></td></tr></table></figure><blockquote><p>PS: tcpdump 的参数说明：</p><p>i lo: 监听 lo 网卡<br>n: 不解析 IP 地址<br>port 8880: 只监听 8880 端口的数据包<br>absolute-tcp-sequence-numbers: 显示绝对的 TCP 序列号，如果不加这个参数，tcpdump 会显示相对的序列号。</p></blockquote><p><code>tcpdump</code> 输出的 Flags 字段解释：</p><p>[.] - ACK (Acknowledgment)<br>[S] - SYN (Start Connection)<br>[P] - PSH (Push Data)<br>[F] - FIN (Finish Connection)<br>[R] - RST (Reset Connection)<br>[S.] - SYN-ACK (SynAcK Packet)</p><p>可以看到，Client 发送了一个 SYN 数据包，Server 回复了一个 SYN+ACK 数据包，Client 再回复一个 ACK 数据包，三次握手完成。</p><p>seq 和 ack 是 TCP 数据包中的两个重要字段，seq 表示数据包的序列号，ack 表示确认号。SYN 数据包的 seq 是随机的，SYN+ACK 数据包的 seq 是随机的，ack 是 SYN 数据包的 seq+1，ACK 数据包的 seq 是 SYN+ACK 数据包的 ack，ack 是 SYN+ACK 数据包的 seq+1。</p><p>seq 和 ack 的作用是用来保证数据包的可靠传输。TCP 通过 seq 和 ack 来保证数据包的有序传输和可靠接收。</p><h3 id="三次挥手与四次挥手：">三次挥手与四次挥手：</h3><h4 id="三次挥手">三次挥手</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">16:21:10.284200 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [F.], seq 1857515790, ack 3256784620, win 260, options [nop,nop,TS val 3851402259 ecr 3851400314], length 0</span><br><span class="line">16:21:10.284297 IP 127.0.0.1.8880 &gt; 127.0.0.1.57830: Flags [F.], seq 3256784620, ack 1857515791, win 260, options [nop,nop,TS val 3851402259 ecr 3851402259], length 0</span><br><span class="line">16:21:10.284308 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [.], ack 3256784621, win 260, options [nop,nop,TS val 3851402259 ecr 3851402259], length 0</span><br></pre></td></tr></table></figure><p>三次挥手的过程只是将 FIN 与 ACK 数据包一同发送。</p><p>三次挥手是 Linux 默认开启 <a href="https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment">TCP 延迟确认机制</a>，而 RFC795 中是四次挥手：<a href="https://www.rfc-editor.org/rfc/rfc793#section-3.5">https://www.rfc-editor.org/rfc/rfc793#section-3.5</a></p><p>关于 TCP 延迟确认机制，可以使用 <code>TCP_QUICKACK</code> 关闭：<a href="https://www.man7.org/linux/man-pages/man7/tcp.7.html">https://www.man7.org/linux/man-pages/man7/tcp.7.html</a>, 关于这个参数，这个参数并不是 <strong>永久性的</strong>，设置或清除这个标志只会暂时地启用或禁用快速确认模式。随后的 TCP 协议操作会根据内部协议处理和延迟确认超时等因素再次进入或退出快速确认模式。因此，这个标志的状态在操作过程中可能会动态变化。可以看上面的 <code>server.py</code> 中的使用方法</p><p><a href="https://datatracker.ietf.org/doc/html/rfc1122#page-96">RFC1122</a> 中对 Delay ACK 的说明：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Delayed ACK&#x27;s</span><br><span class="line">Delay &lt; 0.5 seconds</span><br><span class="line">2nd full-sized segment ACK&#x27;d</span><br></pre></td></tr></table></figure><p>如 RFC1122 所说，一个主机可以延迟发送 ACK 报文高达 500 毫秒。此外，以每完整的数据包为一段，ACK 报文必须每两段发送一次（所以上面的三次挥手过程最后会发送一个 ACK 报文）。</p><p>延迟的 ACK 可以使应用程序有机会更新 TCP 接收窗口，也可以立即发送 ACK 报文。对于某些协议（如 Telnet），通过将 ACK、窗口更新和响应数据组合到一个段中，可以将服务器发送的响应数量减少 3 倍。</p><h4 id="四次挥手">四次挥手</h4><ol><li>Client 发送一个 FIN 数据包，表示不再发送数据了。进入 <code>FIN_WAIT_1</code> 状态。</li><li>Server 收到 FIN 数据包后，回复一个 ACK 数据包，表示收到了 Client 的 FIN 数据包。进入 <code>CLOSED_WAIT</code> 状态。</li><li>Client 收到 ACK 数据包，进入 <code>FIN_WAIT_2</code> 状态。</li><li>Server 发送一个 FIN 数据包，表示不再发送数据了。进入 <code>LAST_ACK</code> 状态。</li><li>Client 收到 FIN 数据包后，回复一个 ACK 数据包，表示收到了 Server 的 FIN 数据包。进入 <code>TIME_WAIT</code> 状态。</li><li>服务端收到了 ACK 应答报文后，就进入了 CLOSE 状态，至此服务端已经完成连接的关闭。</li></ol><p><strong>首先</strong>发出 FIN 的一侧，如果在给“对侧”的 FIN 响应了 ACK（发送了最后一个 ACK 数据包），那么就会超时等待 2 * MSL 时间（处于 TIME_WAIT 状态），然后关闭连接。在这段超时等待时间内，本地的端口不能被新连接使用；</p><p>RFC793 定义了 MSL 为 2 分钟，Linux 设置成了 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/ipv4/tcp.c?h=linux-6.8.y#n2863">30s</a> 。参数 tcp_max_tw_buckets 控制并发的 TIME_WAIT 的数量，默认值是 180000，如果超限，那么，系统会把多的 TIME_WAIT 状态的连接给 destory 掉，然后在日志里打一个警告（如：time wait bucket table overflow）</p><p>这也就是手动将 server 进程杀掉之后，端口仍被占用的原因。TCP 连接关闭之后，不会马上释放这个 socket，之后任何想将新 socket 绑定到相同的地址和端口的操作都会失败，直到旧的 socket 关闭为止。</p><blockquote><p>可以使用 <code>SO_REUSEADDR</code> 解决这个问题。<a href="https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ">https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ</a><br>还有一种方式是设置 <code>SO_LINGER</code> 选项的超时时长参数为 0。这是不推荐使用的方法：<a href="https://stackoverflow.com/questions/3757289/when-is-tcp-option-so-linger-0-required">https://stackoverflow.com/questions/3757289/when-is-tcp-option-so-linger-0-required</a></p></blockquote><h4 id="TIEM-WAIT-的意义">TIEM_WAIT 的意义</h4><p>这个状态是<strong>避免延时的包的到达与随后的新连接相混淆</strong>和<strong>保证「被动关闭连接」的一方，能被正确的关闭</strong>。</p><p>保证「被动关闭连接」的一方可以被正确关闭：</p><p>如果客户端（主动关闭方）最后一次 ACK 报文（第四次挥手）在网络中丢失了，那么按照 TCP 可靠性原则，服务端（被动关闭方）会重发 FIN 报文。</p><p>假设客户端没有 TIME_WAIT 状态，而是在发完最后一次回 ACK 报文就直接进入 CLOSE 状态，如果该 ACK 报文丢失了，服务端则重传的 FIN 报文，而这时客户端已经进入到关闭状态了，在收到服务端重传的 FIN 报文后，就会回 RST 报文。</p>]]></content>
    
    
    <summary type="html">在网络通信中，TCP（Transmission Control Protocol）是一种可靠的传输协议，而TCP三次握手是建立TCP连接时的重要过程之一。TCP 三次握手，包括三个步骤：</summary>
    
    
    
    <category term="network" scheme="http://blog.fooo.in/categories/network/"/>
    
    
  </entry>
  
  <entry>
    <title>TCP Fast Open</title>
    <link href="http://blog.fooo.in/2024/08/14/network/tcp-fast-open/"/>
    <id>http://blog.fooo.in/2024/08/14/network/tcp-fast-open/</id>
    <published>2024-08-14T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>折腾 TCP 的同时看到了 TCP Fast Open 这个有趣的参数，它是对 TCP 的优化，无需等待 3 次握手，应用程序就可以通过 TCP 发送数据。</p><p>正常 TCP 建立连接过程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">    Client                                               Server</span><br><span class="line"></span><br><span class="line">1.  CLOSED                                               LISTEN</span><br><span class="line"></span><br><span class="line">2.  SYN-SENT    --&gt;           SYN M                  --&gt; SYN-RECEIVED</span><br><span class="line"></span><br><span class="line">3.  ESTABLISHED &lt;--        SYN N,ACK M+1             &lt;-- SYN-RECEIVED</span><br><span class="line"></span><br><span class="line">4.  ESTABLISHED --&gt;          ACK N+1                 --&gt; ESTABLISHED</span><br></pre></td></tr></table></figure><p>当前 TCP 实现的问题是，只有在连接发起方收到来自对等 TCP 的 ACK（确认）段后，才能在连接上交换数据。也就是说，只有在三次握手的第三步（发起方发送的 ACK 报文段）中，数据才能从客户端发送到服务器。因此，在对等点之间交换数据之前，就会损失一个完整的往返时间 (round trip time) 。这种丢失的 RTT 是短网络对话延迟的重要组成部分。TCP Fast Open 就是为了解决这个问题。</p><h2 id="消除-RTT">消除 RTT</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">    Client                                               Server</span><br><span class="line"></span><br><span class="line">1.  CLOSED                                               LISTEN</span><br><span class="line"></span><br><span class="line">2.  SYN-SENT    --&gt; SYN, with cookie + data          --&gt; SYN-RECEIVED---</span><br><span class="line">                                                                       | Server TCP validates cookie, passes data to application</span><br><span class="line">3.  Client      &lt;--        SYN-ACK                   &lt;-- SYN-RECEIVED---</span><br><span class="line"></span><br><span class="line">4.  Client      &lt;--        responses                 &lt;-- Server          Server can send responses before receiving client ACK</span><br><span class="line"></span><br><span class="line">4.  ESTABLISHED --&gt;          ACK                     --&gt; ESTABLISHED</span><br></pre></td></tr></table></figure><p>上图所示步骤如下：</p><ol><li><p>客户端 TCP 发送 SYN，其中包含 TFO cookie（指定为 TCP 选项）和来自客户端应用程序的数据。</p></li><li><p>服务器 TCP 通过基于新 SYN 的源 IP 地址重复加密过程来验证 TFO cookie。如果 cookie 被证明是有效的，那么服务器 TCP 就可以确信这个 SYN 来自它声称来自的地址。这意味着服务器 TCP 可以立即将应用程序数据传递给服务器应用程序。</p></li><li><p>从这里开始，TCP 会话正常进行：服务器 TCP 向客户端发送 SYN-ACK 段，然后客户端 TCP 进行确认，从而完成三向握手。服务器 TCP 还可以在收到客户端的 ACK <strong>之前</strong> 向客户端 TCP 发送响应数据段。</p></li></ol><p>这是一个使用 <code>tcpdump</code> 查看使用了 <code>TCP_FAST_OPEN</code> 选项的抓包记录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">1. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [S], seq 3550480872:3550480878, win 33280, options [mss 65495,sackOK,TS val 1437621030 ecr 0,nop,wscale 7,tfo  cookie ce80700cf8e6113c,nop,nop], length 6</span><br><span class="line">2. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [S.], seq 2245778431, ack 3550480873, win 33280, options [mss 65495,sackOK,TS val 1437621030 ecr 1437621030,nop,wscale 7], length 0</span><br><span class="line">3. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [P.], seq 3550480873:3550480879, ack 2245778432, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 6</span><br><span class="line">4. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [.], ack 3550480879, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">5. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [P.], seq 2245778432:2245778438, ack 3550480879, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 6</span><br><span class="line">6. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [.], ack 2245778438, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">7. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [F.], seq 2245778438, ack 3550480879, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">8. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [F.], seq 3550480879, ack 2245778439, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">9. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [.], ack 3550480880, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br></pre></td></tr></table></figure><p>可以看到 <code>3</code> 这个数据段在 <code>4</code> 这个 ACK 数据包之前。并且 options 中有一个 <code>cookie</code> 字段。</p><p><a href="http://Server.py">Server.py</a>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_address = (<span class="string">&#x27;localhost&#x27;</span>, <span class="number">8880</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;Starting up on &#123;&#125; port &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*server_address))</span><br><span class="line">server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line">server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_FASTOPEN, <span class="number">1</span>)</span><br><span class="line">server_socket.bind(server_address)</span><br><span class="line"></span><br><span class="line">server_socket.listen(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Waiting for a connection...&#x27;</span>)</span><br><span class="line">    connection, client_address = server_socket.accept()</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Connection from&#x27;</span>, client_address)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            data = connection.recv(<span class="number">1024</span>)</span><br><span class="line">            <span class="comment"># TCP_QUICKACK</span></span><br><span class="line">            connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, <span class="number">1</span>)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;Received:&#x27;</span>, data.decode())</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> data:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;No data received from&#x27;</span>, client_address)</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        connection.close()</span><br></pre></td></tr></table></figure><p><a href="http://Client.py">Client.py</a>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line">addr = (<span class="string">&quot;localhost&quot;</span>, <span class="number">8880</span>)</span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">s.sendto(<span class="string">&quot;hello!&quot;</span>.encode(), <span class="number">536870912</span>, addr)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(s.recv(<span class="number">1000</span>))</span><br></pre></td></tr></table></figure><blockquote><p><code>sendto</code> 需要提供 ip, 因为是 connectionless.</p></blockquote><p>具体参考：</p><p>[1] <a href="https://lwn.net/Articles/508865/">https://lwn.net/Articles/508865/</a></p>]]></content>
    
    
    <summary type="html">折腾 TCP 的同时看到了 TCP Fast Open 这个有趣的参数，它是对 TCP 的优化，无需等待 3 次握手，应用程序就可以通过 TCP 发送数据。正常 TCP 建立连接过程：当前 TCP 实现的问题是，只有在连接发起方收到来自对等 TCP 的 ACK（确认）段后，才能在连接上交换数据。也就是说，只有在三次握手的第三步（发起方发送的ACK报文段）中，数据才能从客户端发送到服务器。因此，在对等点之间交换数据之前，就会损失一个完整的往返时间 (round trip time) 。这种丢失的 RTT 是短网络对话延迟的重要组成部分。TCP Fast Open 就是为了解决这个问题。</summary>
    
    
    
    <category term="network" scheme="http://blog.fooo.in/categories/network/"/>
    
    
    <category term="tcp" scheme="http://blog.fooo.in/tags/tcp/"/>
    
    <category term="network" scheme="http://blog.fooo.in/tags/network/"/>
    
    <category term="networktcp" scheme="http://blog.fooo.in/tags/networktcp/"/>
    
  </entry>
  
  <entry>
    <title>Ansible</title>
    <link href="http://blog.fooo.in/2024/08/12/SRE/ansible/"/>
    <id>http://blog.fooo.in/2024/08/12/SRE/ansible/</id>
    <published>2024-08-12T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>一些关于 Ansible 的笔记。</p><h2 id="Playbook">Playbook</h2><p>Playbooks 是自动化蓝图，它们采用 yml 格式，Ansible 使用它们来部署和配置被管理的节点。</p><h3 id="Playbook-2">Playbook</h3><p>一个 playbook 是一个 plays 的列表，它们定义了 Ansible 执行操作的顺序（从上到下），以实现一个整体目标。</p><h3 id="Play">Play</h3><p>一个 play 是一个任务（tasks）的有序列表，这些任务映射到 inventory 中的被管理节点。</p><h3 id="Task">Task</h3><p>一个 task 是对单个模块的引用，它定义了 Ansible 将要执行的操作。</p><blockquote><p>可以理解为脚本</p></blockquote><h3 id="Module">Module</h3><p>模块是 Ansible 在被管理节点上运行的代码或二进制文件的单元。Ansible 模块被分组在 collections 中，每个模块都有一个完全限定的集合名称（FQCN）。</p><blockquote><p>Ansible 自带的 Modules: <a href="https://docs.ansible.com/ansible/latest/collections/index.html">https://docs.ansible.com/ansible/latest/collections/index.html</a>. 也可以自己实现。</p></blockquote><p>可以使用 <code>ansible-doc</code> 查看某个模块。</p><h4 id="自定义-Module">自定义 Module</h4><p><a href="https://docs.ansible.com/ansible/latest/reference_appendices/module_utils.html">https://docs.ansible.com/ansible/latest/reference_appendices/module_utils.html</a></p><p><strong>示例</strong>：</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">from</span> ansible.module_utils.basic <span class="keyword">import</span> AnsibleModule</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">run_module</span>():</span><br><span class="line">    module_args = <span class="built_in">dict</span>(</span><br><span class="line">        path=<span class="built_in">dict</span>(<span class="built_in">type</span>=<span class="string">&#x27;str&#x27;</span>, required=<span class="literal">True</span>)</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    result = <span class="built_in">dict</span>(</span><br><span class="line">        changed=<span class="literal">False</span>,</span><br><span class="line">        original_message=<span class="string">&#x27;&#x27;</span>,</span><br><span class="line">        message=<span class="string">&#x27;&#x27;</span></span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    module = AnsibleModule(</span><br><span class="line">        argument_spec=module_args,</span><br><span class="line">        supports_check_mode=<span class="literal">True</span></span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    path = module.params[<span class="string">&#x27;path&#x27;</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(path, <span class="string">&#x27;r&#x27;</span>) <span class="keyword">as</span> file:</span><br><span class="line">            content = file.read()</span><br><span class="line">        result[<span class="string">&#x27;message&#x27;</span>] = content</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        module.fail_json(msg=<span class="built_in">str</span>(e), **result)</span><br><span class="line"></span><br><span class="line">    module.exit_json(**result)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    run_module()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><p>使用模块：</p><p>在你的 playbook 中引用自定义模块：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Test</span> <span class="string">custom</span> <span class="string">module</span></span><br><span class="line">  <span class="attr">hosts:</span> <span class="string">localhost</span></span><br><span class="line">  <span class="attr">tasks:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Read</span> <span class="string">content</span> <span class="string">from</span> <span class="string">file</span></span><br><span class="line">      <span class="attr">my_module:</span></span><br><span class="line">        <span class="attr">path:</span> <span class="string">/path/to/your/file.txt</span></span><br><span class="line">      <span class="attr">register:</span> <span class="string">result</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Show</span> <span class="string">result</span></span><br><span class="line">      <span class="attr">debug:</span></span><br><span class="line">        <span class="attr">var:</span> <span class="string">result.message</span></span><br></pre></td></tr></table></figure><p>运行 playbook：</p><p>确保 <code>my_module.py</code> 文件在 library 目录下，Ansible 会自动查找该目录中的模块。然后运行你的 playbook：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ansible-playbook your_playbook.yml</span><br></pre></td></tr></table></figure><h3 id="示例">示例</h3><p>为了更好地理解这些概念，以下是一个简单的 playbook 示例：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Ensure</span> <span class="string">web</span> <span class="string">server</span> <span class="string">is</span> <span class="string">installed</span> <span class="string">and</span> <span class="string">running</span></span><br><span class="line">  <span class="attr">hosts:</span> <span class="string">webservers</span></span><br><span class="line">  <span class="attr">become:</span> <span class="literal">yes</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">tasks:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Apache</span></span><br><span class="line">      <span class="attr">ansible.builtin.yum:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">httpd</span></span><br><span class="line">        <span class="attr">state:</span> <span class="string">present</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Start</span> <span class="string">Apache</span> <span class="string">service</span></span><br><span class="line">      <span class="attr">ansible.builtin.service:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">httpd</span></span><br><span class="line">        <span class="attr">state:</span> <span class="string">started</span></span><br></pre></td></tr></table></figure><p>在这个示例中：</p><ul><li><strong>Playbook</strong> 是整个 yml 文件。</li><li><strong>Play</strong> 是从 <code>- name: Ensure web server is installed and running</code> 开始的部分。</li><li><strong>Task</strong> 是 <code>Install Apache</code> 和 <code>Start Apache service</code> 这两个步骤。</li><li><strong>Module</strong> 是 <code>ansible.builtin.yum</code> 和 <code>ansible.builtin.service</code>，它们分别用于安装 Apache 和启动 Apache 服务。</li></ul><h2 id="kubespray">kubespray</h2><p>kubespray 就是使用 ansible 进行生产环境的 k8s 部署，ansible 提供一个 playbook <code>cluster.yml</code>，类似：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">hosts:</span> <span class="string">bastion:k8s_cluster:etcd_k8s</span></span><br><span class="line">  <span class="attr">gather_facts:</span> <span class="literal">True</span></span><br><span class="line">  <span class="attr">roles:</span></span><br><span class="line">    <span class="bullet">-</span> &#123; <span class="attr">role:</span> <span class="string">kubernetes/pre-pkg/releases</span> &#125;</span><br></pre></td></tr></table></figure><p><code>hosts: bastion:k8s_cluster:etcd_k8s</code>: 这部分指定了要运行 playbook 的目标主机（或主机组）。bastion, k8s_cluster, etcd_k8s 可能是定义在你的 Ansible 清单文件中的主机或主机组。这意味着该任务将会在这些主机上执行。</p><p><code>gather_facts: True</code>: 这个选项表示在执行任务之前，Ansible 会收集目标主机的事实（即主机的系统信息，如 IP 地址、操作系统类型等）。这些事实可以在后续任务中使用。</p><p><code>roles</code>: 这部分定义了要在目标主机上执行的角色列表。每个角色包含了一系列任务、变量、模板和其他文件，可以自动应用到目标主机上。</p><p><code>&#123; role: kubernetes/pre-pkg/releases &#125;</code>: 这是一种简化的方式来定义角色。在这里，kubernetes/pre-pkg/releases 是角色的名称，表示这是一个与 Kubernetes 相关的角色，位于 pre-pkg/releases 目录中。这个角色可能包含了一些预定义的任务，用于设置或管理 Kubernetes 集群的特定组件或版本。</p><p>roles 是 Ansible 用来组织和复用代码的机制，通过指定一个或多个角色，可以在多个主机上执行复杂的自动化任务。</p><p>在 role 目录下有一系列 task 文件，比如：</p><ol><li><p>defaults:</p><p>作用：存储角色的默认变量值。defaults/main.yml 文件通常包含所有角色的默认设置。这些默认值可以在 playbook 或 vars 文件中被覆盖。</p></li><li><p>files:</p><p>作用：存放角色可能需要分发到目标主机上的静态文件。Ansible 的 copy 或 template 模块可以从这个目录中复制文件到目标主机。</p></li><li><p>handlers:</p><p>作用：存储在任务中触发的处理程序（handlers）。处理程序通常在某个条件满足时被调用，例如在配置文件改变后重新启动服务。</p></li><li><p>meta:</p><p>作用：存储角色的元数据文件，通常是 main.yml 文件。元数据文件定义了角色的依赖关系、支持的 Ansible 版本、以及其他角色的依赖等信息。</p></li><li><p>tasks:</p><p>作用：存放角色的主要任务文件。tasks/main.yml 是角色执行的主要任务入口文件，可能导入其他任务文件。</p></li><li><p>templates:</p><p>作用：存放 Jinja2 模板文件。模板文件通常用于动态生成配置文件，并通过 template 模块分发到目标主机。</p></li><li><p>vars:</p><p>作用：存储角色的变量文件。vars/main.yml 文件通常包含在角色中定义的变量，这些变量的优先级比 defaults 中定义的更高，但比 playbook 中定义的低。</p></li></ol>]]></content>
    
    
    <summary type="html">一些关于 Ansible 的笔记。</summary>
    
    
    
    <category term="SRE" scheme="http://blog.fooo.in/categories/SRE/"/>
    
    
    <category term="k8s" scheme="http://blog.fooo.in/tags/k8s/"/>
    
    <category term="sre" scheme="http://blog.fooo.in/tags/sre/"/>
    
    <category term="srek8sk8s" scheme="http://blog.fooo.in/tags/srek8sk8s/"/>
    
  </entry>
  
  <entry>
    <title>minikube 设置代理</title>
    <link href="http://blog.fooo.in/2024/08/10/k8s/set-proxy-for-minikube/"/>
    <id>http://blog.fooo.in/2024/08/10/k8s/set-proxy-for-minikube/</id>
    <published>2024-08-10T00:00:00.000Z</published>
    <updated>2024-08-10T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>在好久以前就折腾过 minikube 的代理配置，但是当时懒得写下来（其实也没啥配置步骤），想了想还是写下来算了，以后免得又忘记。</p><p>minikube 会在宿主机上创建一个 bridge 让集群与宿主机通信：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">39: br-820c7fa60371: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP group default</span><br><span class="line">    <span class="built_in">link</span>/ether 02:42:8d:3f:96:ae brd ff:ff:ff:ff:ff:ff</span><br><span class="line">    inet 192.168.49.1/24 brd 192.168.49.255 scope global br-820c7fa60371</span><br><span class="line">       valid_lft forever preferred_lft forever</span><br></pre></td></tr></table></figure><p>可以使用 <code>minikube profile list</code> 查看：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">❯ minikube profile list</span><br><span class="line">|----------|-----------|---------|--------------|------|---------|---------|-------|----------------|--------------------|</span><br><span class="line">| Profile  | VM Driver | Runtime |      IP      | Port | Version | Status  | Nodes | Active Profile | Active Kubecontext |</span><br><span class="line">|----------|-----------|---------|--------------|------|---------|---------|-------|----------------|--------------------|</span><br><span class="line">| minikube | docker    | docker  | 192.168.49.2 | 8443 | v1.30.0 | Running |     1 | *              | *                  |</span><br><span class="line">|----------|-----------|---------|--------------|------|---------|---------|-------|----------------|--------------------|</span><br></pre></td></tr></table></figure><p>也就是可以将环境变量修改成 <code>192.168.49.1</code> 地址就可以将让集群使用宿主机的代理：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">k8s_proxy</span></span>() &#123;</span><br><span class="line">  proxy_server=<span class="string">&quot;http://192.168.49.1:10809&quot;</span></span><br><span class="line">  no_proxy=<span class="string">&quot;localhost,127.0.0.1,.local,192.168.49.2&quot;</span></span><br><span class="line">  <span class="built_in">export</span> http_proxy=<span class="string">&quot;<span class="variable">$proxy_server</span>&quot;</span></span><br><span class="line">  <span class="built_in">export</span> https_proxy=<span class="string">&quot;<span class="variable">$proxy_server</span>&quot;</span></span><br><span class="line">  <span class="built_in">export</span> all_proxy=<span class="string">&quot;<span class="variable">$proxy_server</span>&quot;</span></span><br><span class="line">  <span class="built_in">export</span> HTTP_PROXY=<span class="string">&quot;<span class="variable">$proxy_server</span>&quot;</span></span><br><span class="line">  <span class="built_in">export</span> HTTPS_PROXY=<span class="string">&quot;<span class="variable">$proxy_server</span>&quot;</span></span><br><span class="line">  <span class="built_in">export</span> ALL_PROXY=<span class="string">&quot;<span class="variable">$proxy_server</span>&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">export</span> no_proxy=<span class="string">&quot;<span class="variable">$no_proxy</span>&quot;</span></span><br><span class="line">  <span class="built_in">export</span> NO_PROXY=<span class="string">&quot;<span class="variable">$no_proxy</span>&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代理客户端监听至 <code>0.0.0.0</code> <s>（虽然有些危险，但是自己的电脑应该也没事吧）</s>。</p>]]></content>
    
    
    <summary type="html">在好久以前就折腾过 minikube 的代理配置，但是当时懒得写下来（其实也没啥配置步骤），想了想还是写下来算了，以后免得又忘记。minikube 会在宿主机上创建一个 bridge 让集群与宿主机通信：也就是可以将环境变量修改成 192.168.49.1 地址就可以将让集群使用宿主机的代理：</summary>
    
    
    
    <category term="k8s" scheme="http://blog.fooo.in/categories/k8s/"/>
    
    
  </entry>
  
  <entry>
    <title>Linux 设置默认麦克风以及音频输出</title>
    <link href="http://blog.fooo.in/2024/07/24/linux/linux-setting-the-default-microphone/"/>
    <id>http://blog.fooo.in/2024/07/24/linux/linux-setting-the-default-microphone/</id>
    <published>2024-07-24T00:00:00.000Z</published>
    <updated>2025-10-29T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>最近在想办法医治我的拖延症，于是从各种出现的小问题开始解决。因为我其中一台笔记本在飞书开会的时候经常会出现默认麦克风无法使用的情况，经常需要调整，不说很大的问题，但是也是一个小问题。</p><p>没有使用 PulseAudio 作为我的音频服务器，如果是想查看 PulseAudio 的设置方法，参考 <a href="https://wiki.archlinux.org/title/PulseAudio/Examples#Set_default_input_source">Arch Wiki</a>。</p><p>通过 PipeWire 设置默认麦克风。最开始我参考 <a href="https://wiki.archlinux.org/title/Advanced_Linux_Sound_Architecture/Troubleshooting#Setting_the_default_microphone/capture_device">Arch Wiki</a><br>中的内容，使用 <code>arecord -L</code> 查看所有设备：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arecord -L</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">null</span><br><span class="line">    Discard all samples (playback) or generate zero samples (capture)</span><br><span class="line">pipewire</span><br><span class="line">    PipeWire Sound Server</span><br><span class="line">default</span><br><span class="line">    Default ALSA Output (currently PipeWire Media Server)</span><br><span class="line">sysdefault:CARD=sofhdadsp</span><br><span class="line">    sof-hda-dsp,</span><br><span class="line">    Default Audio Device</span><br></pre></td></tr></table></figure><p>才发现默认麦克风由 PipeWire 控制。所以直接配置 PipeWire 来选择默认的录音设备，不修改 <code>~/.asoundrc</code>。</p><p>查看麦克风设备：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pactl list short sources</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">48      alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__HDMI3__sink.monitor       PipeWire s24-32le 2ch 48000Hz    SUSPENDED</span><br><span class="line">49      alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__HDMI2__sink.monitor       PipeWire s24-32le 2ch 48000Hz    SUSPENDED</span><br><span class="line">50      alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__HDMI1__sink.monitor       PipeWire s24-32le 2ch 48000Hz    SUSPENDED</span><br><span class="line">51      alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink.monitor     PipeWire s32le 2ch 48000Hz       SUSPENDED</span><br><span class="line">52      alsa_input.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Mic2__source       PipeWires24-32le 2ch 48000Hz     SUSPENDED</span><br><span class="line">53      alsa_input.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Mic1__source       PipeWires32le 2ch 48000Hz        SUSPENDED</span><br></pre></td></tr></table></figure><p>所以到底哪一个是有效的麦克风呢？我并没有去往下查找这些麦克风命名的规范以及各种信息，可以看到最右边的输出是 SUSPENDED，所以可以通过开启麦克风来进行测试（我也不知道为什么使用 arecord 命令麦克风会正常工作，或许是全遍历一边吧）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arecord -vv --format=dat /dev/null</span><br></pre></td></tr></table></figure><p>这时，就可以发现处于 RUNNING 的麦克风设备是可以正常工作的了，选择设备名称，设置为默认麦克风：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pactl set-default-source &lt;source-name&gt;</span><br></pre></td></tr></table></figure><p>验证一下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pactl info | grep <span class="string">&quot;Default Source&quot;</span></span><br></pre></td></tr></table></figure><p>好，现在飞书会议麦克风设置成系统默认也可以正常工作了。</p><p>音频输出方面是因为笔记本每次插上 HDMI 之后都会错误都会错误切换到 HDMI 那边，然而显示器并没有这个功能。</p><p>同样列出可用的输出设备（Sinks）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pactl list sinks</span><br></pre></td></tr></table></figure><p>或者：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pactl list sinks</span><br></pre></td></tr></table></figure><p>找到能出声的输出设备，把它设置成默认输出设备：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pactl set-default-sink alsa_output.pci-0000_00_1f.3-platform-skl_hda_dsp_generic.HiFi__Speaker__sink</span><br></pre></td></tr></table></figure><p>参考：</p><ul><li><a href="https://wiki.archlinux.org/title/Advanced_Linux_Sound_Architecture/Troubleshooting#Setting_the_default_microphone/capture_device">https://wiki.archlinux.org/title/Advanced_Linux_Sound_Architecture/Troubleshooting#Setting_the_default_microphone/capture_device</a></li><li><a href="https://wiki.archlinux.org/title/PulseAudio/Examples#Set_default_input_source">https://wiki.archlinux.org/title/PulseAudio/Examples#Set_default_input_source</a></li></ul>]]></content>
    
    
    <summary type="html">最近在想办法医治我的拖延症，于是从各种出现的小问题开始解决。因为我其中一台笔记本在飞书开会的时候经常会出现默认麦克风无法使用的情况，经常需要调整，不说很大的问题，但是也是一个小问题。中的内容，使用 arecord -L 查看所有设备：输出：</summary>
    
    
    
    <category term="linux" scheme="http://blog.fooo.in/categories/linux/"/>
    
    
    <category term="linux" scheme="http://blog.fooo.in/tags/linux/"/>
    
    <category term="alsa" scheme="http://blog.fooo.in/tags/alsa/"/>
    
    <category term="pipewire" scheme="http://blog.fooo.in/tags/pipewire/"/>
    
  </entry>
  
  <entry>
    <title>Vhost-user Protocol</title>
    <link href="http://blog.fooo.in/2024/07/04/qemu/vhost-user/"/>
    <id>http://blog.fooo.in/2024/07/04/qemu/vhost-user/</id>
    <published>2024-07-04T00:00:00.000Z</published>
    <updated>2025-10-29T16:32:04.111Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>vhost-user enables VIRTIO devices to be implemented as separate processes outside of QEMU.</p>]]></content>
    
    
    <summary type="html">vhost-user enables VIRTIO devices to be implemented as separate processes outside of QEMU.</summary>
    
    
    
    <category term="qemu" scheme="http://blog.fooo.in/categories/qemu/"/>
    
    
  </entry>
  
  <entry>
    <title>SAR 命令输出解释</title>
    <link href="http://blog.fooo.in/2024/04/16/systems-performance/explain-sar-output/"/>
    <id>http://blog.fooo.in/2024/04/16/systems-performance/explain-sar-output/</id>
    <published>2024-04-16T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>SAR (System Activity Reporter)</p><p>在这里记录一下 <code>sar</code> 输出的解释，以及输出的指标有什么作用。<br>其实都在 mannual 中说的很清楚了，在这里记录只是加深自己的理解和分析这些参数可以知道什么。</p><ol><li><code>sar</code> 功能：</li></ol><ul><li><code>sar</code> 收集系统的性能数据，包括 CPU 使用率、内存使用情况、磁盘 IO、网络流量等，并以可读的格式呈现。</li><li><code>sar</code> 还可以生成历史数据报告，帮助管理员了解系统的负载、趋势和峰值。</li></ul><ol start="2"><li>常用参数：</li></ol><ul><li><code>-u</code>：CPU 使用情况</li><li><code>-r</code>：内存使用情况</li><li><code>-b</code>：磁盘 IO 情况</li><li><code>-n</code>：网络流量情况</li><li><code>-q</code>：负载情况</li></ul><h2 id="CPU">CPU</h2><p>下面命令每隔 1 秒输出所有信息一次，总共输出 10 次。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sar -u ALL 1 10</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Linux 6.8.4-arch1-1 (xun) 04/16/2024 _x86_64_(20 CPU)</span><br><span class="line"></span><br><span class="line">10:21:16 AM     CPU      %usr     %nice      %sys   %iowait    %steal      %irq     %soft    %guest    %gnice     %idle</span><br><span class="line">10:21:17 AM     all      0.75      0.00      0.60      0.00      0.00      0.15      0.05      0.00      0.00     98.45</span><br><span class="line">10:21:18 AM     all      0.61      0.00      0.35      0.00      0.00      0.10      0.05      0.00      0.00     98.89</span><br><span class="line">10:21:19 AM     all      2.31      0.00      0.70      0.00      0.00      0.25      0.05      0.00      0.00     96.69</span><br><span class="line">10:21:20 AM     all      0.85      0.00      0.55      0.05      0.00      0.15      0.05      0.00      0.00     98.35</span><br><span class="line">10:21:21 AM     all      0.55      0.00      0.55      0.00      0.00      0.00      0.15      0.00      0.00     98.75</span><br><span class="line">10:21:22 AM     all      0.70      0.00      0.30      0.00      0.00      0.15      0.15      0.00      0.00     98.69</span><br><span class="line">10:21:23 AM     all      0.50      0.00      0.25      0.00      0.00      0.05      0.00      0.00      0.00     99.19</span><br><span class="line">10:21:24 AM     all      0.55      0.00      0.30      0.00      0.00      0.10      0.00      0.00      0.00     99.05</span><br><span class="line">10:21:25 AM     all      0.35      0.00      0.30      0.00      0.00      0.10      0.10      0.00      0.00     99.15</span><br><span class="line">10:21:26 AM     all      0.65      0.00      0.30      0.00      0.00      0.15      0.05      0.00      0.00     98.85</span><br><span class="line">Average:        all      0.78      0.00      0.42      0.01      0.00      0.12      0.07      0.00      0.00     98.61</span><br></pre></td></tr></table></figure><ol><li><p><strong>%user / %usr</strong>：</p><ul><li><code>%user</code> 或 <code>%usr</code> 表示 CPU 在执行用户级别（应用程序）任务时的利用率。这包括处理运行在用户空间的应用程序和处理器等待其工作完成的时间。高的 <code>%user</code> 值可能表示系统上有许多用户级别任务在运行。</li><li>示例：<code>%user</code> 或 <code>%usr</code> 在 50% 以上，可能表示系统上有大量用户级别任务正在运行。</li></ul><blockquote><p>这两个指标的区别在于是否包括运行虚拟处理器的时间。<code>%user</code> 包括，<code>%usr</code> 不包括。</p></blockquote></li><li><p><strong>%nice</strong>：</p><ul><li><code>%nice</code> 表示 CPU 在执行用户级别（应用程序）任务，并具有 nice 优先级的任务时的利用率。nice 值较高的任务具有较低的优先级。</li><li>示例：<code>%nice</code> 在 10% 以上，可能表示有一些优先级较低的任务正在执行。</li></ul></li><li><p><strong>%system / %sys</strong>：</p><ul><li><code>%system</code> 或 <code>%sys</code> 表示 CPU 在执行系统级别（内核）任务时的利用率。这包括内核运行的系统任务、处理中断和执行系统调用的时间。</li><li>示例：<code>%system</code> 或 <code>%sys</code> 在 20% 以上，可能表示系统内核在执行大量的系统级别任务或处理中断。</li></ul><blockquote><p><code>%system</code> 包括服务硬件和软件中断的时间，<code>%sys</code> 不包括。</p></blockquote></li><li><p><strong>%iowait</strong>：</p><ul><li><code>%iowait</code> 表示 CPU 在等待磁盘 IO 完成时的空闲时间百分比。如果此值较高，则表示系统中有较多的 IO 操作正在进行，可能存在 IO 瓶颈。</li><li>示例：<code>%iowait</code> 在 30% 以上，可能表示系统中存在磁盘 IO 瓶颈。但只是可能，因为 <code>iowait</code> 是以一个 CPU 的角度的状态，而不是以进程的状态。<blockquote><p>参考 <a href="https://www.kawabangga.com/posts/5903">iowait 的含义</a></p></blockquote></li></ul></li><li><p><strong>%steal</strong>：</p><ul><li><code>%steal</code> 表示在虚拟化环境中，CPU 被其他虚拟处理器占用时的时间百分比。这可能由于宿主系统或其他虚拟机的高 CPU 使用率导致。</li><li>示例：<code>%steal</code> 在 5% 以上，可能表示虚拟化环境中 CPU 竞争较高。</li></ul></li><li><p><strong>%irq / %soft</strong>：</p><ul><li><code>%irq</code> 表示 CPU 用于处理硬件中断的时间百分比，而 <code>%soft</code> 表示 CPU 用于处理软件中断的时间百分比。高的值可能表示系统中有较多的中断发生。</li><li>示例：<code>%irq</code> 或 <code>%soft</code> 在 5% 以上，可能表示系统中有大量的中断发生。</li></ul></li><li><p><strong>%guest / %gnice</strong>：</p><ul><li><code>%guest</code> 或 <code>%gnice</code> 表示 CPU 用于运行虚拟处理器的时间百分比，或者用于运行具有 nice 优先级的虚拟处理器的时间百分比。</li><li>示例：<code>%guest</code> 或 <code>%gnice</code> 在 10% 以上，可能表示虚拟化环境中的虚拟处理器占用较多 CPU 资源。</li></ul></li><li><p><strong>%idle</strong>：</p><ul><li><code>%idle</code> 表示 CPU 空闲且系统没有未完成的磁盘 IO 请求的时间百分比。较高的 <code>%idle</code> 值表示系统的 CPU 使用率较低。</li><li>示例：<code>%idle</code> 在 60% 以上，表示系统中的 CPU 大部分时间处于空闲状态。</li></ul></li></ol><blockquote><p>与 <code>iostat</code> 输出参数中关于 CPU 的差不多。</p></blockquote><h2 id="Memory">Memory</h2><ol><li><p><strong>kbmemused</strong>：</p><ul><li>表示已使用的内存量（以千字节为单位），计算公式为总安装内存 - kbmemfree - kbbuffers - kbcached - kbslab。</li></ul></li><li><p><strong>%memused</strong>：</p><ul><li>表示已使用内存的百分比。</li></ul></li><li><p><strong>kbbuffers</strong>：</p><ul><li>表示内核用作缓冲区的内存量（以千字节为单位）。</li></ul></li><li><p><strong>kbcached</strong>：</p><ul><li>表示内核用作缓存的数据内存量（以千字节为单位）。</li></ul></li><li><p><strong>kbcommit</strong>：</p><ul><li>表示当前工作负载所需的内存量（以千字节为单位），是确保系统永远不会发生内存不足的估计值，包括 RAM 和 swap 需要的内存。</li></ul></li><li><p><strong>%commit</strong>：</p><ul><li>表示当前工作负载所需内存的百分比，相对于总内存（RAM+swap）。由于内核通常会过度承诺内存，因此此数字可能大于 100%。</li></ul></li><li><p><strong>kbactive</strong>：</p><ul><li>表示活跃内存量（以千字节为单位），最近使用且通常不会被回收的内存。</li></ul></li><li><p><strong>kbinact</strong>：</p><ul><li>表示非活跃内存量（以千字节为单位），较长时间未被使用，更容易被回收以用于其他目的的内存。</li></ul></li><li><p><strong>kbdirty</strong>：</p><ul><li>表示等待写回磁盘的内存量（以千字节为单位）。</li></ul></li><li><p><strong>kbanonpg</strong>：</p><ul><li>表示映射到用户空间页表中的非文件备份页面的内存量（以千字节为单位）。</li></ul></li><li><p><strong>kbslab</strong>：</p><ul><li>表示内核用于缓存数据结构的内存量（以千字节为单位）。</li></ul></li><li><p><strong>kbkstack</strong>：</p><ul><li>表示用于内核堆栈空间的内存量（以千字节为单位）。</li></ul></li><li><p><strong>kbpgtbl</strong>：</p><ul><li>表示用于最低级别页表的内存量（以千字节为单位）。</li></ul></li><li><p><strong>kbvmused</strong>：</p><ul><li>表示已使用的虚拟地址空间的内存量（以千字节为单位）。</li></ul></li></ol>]]></content>
    
    
    <summary type="html">SAR (System Activity Reporter)在这里记录一下 sar 输出的解释，以及输出的指标有什么作用。其实都在 mannual 中说的很清楚了，在这里记录只是加深自己的理解和分析这些参数可以知道什么。1. sar 功能：- sar 收集系统的性能数据，包括 CPU 使用率、内存使用情况、磁盘IO、网络流量等，并以可读的格式呈现。- sar 还可以生成历史数据报告，帮助管理员了解系统的负载、趋势和峰值。2. 常用参数：- -u：CPU使用情况- -r：内存使用情况- -b：磁盘IO情况- -n：网络流量情况- -q：负载情况</summary>
    
    
    
    <category term="systems-performance" scheme="http://blog.fooo.in/categories/systems-performance/"/>
    
    
  </entry>
  
  <entry>
    <title>MYSQL 101</title>
    <link href="http://blog.fooo.in/2024/04/14/SQL/mysql-101/"/>
    <id>http://blog.fooo.in/2024/04/14/SQL/mysql-101/</id>
    <published>2024-04-14T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>MYSQL 的一些常见命令总结，方便自己更快复习。</p><h2 id="一些常用的重要的-SQL-命令">一些常用的重要的 SQL 命令</h2><ul><li>SELECT - extracts data from a database</li><li>UPDATE - updates data in a database</li><li>DELETE - deletes data from a database</li><li>INSERT INTO - inserts new data into a database</li><li>CREATE DATABASE - creates a new database</li><li>ALTER DATABASE - modifies a database</li><li>CREATE TABLE - creates a new table</li><li>ALTER TABLE - modifies a table</li><li>DROP TABLE - deletes a table</li><li>CREATE INDEX - creates an index (search key)</li><li>DROP INDEX - deletes an index</li></ul><h2 id="SELECT">SELECT</h2><p>语法：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column1, column2, ...</span><br><span class="line"><span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><h3 id="SELECT-DISTINCT">SELECT DISTINCT</h3><p><code>SELECT DISTINCT</code> 语句用于返回不同的值。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> column1, column2, ...</span><br><span class="line"><span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><p>获取不同的 column 的数量：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(<span class="keyword">DISTINCT</span> <span class="keyword">column</span>) <span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><h2 id="WHERE">WHERE</h2><p><code>WHERE</code> 子句用于过滤记录。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column1, column2, ...</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><blockquote><p><code>WHERE</code> 还可以用于 <code>UPDATE</code>，<code>DELETE</code> 等等。</p></blockquote><h2 id="AND-OR-NOT">AND/OR/NOT</h2><p><code>WHERE</code> 子句可以和 <code>AND</code>, <code>OR</code>, <code>NOT</code> 运算符结合使用。</p><h3 id="AND">AND</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column1, column2, ...</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> condition1 <span class="keyword">AND</span> condition2 <span class="keyword">AND</span> condition3 ...;</span><br></pre></td></tr></table></figure><h3 id="OR">OR</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column1, column2, ...</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> condition1 <span class="keyword">OR</span> condition2 <span class="keyword">OR</span> condition3 ...;</span><br></pre></td></tr></table></figure><h3 id="NOT">NOT</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column1, column2, ...</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">NOT</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><h2 id="AND-和-OR-NOT-结合使用">AND 和 OR, NOT 结合使用</h2><p>在满足 condition1 的同时满足 condition2/condition3:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> condition1 <span class="keyword">AND</span> (condition2 <span class="keyword">OR</span> condition3)</span><br></pre></td></tr></table></figure><p>多个否定：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">NOT</span> condition1 <span class="keyword">AND</span> <span class="keyword">NOT</span> condition2;</span><br></pre></td></tr></table></figure><h2 id="ORDER-BY">ORDER BY</h2><p><code>ORDER BY</code> 关键字用于按升序或降序对结果集进行排序。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column1, column2, ...</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> column1, column2, ... <span class="keyword">ASC</span><span class="operator">|</span><span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><blockquote><p>ASC:Ascending（升序）, DESC: descending（降序）</p></blockquote><h2 id="INSERT-INTO">INSERT INTO</h2><p><code>INSERT INTO</code> 语句用于在表中插入新记录。</p><p>指定要插入的值：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> table_name (column1, column2, column3, ...)</span><br><span class="line"><span class="keyword">VALUES</span> (value1, value2, value3, ...)</span><br></pre></td></tr></table></figure><p>向表的所有列添加内容，不需要在 SQL 中指定列名称：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> table_name</span><br><span class="line"><span class="keyword">VALUES</span>(value1, value2, value3, ...);</span><br></pre></td></tr></table></figure><h2 id="NULL-值">NULL 值</h2><p>判断一个字段是否为 NULL:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_names</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> column_name <span class="keyword">IS</span> <span class="keyword">NULL</span>;</span><br></pre></td></tr></table></figure><p>与 <code>NOT</code> 配合使用：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> column_name <span class="keyword">IS</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span>;</span><br></pre></td></tr></table></figure><h2 id="UPDATE-SET">UPDATE SET</h2><p><code>UPDATE</code> 用于更新已存在的记录。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> table_name</span><br><span class="line"><span class="keyword">SET</span> column1 <span class="operator">=</span> value1, column2 <span class="operator">=</span> value2, ...</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><blockquote><p>WARN: 注意使用 <code>UPDATE SET</code> 语句时要添加 <code>WHERE</code>，不然，表中的所有记录都会被修改。</p></blockquote><h2 id="DELETE">DELETE</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><h2 id="LIMIT">LIMIT</h2><p><code>LIMIT</code> 指定返回的记录数。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span></span><br><span class="line">LIMIT number;</span><br></pre></td></tr></table></figure><p>从查找出的记录中返回前 3 条记录：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name</span><br><span class="line">LIMIT <span class="number">3</span>;</span><br></pre></td></tr></table></figure><h3 id="配合-OFFSET-使用">配合 OFFSET 使用</h3><p>从查找出来的第 3 个记录开始返回（不包括第 3 个）：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name</span><br><span class="line">LIMIT <span class="number">3</span> <span class="keyword">OFFSET</span> <span class="number">3</span>;</span><br></pre></td></tr></table></figure><blockquote><p>常用于分页返回</p></blockquote><h3 id="配合-WHERE-使用">配合 WHERE 使用</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span></span><br><span class="line">LIMIT <span class="number">3</span>;</span><br></pre></td></tr></table></figure><h2 id="MYSQL-Functions">MYSQL Functions</h2><p>一些常见的函数。</p><h3 id="MIN-MAX">MIN(), MAX()</h3><p><code>MIN()</code> 函数返回所选列的最小值。<br><code>MAX()</code> 函数返回所选列的最大值。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="built_in">MIN</span>(column_name)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="built_in">MAX</span>(column_name)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><h3 id="COUNT-AVG-SUM">COUNT(), AVG(), SUM()</h3><p><code>COUNT()</code> 函数返回与指定条件匹配的行数。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="built_in">COUNT</span>(column_name)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><p>AVG() 函数返回列的值为数字的平均值。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="built_in">AVG</span>(column_name)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><p>SUM() 函数返回列的值为数字的总和。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="built_in">SUM</span>(column_name)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><h2 id="LIKE">LIKE</h2><p><code>LIKE</code> 运算符在 <code>WHERE</code> 子句中使用以搜索列中的指定模式。</p><p>有两个通配符经常与 LIKE 运算符一起使用：</p><ul><li>百分号 <code>%</code> 表示零个、一个或多个字符</li><li>下划线符号 <code>_</code> 代表一个，单个字符。（占位符）</li></ul><p>百分号和下划线可以组合使用。</p><p>以 a 开头且必须要两个字符以上：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> column_name <span class="keyword">LIKE</span> <span class="string">&#x27;a_%&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="IN">IN</h2><p><code>IN</code> 运算符允许在 <code>WHERE</code> 子句中指定多个值。</p><p><code>IN</code> 运算符是多个 <code>OR</code> 条件的简写。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> column_name <span class="keyword">IN</span> (value1, value2, ...);</span><br></pre></td></tr></table></figure><h2 id="BETWEEN">BETWEEN</h2><p><code>BETWEEN</code> 运算符选择给定范围内的值。这些值可以是数字、文本或日期。</p><p><code>BETWEEN</code> 运算符具有包容性：包括开始值和结束值。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span></span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> column_name <span class="keyword">BETWEEN</span> value1 <span class="keyword">AND</span> value2;</span><br></pre></td></tr></table></figure><p>当 <code>BETWEEN</code> 比较的是字符串时，会按照字符串的字典序进行比较。</p><h2 id="别名-AS">别名 AS</h2><p>别名用于为<strong>表</strong>或<strong>表中的列</strong>提供临时名称。</p><p>别名通常用于使列名更具可读性。</p><p>别名仅在该查询期间存在。</p><p>使用 AS 关键字创建别名。</p><p>Alias Column:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name <span class="keyword">AS</span> alias_name</span><br><span class="line"><span class="keyword">FROM</span> table_name;</span><br></pre></td></tr></table></figure><p>Alias Table:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name</span><br><span class="line"><span class="keyword">FROM</span> table_name <span class="keyword">AS</span> alias_name;</span><br></pre></td></tr></table></figure><h2 id="JOIN">JOIN</h2><p><code>JOIN</code> 子句用于根据它们之间的相关列组合来自两个或多个表的行。</p><p>四种 <code>JOIN</code> 类型：</p><ul><li><code>INNER JOIN</code>：返回两个表中值匹配的记录</li><li><code>LEFT JOIN</code>：返回左表所有记录，右表匹配记录</li><li><code>RIGHT JOIN</code>：返回右表的所有记录，以及左表的匹配记录</li><li><code>CROSS JOIN</code>：返回两个表中的所有记录</li></ul><p>INNER/LEFT/RIGHT 语法：</p><p>返回符合两表中 <code>column3</code> 相等的 <code>table1.column1</code> 和 <code>table2.column1</code> 列：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> table1.column1, table2.column1, ...</span><br><span class="line"><span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">INNER</span><span class="operator">/</span><span class="keyword">LEFT</span><span class="operator">/</span><span class="keyword">RIGHT</span> <span class="keyword">JOIN</span> table2</span><br><span class="line"><span class="keyword">ON</span> table1.column3<span class="operator">=</span>table2.column3;</span><br></pre></td></tr></table></figure><p>CROSS 语法：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_names,....</span><br><span class="line"><span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">CROSS</span> <span class="keyword">JOIN</span> table2;</span><br></pre></td></tr></table></figure><p>下面以这两个表为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; select * from fruits;</span><br><span class="line">+------+--------+</span><br><span class="line">| id   | name   |</span><br><span class="line">+------+--------+</span><br><span class="line">|    1 | apple  |</span><br><span class="line">|    2 | banana |</span><br><span class="line">|    3 | cherry |</span><br><span class="line">|    4 | peach  |</span><br><span class="line">|    7 | test   |</span><br><span class="line">+------+--------+</span><br><span class="line"></span><br><span class="line">mysql&gt; select * from product;</span><br><span class="line">+------+------+---------+</span><br><span class="line">| id   | fid  | pname   |</span><br><span class="line">+------+------+---------+</span><br><span class="line">|    1 |    1 | apples  |</span><br><span class="line">|    2 |    2 | bananas |</span><br><span class="line">|    3 |    3 | cherrys |</span><br><span class="line">|    4 |    4 | peachs  |</span><br><span class="line">+------+------+---------+</span><br></pre></td></tr></table></figure><h3 id="INNER-JOIN">INNER JOIN</h3><h3 id="LEFT-JOIN">LEFT JOIN</h3><p><code>LEFT JOIN</code> 关键字返回左表中的所有记录，即使右边没有匹配项。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> p.pname, f.name <span class="keyword">from</span> product <span class="keyword">as</span> p <span class="keyword">left</span> <span class="keyword">join</span> fruits <span class="keyword">as</span> f <span class="keyword">on</span> p.fid<span class="operator">=</span>f.id;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br><span class="line"><span class="operator">|</span> pname   <span class="operator">|</span> name   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br><span class="line"><span class="operator">|</span> apples  <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> bananas <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherrys <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peachs  <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br></pre></td></tr></table></figure><p>可以看到匹配的全是左表的记录。可以看 <code>RIGHT JOIN</code> 示例。</p><h3 id="RIGHT-JOIN">RIGHT JOIN</h3><p><code>RIGHT JOIN</code> 关键字返回右表中的所有记录，即使左边没有匹配项。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> p.pname, f.name <span class="keyword">from</span> product <span class="keyword">as</span> p <span class="keyword">right</span> <span class="keyword">join</span> fruits <span class="keyword">as</span> f <span class="keyword">on</span> p.fid<span class="operator">=</span>f.id;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br><span class="line"><span class="operator">|</span> pname   <span class="operator">|</span> name   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br><span class="line"><span class="operator">|</span> apples  <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> bananas <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherrys <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peachs  <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">NULL</span>    <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br></pre></td></tr></table></figure><p>看到 <code>pname</code> 为 <code>NULL</code> 这里返回右表 <code>fruits</code> 的所有内容，不管有没有匹配上。<br>只不过 <code>pname</code> 为 <code>NULL</code>。</p><h3 id="CROSS-JOIN">CROSS JOIN</h3><p><code>CROSS JOIN</code> 返回两个表行数的乘积。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> p.pname,f.name <span class="keyword">from</span> product <span class="keyword">as</span> p <span class="keyword">cross</span> <span class="keyword">join</span> fruits <span class="keyword">as</span> f;</span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br><span class="line"><span class="operator">|</span> pname   <span class="operator">|</span> name   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br><span class="line"><span class="operator">|</span> peachs  <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherrys <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> bananas <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apples  <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peachs  <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherrys <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> bananas <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apples  <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peachs  <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherrys <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> bananas <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apples  <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peachs  <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherrys <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> bananas <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apples  <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peachs  <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherrys <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> bananas <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apples  <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">---------+--------+</span></span><br></pre></td></tr></table></figure><h3 id="SELF-JOIN">SELF JOIN</h3><p>区分这两个：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> A.name, B.name <span class="keyword">from</span> fruits <span class="keyword">as</span> A, fruits <span class="keyword">as</span> B <span class="keyword">where</span> A.id <span class="operator">&lt;&gt;</span> B.id;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------+--------+</span></span><br><span class="line"><span class="operator">|</span> name   <span class="operator">|</span> name   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+--------+</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+--------+</span></span><br><span class="line"><span class="number">20</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br><span class="line"></span><br><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> A.name, B.name <span class="keyword">from</span> fruits <span class="keyword">as</span> A, fruits <span class="keyword">as</span> B;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------+--------+</span></span><br><span class="line"><span class="operator">|</span> name   <span class="operator">|</span> name   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+--------+</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+--------+</span></span><br><span class="line"><span class="number">25</span> <span class="keyword">rows</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure><p>连接操作中排除那些自身匹配的情况，确保只有不同 fruit 之间的匹配会被包含在结果中。</p><h2 id="UNION">UNION</h2><p><code>UNION</code> 运算符用于组合两个或多个 <code>SELECT</code> 语句的结果集。</p><ul><li><code>UNION</code> 中的每个 <code>SELECT</code> 语句必须具有相同的列数</li><li>列还必须具有相似的数据类型</li><li>每个 <code>SELECT</code> 语句中的列也必须是相同的顺序</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name(s) <span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">UNION</span></span><br><span class="line"><span class="keyword">SELECT</span> column_name(s) <span class="keyword">FROM</span> table2;</span><br></pre></td></tr></table></figure><p><code>UNION</code> 会将结果都排在一起。</p><h2 id="GROUP-BY">GROUP BY</h2><p><code>GROUP BY</code> 语句将具有相同值的行分组为汇总行，例如&quot;查找每个地区的客户数量&quot;。</p><p><code>GROUP BY</code> 语句通常与聚合函数 (<code>COUNT()</code>、<code>MAX()</code>, <code>MIN()</code>, <code>SUM()</code>, <code>AVG()</code>) 按一列或多列对结果集进行分组。</p><p>语法：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name(s)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> column_name(s)</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> column_name(s);</span><br></pre></td></tr></table></figure><p>一眼看过去，不懂 <code>GROUP BY</code> 的作用，举例：</p><p>存在以下表：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">+------+--------+</span><br><span class="line">| id   | name   |</span><br><span class="line">+------+--------+</span><br><span class="line">|    1 | apple  |</span><br><span class="line">|    2 | banana |</span><br><span class="line">|    3 | cherry |</span><br><span class="line">|    4 | peach  |</span><br><span class="line">|    7 | test   |</span><br><span class="line">|   99 | apple  |</span><br><span class="line">+------+--------+</span><br></pre></td></tr></table></figure><p>考虑以下输出：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> name <span class="keyword">from</span> fruits <span class="keyword">group</span> <span class="keyword">by</span> name;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------+</span></span><br><span class="line"><span class="operator">|</span> name   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+</span></span><br></pre></td></tr></table></figure><p>与：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> <span class="keyword">distinct</span> name <span class="keyword">from</span> fruits;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------+</span></span><br><span class="line"><span class="operator">|</span> name   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+</span></span><br></pre></td></tr></table></figure><p><code>DISTINCT</code> 输出与我们通过上面的 <code>GROUP BY</code> 查询得到的结果相同。</p><p><code>select name, count(*) from fruits group by name;</code></p><p>输出：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> name, <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> fruits <span class="keyword">group</span> <span class="keyword">by</span> name;</span><br><span class="line"><span class="operator">+</span><span class="comment">--------+----------+</span></span><br><span class="line"><span class="operator">|</span> name   <span class="operator">|</span> <span class="built_in">count</span>(<span class="operator">*</span>) <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+----------+</span></span><br><span class="line"><span class="operator">|</span> apple  <span class="operator">|</span>        <span class="number">2</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> banana <span class="operator">|</span>        <span class="number">1</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> cherry <span class="operator">|</span>        <span class="number">1</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> peach  <span class="operator">|</span>        <span class="number">1</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">|</span> test   <span class="operator">|</span>        <span class="number">1</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">--------+----------+</span></span><br></pre></td></tr></table></figure><h2 id="HAVING">HAVING</h2><p><code>HVAING</code> 与聚合函数一起使用，因为 <code>WHERE</code> 不能与聚合函数一起使用。</p><p>语法：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name(s)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> column_name(s)</span><br><span class="line"><span class="keyword">HAVING</span> <span class="keyword">condition</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> column_name(s);</span><br></pre></td></tr></table></figure><p>查询数量在两个以上的 fruit:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> <span class="built_in">count</span>(id), name <span class="keyword">from</span> fruits <span class="keyword">group</span> <span class="keyword">by</span> name  <span class="keyword">having</span> <span class="built_in">count</span>(id) <span class="operator">&gt;</span> <span class="number">1</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+-------+</span></span><br><span class="line"><span class="operator">|</span> <span class="built_in">count</span>(id) <span class="operator">|</span> name  <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+-------+</span></span><br><span class="line"><span class="operator">|</span>         <span class="number">2</span> <span class="operator">|</span> apple <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------+-------+</span></span><br></pre></td></tr></table></figure><blockquote><p>必须使用 <code>GROUP BY</code>，因为 <code>name</code> 属于非聚合列</p></blockquote><h2 id="EXISTS">EXISTS</h2><p><code>EXISTS</code> 运算符用于测试子查询中是否存在任何记录。</p><p>如果子查询返回一条或多条记录，<code>EXISTS</code> 运算符返回 TRUE。</p><p>语法：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> column_name(s)</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">EXISTS</span></span><br><span class="line">(<span class="keyword">SELECT</span> column_name <span class="keyword">FROM</span> table_name <span class="keyword">WHERE</span> <span class="keyword">condition</span>);</span><br></pre></td></tr></table></figure><h2 id="INSERT-INTO-SELECT">INSERT INTO SELECT</h2><p><code>INSERT INTO SELECT</code> 语句从一个表中复制数据并将其插入到另一个表中。</p><p><code>INSERT INTO SELECT</code> 语句要求源表和目标表中的数据类型匹配。</p><blockquote><p>目标表中的现有记录不受影响。</p></blockquote><p>语法：</p><p>将一个表的所有列复制到另一个表：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> table2</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><p>仅将一个表中的一些列复制到另一个表中：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> table2 (column1, column2, column3, ...)</span><br><span class="line"><span class="keyword">SELECT</span> column1, column2, column3, ...</span><br><span class="line"><span class="keyword">FROM</span> table1</span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">condition</span>;</span><br></pre></td></tr></table></figure><h2 id="CASE">CASE</h2><p><code>CASE</code> 语句遍历条件并在满足第一个条件时返回一个值（如 if-then-else 语句）。因此，一旦条件为真，它将停止读取并返回结果。如果没有条件为真，则返回 <code>ELSE</code> 子句中的值。</p><p>如果没有 <code>ELSE</code> 部分且没有条件为真，则返回 NULL。</p><p>语法：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CASE</span></span><br><span class="line">    <span class="keyword">WHEN</span> condition1 <span class="keyword">THEN</span> result1</span><br><span class="line">    <span class="keyword">WHEN</span> condition2 <span class="keyword">THEN</span> result2</span><br><span class="line">    <span class="keyword">WHEN</span> conditionN <span class="keyword">THEN</span> resultN</span><br><span class="line">    <span class="keyword">ELSE</span> <span class="keyword">result</span></span><br><span class="line"><span class="keyword">END</span>; </span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">MYSQL 的一些常见命令总结，方便自己更快复习。</summary>
    
    
    
    <category term="SQL" scheme="http://blog.fooo.in/categories/SQL/"/>
    
    
  </entry>
  
  <entry>
    <title>TCP 握手与挥手</title>
    <link href="http://blog.fooo.in/2024/04/03/tcp/tcp_handshaking/"/>
    <id>http://blog.fooo.in/2024/04/03/tcp/tcp_handshaking/</id>
    <published>2024-04-03T00:00:00.000Z</published>
    <updated>2024-04-03T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>最近在看某个项目的一个功能：<a href="https://www.criu.org/TCP_connection#Checkpoint_and_restore_TCP_connection">network-lock</a>, 感觉还挺有意思的，于是写了一个 TCP Demo 来测试这个功能，但是在我手动将 server 关闭的时候发现不能马上使用上次 server 监听的端口，而是跟我说端口已被占用，有些好奇，于是就有了以下的一些折腾。顺便在这里记录一下关于 TCP 三次握手与挥手的笔记。顺带 <code>tcpdump</code> 分析。</p><h2 id="介绍">介绍</h2><p>在网络通信中，TCP（Transmission Control Protocol）是一种可靠的传输协议，而TCP三次握手是建立TCP连接时的重要过程之一。</p><h2 id="概述">概述</h2><p>TCP 三次握手，包括三个步骤：</p><ol><li><p>客户端发送SYN请求：客户端向服务器发送一个SYN（同步）标志的数据包，表明它想要建立连接。</p></li><li><p>服务器响应ACK和SYN：服务器接收到客户端的SYN请求后，向客户端发送一个ACK（确认）数据包，表示已收到客户端的请求，并发送自己的SYN标志，以示同意建立连接。</p></li><li><p>客户端发送ACK：客户端接收到服务器的ACK和SYN后，向服务器发送一个ACK确认数据包，表示已收到服务器的响应，连接建立完成。</p></li></ol><h2 id="代码示例与-tcpdump-分析">代码示例与 tcpdump 分析</h2><p>下面是一个 client 和 server 建立 TCP 连接的代码。我最开始是用 C 写的，但是后面发现 Python 写起来舒服多了。</p><p>这个 Server 会监听 localhost 的 8880 端口。<br><code>Server.py</code>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_address = (<span class="string">&#x27;localhost&#x27;</span>, <span class="number">8880</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;Starting up on &#123;&#125; port &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*server_address))</span><br><span class="line">server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_FASTOPEN, <span class="number">1</span>)</span><br><span class="line">server_socket.bind(server_address)</span><br><span class="line"></span><br><span class="line">server_socket.listen(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Waiting for a connection...&#x27;</span>)</span><br><span class="line">    connection, client_address = server_socket.accept()</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Connection from&#x27;</span>, client_address)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                data = connection.recv(<span class="number">1024</span>)</span><br><span class="line">                <span class="comment"># TCP_QUICKACK</span></span><br><span class="line">                <span class="comment"># connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1)</span></span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;Received:&#x27;</span>, data.decode())</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> <span class="keyword">not</span> data:</span><br><span class="line">                    <span class="built_in">print</span>(<span class="string">&#x27;No data received from&#x27;</span>, client_address)</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">except</span> ConnectionResetError:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;Connection is reset by the client&#x27;</span>)</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">except</span> KeyboardInterrupt:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;KeyboardInterrupt&#x27;</span>)</span><br><span class="line">                connection.close()</span><br><span class="line">                exit(<span class="number">0</span>)</span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        connection.close()</span><br></pre></td></tr></table></figure><p>这个 Client 会与 Server 建立连接并每隔 2s 发送当前时间给 Server 。<br><code>Client.py</code>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"></span><br><span class="line">server_address = (<span class="string">&#x27;localhost&#x27;</span>, <span class="number">8880</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;Connecting to &#123;&#125; port &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*server_address))</span><br><span class="line">client_socket.connect(server_address)</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        message = <span class="string">&#x27;Hello, server! Time is &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(time.ctime())</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Sending:&#x27;</span>, message)</span><br><span class="line">        <span class="comment"># client_socket.sendall(message.encode())</span></span><br><span class="line">        client_socket.sendto(message.encode(), server_address)</span><br><span class="line">        time.sleep(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">finally</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Closing the connection&#x27;</span>)</span><br><span class="line">    client_socket.close()</span><br></pre></td></tr></table></figure><p>运行这两个代码，使用 <code>tcpdump</code> 来监听 8880 端口的 tcp 数据包：</p><h3 id="三次握手：">三次握手：</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ tcpdump -i lo -n port 8880  --absolute-tcp-sequence-numbers</span><br><span class="line"></span><br><span class="line">tcpdump: verbose output suppressed, use -v[v]... for full protocol decode</span><br><span class="line">listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes</span><br><span class="line">16:18:18.333865 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [S], seq 1857514943, win 33280, options [mss 65495,sackOK,TS val 3851230309 ecr 0,nop,wscale 7], length 0</span><br><span class="line">16:18:18.333874 IP 127.0.0.1.8880 &gt; 127.0.0.1.57830: Flags [S.], seq 3256784619, ack 1857514944, win 33280, options [mss 65495,sackOK,TS val 3851230309 ecr 3851230309,nop,wscale 7], length 0</span><br><span class="line">16:18:18.333880 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [.], ack 3256784620, win 260, options [nop,nop,TS val 3851230309 ecr 3851230309], length 0</span><br><span class="line"></span><br><span class="line">16:18:18.333910 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [P.], seq 1857514944:1857514991, ack 3256784620, win 260, options [nop,nop,TS val 3851230309 ecr 3851230309], length 47</span><br><span class="line">16:18:18.333912 IP 127.0.0.1.8880 &gt; 127.0.0.1.57830: Flags [.], ack 1857514991, win 260, options [nop,nop,TS val 3851230309 ecr 3851230309], length 0</span><br></pre></td></tr></table></figure><blockquote><p>PS: tcpdump 的参数说明：</p><p>i lo: 监听 lo 网卡<br>n: 不解析 IP 地址<br>port 8880: 只监听 8880 端口的数据包<br>absolute-tcp-sequence-numbers: 显示绝对的 TCP 序列号，如果不加这个参数，tcpdump 会显示相对的序列号。</p></blockquote><p><code>tcpdump</code> 输出的 Flags 字段解释：</p><p>[.] - ACK (Acknowledgment)<br>[S] - SYN (Start Connection)<br>[P] - PSH (Push Data)<br>[F] - FIN (Finish Connection)<br>[R] - RST (Reset Connection)<br>[S.] - SYN-ACK (SynAcK Packet)</p><p>可以看到，Client 发送了一个 SYN 数据包，Server 回复了一个 SYN+ACK 数据包，Client 再回复一个 ACK 数据包，三次握手完成。</p><p>seq 和 ack 是 TCP 数据包中的两个重要字段，seq 表示数据包的序列号，ack 表示确认号。SYN 数据包的 seq 是随机的，SYN+ACK 数据包的 seq 是随机的，ack 是 SYN 数据包的 seq+1，ACK 数据包的 seq 是 SYN+ACK 数据包的 ack，ack 是 SYN+ACK 数据包的 seq+1。</p><p>seq 和 ack 的作用是用来保证数据包的可靠传输。TCP 通过 seq 和 ack 来保证数据包的有序传输和可靠接收。</p><h3 id="三次挥手与四次挥手：">三次挥手与四次挥手：</h3><h4 id="三次挥手">三次挥手</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">16:21:10.284200 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [F.], seq 1857515790, ack 3256784620, win 260, options [nop,nop,TS val 3851402259 ecr 3851400314], length 0</span><br><span class="line">16:21:10.284297 IP 127.0.0.1.8880 &gt; 127.0.0.1.57830: Flags [F.], seq 3256784620, ack 1857515791, win 260, options [nop,nop,TS val 3851402259 ecr 3851402259], length 0</span><br><span class="line">16:21:10.284308 IP 127.0.0.1.57830 &gt; 127.0.0.1.8880: Flags [.], ack 3256784621, win 260, options [nop,nop,TS val 3851402259 ecr 3851402259], length 0</span><br></pre></td></tr></table></figure><p>三次挥手的过程只是将 FIN 与 ACK 数据包一同发送。</p><p>三次挥手是 Linux 默认开启 <a href="https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment">TCP 延迟确认机制</a>，而 RFC795 中是四次挥手：<a href="https://www.rfc-editor.org/rfc/rfc793#section-3.5">https://www.rfc-editor.org/rfc/rfc793#section-3.5</a></p><p>关于 TCP 延迟确认机制，可以使用 <code>TCP_QUICKACK</code> 关闭：<a href="https://www.man7.org/linux/man-pages/man7/tcp.7.html">https://www.man7.org/linux/man-pages/man7/tcp.7.html</a>, 关于这个参数，这个参数并不是 <strong>永久性的</strong>，设置或清除这个标志只会暂时地启用或禁用快速确认模式。随后的TCP协议操作会根据内部协议处理和延迟确认超时等因素再次进入或退出快速确认模式。因此，这个标志的状态在操作过程中可能会动态变化。可以看上面的 <code>server.py</code> 中的使用方法</p><p><a href="https://datatracker.ietf.org/doc/html/rfc1122#page-96">RFC1122</a> 中对 Delay ACK 的说明：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Delayed ACK&#x27;s</span><br><span class="line">Delay &lt; 0.5 seconds</span><br><span class="line">2nd full-sized segment ACK&#x27;d</span><br></pre></td></tr></table></figure><p>如 RFC1122 所说，一个主机可以延迟发送 ACK 报文高达 500 毫秒。此外，以每完整的数据包为一段，ACK 报文必须每两段发送一次（所以上面的三次挥手过程最后会发送一个 ACK 报文）。</p><p>延迟的 ACK 可以使应用程序有机会更新 TCP 接收窗口，也可以立即发送 ACK 报文。对于某些协议（如 Telnet），通过将 ACK、窗口更新和响应数据组合到一个段中，可以将服务器发送的响应数量减少 3 倍。</p><h4 id="四次挥手">四次挥手</h4><ol><li>Client 发送一个 FIN 数据包，表示不再发送数据了。进入 <code>FIN_WAIT_1</code> 状态。</li><li>Server 收到 FIN 数据包后，回复一个 ACK 数据包，表示收到了 Client 的 FIN 数据包。进入 <code>CLOSED_WAIT</code> 状态。</li><li>Client 收到 ACK 数据包，进入 <code>FIN_WAIT_2</code> 状态。</li><li>Server 发送一个 FIN 数据包，表示不再发送数据了。进入 <code>LAST_ACK</code> 状态。</li><li>Client 收到 FIN 数据包后，回复一个 ACK 数据包，表示收到了 Server 的 FIN 数据包。进入 <code>TIME_WAIT</code> 状态。</li><li>服务端收到了 ACK 应答报文后，就进入了 CLOSE 状态，至此服务端已经完成连接的关闭。</li></ol><p><strong>首先</strong>发出 FIN 的一侧，如果在给“对侧”的 FIN 响应了 ACK（发送了最后一个 ACK 数据包），那么就会超时等待 2 * MSL 时间（处于 TIME_WAIT 状态），然后关闭连接。在这段超时等待时间内，本地的端口不能被新连接使用；</p><p>RFC793 定义了 MSL 为 2 分钟，Linux 设置成了 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/ipv4/tcp.c?h=linux-6.8.y#n2863">30s</a> 。参数 tcp_max_tw_buckets 控制并发的 TIME_WAIT 的数量，默认值是 180000，如果超限，那么，系统会把多的 TIME_WAIT 状态的连接给 destory 掉，然后在日志里打一个警告（如：time wait bucket table overflow）</p><p>这也就是手动将 server 进程杀掉之后，端口仍被占用的原因。TCP 连接关闭之后，不会马上释放这个 socket，之后任何想将新 socket 绑定到相同的地址和端口的操作都会失败，直到旧的 socket 关闭为止。</p><blockquote><p>可以使用 <code>SO_REUSEADDR</code> 解决这个问题。<a href="https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ">https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ</a><br>还有一种方式是设置 <code>SO_LINGER</code> 选项的超时时长参数为 0 。这是不推荐使用的方法：<a href="https://stackoverflow.com/questions/3757289/when-is-tcp-option-so-linger-0-required">https://stackoverflow.com/questions/3757289/when-is-tcp-option-so-linger-0-required</a></p></blockquote><h4 id="TIEM-WAIT-的意义">TIEM_WAIT 的意义</h4><p>这个状态是<strong>避免延时的包的到达与随后的新连接相混淆</strong>和<strong>保证「被动关闭连接」的一方，能被正确的关闭</strong>。</p><p>保证「被动关闭连接」的一方可以被正确关闭：</p><p>如果客户端（主动关闭方）最后一次 ACK 报文（第四次挥手）在网络中丢失了，那么按照 TCP 可靠性原则，服务端（被动关闭方）会重发 FIN 报文。</p><p>假设客户端没有 TIME_WAIT 状态，而是在发完最后一次回 ACK 报文就直接进入 CLOSE 状态，如果该 ACK 报文丢失了，服务端则重传的 FIN 报文，而这时客户端已经进入到关闭状态了，在收到服务端重传的 FIN 报文后，就会回 RST 报文。</p>]]></content>
    
    
    <summary type="html">在网络通信中，TCP（Transmission Control Protocol）是一种可靠的传输协议，而TCP三次握手是建立TCP连接时的重要过程之一。TCP 三次握手，包括三个步骤：</summary>
    
    
    
    <category term="tcp" scheme="http://blog.fooo.in/categories/tcp/"/>
    
    
  </entry>
  
  <entry>
    <title>阅读流程分享</title>
    <link href="http://blog.fooo.in/2024/04/02/reading/my-reading-flow/"/>
    <id>http://blog.fooo.in/2024/04/02/reading/my-reading-flow/</id>
    <published>2024-04-02T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>最近入了一个墨水屏设备用来看和写，用以改善我的阅读流程（只是自己想要一个墨水屏设备罢了）。这里分享一下我的阅读流程。</p><p>PS: 墨水屏设备是「汉王 n10」</p><h2 id="为什么需要改善？">为什么需要改善？</h2><p>我最初的阅读流程非常松散，没有一个明确的流程，以前阅读博客文章只在手机上手动管理 RSS 订阅，但是电脑上却没有 RSS 订阅，这就导致了在不同端上阅读的不一致。<br>虽然有尝试过使用 <a href="https://github.com/FreshRSS/FreshRSS">FreshRSS</a> 来管理订阅，但是中途服务器过期了就再也没有折腾过了。<br>除此之外，我自己也想尝试养成阅读书籍的习惯，但是一直没有找到一个好的方法来管理自己的书籍，经常在电脑上看着看着就不知道丢哪了。<br>而且我更想的是在睡前或者闲暇时间看书，这就排除了电脑这个设备。手机显然不是一个合适的选择，于是在去年就想入手一个墨水屏设备，只是最近才下手买入。</p><h2 id="如何改善？">如何改善？</h2><p>下图是我改善后的阅读流程图：</p><p><img src="https://github.com/Xunop/notes/blob/main/reading/resource/reading_flow.png?raw=true" alt="reading-process"></p><p>使用的工具：</p><ul><li><a href="https://github.com/miniflux/v2">Miniflux</a>: 一个 feed reader，但是不一定要在网页上阅读。这个工具提供了许多集成功能，我是通过开启 Fever API 在我的墨水屏设备上使用 FeedMe 进行阅读。其实网页端也非常好了，我也可以不用 FeedMe 进行阅读而是直接在网页上进行。</li><li><a href="https://github.com/yourselfhosted/slash">slash</a>: 这个是一个链接缩短和共享平台，可以通过添加 url 和 name 快速访问网站。</li><li><a href="https://github.com/plateaukao/einkbro">einkbro</a>: 一个基于 Android WebView 的小型、快速的 Web 浏览器。它专为电子墨水设备量身定制，但也适用于普通 Android 设备。</li><li><a href="https://github.com/sigoden/dufs">dufs</a>: 一个支持静态服务、上传、搜索、访问控制、webdav…的文件服务器。</li><li><a href="https://github.com/newhinton/Round-Sync">Round-Sync</a>: 我用来与我的 webdav 服务器同步摘抄和笔记。</li><li><a href="https://github.com/seazon/FeedMe">FeedMe</a>: 这个阅读软件没有公布源码。其实只在网页上阅读已经完全够了。这个软件可有可无。</li><li><a href="https://github.com/usememos/memos">memos</a>: 一个开源、轻量级笔记服务。我用来记录自己的一些碎碎念。</li><li><a href="https://github.com/ismartcoding/plain-app">plain-app</a>: 是一个开源应用程序，允许通过网络浏览器管理您的手机。使用安全、易于使用的 Web 界面从桌面访问文件、视频、音乐、联系人、短信、通话等，我用来传输文件。</li><li><a href="https://github.com/gedoor/legado">legado</a>: 阅读 3.0, 阅读是一款可以自定义来源阅读网络内容的工具，为广大网络文学爱好者提供一种方便、快捷舒适的试读体验。</li></ul><p>解决我在阅读时候的“痛点”，需要在以下方面改善：</p><ul><li>稳定的阅读来源</li><li>多端同步阅读</li><li>方便添加阅读源（对于博客/公众号等一些网页链接）</li><li>摘抄/笔记同步</li></ul><ol><li>关于阅读来源：</li></ol><ul><li>RSS, 可以使用 <a href="https://github.com/cooderl/wewe-rss">wewe-rss</a> 订阅公众号</li><li>网页文章</li><li>电子书</li></ul><p>通过向 Miniflux 添加想要订阅的博客或者公众号来获取文章。除此之外，在我的墨水屏设备会有一些电子书。</p><ol start="2"><li>多端同步阅读：</li></ol><ul><li><p>slash 用来同步网页链接文章</p><ul><li>使用 slash 进行文章链接的同步，可以通过 slash 的前端页面进行查看。可以通过 Telegram Bot 添加文章内容。</li><li>在墨水屏设备上将 slash 设置为 einkbro 浏览器的主页，方便点击文章链接进行阅读。<blockquote><p>关于 einkbro 我准备自己改一下它的功能，因为它目前只支持将文章链接添加到 pocket，没有 slash，这就导致我如果看到我订阅的 RSS 中的有趣的文章想分享还需要转到 slash 页面去添加</p></blockquote></li></ul></li><li><p>Miniflux 用来同步 RSS 文章</p><ul><li>使用 Miniflux 进行 RSS Feeds 的聚合，定时刷新订阅的 RSS 链接。</li><li>使用 FeedMe 查看 Miniflux 的内容，或在 einkbro 中查看所需文章，实现对 RSS Feeds 的管理和阅读。</li></ul></li></ul><ol start="3"><li>添加阅读源/稍后阅读：</li></ol><p>类似于 <a href="https://getpocket.com/">pocket</a> 的需求，也就是 read it later。将想要稍后阅读的文章链接存放起来稍后阅读。</p><ul><li>Telegram Bot 添加<ul><li>可以通过 Telegram Bot (tgbot) 将想要阅读的文章链接添加到服务器。</li></ul></li><li>网页添加<ul><li>也可以在网页中添加文章链接。不过就是需要打开浏览器输入网址（麻烦）。</li></ul></li></ul><p>关于电子书方面暂时没有什么大的需求，就算有也可以通过汉王的服务或者 plain-app 进行传输，主要还是各种文章。</p><ol start="4"><li>阅读摘抄和笔记同步：</li></ol><ul><li>通过 WebDav 将书籍摘抄和笔记同步到服务器中。<ul><li>服务器部署了 dufs 这样一个 webdav 服务器，我还可以利用它同步我的网文阅读进度。我用 legado 看网文。</li></ul></li><li>对摘抄内容进行处理后发送到自己的 memos 网站，完善书籍阅读后的操作流程。<ul><li>memos 部署的 memos。向这里倒一些垃圾是很必要的。</li></ul></li></ul><h2 id="实现细节">实现细节</h2><p>其实需要我折腾写些代码的地方只是<strong>如何方便地添加阅读源/稍后阅读</strong>和<strong>同步摘抄笔记到 memos</strong>。<br>添加阅读源/稍后阅读我特意写了一个 Telegram Bot 来完成这件事情。</p><p>Telegram Bot: <a href="https://github.com/Xunop/save-link">https://github.com/Xunop/save-link</a></p><p>目前只实现了向 Miniflux 添加 RSS 订阅源和向 slash 添加分享链接：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">/start - Start the bot</span><br><span class="line">/help - Get help</span><br><span class="line">[website] [name] #tags - Save a link with a name</span><br><span class="line">[rss feed] [category] - Save an rss feed</span><br></pre></td></tr></table></figure><p>同步摘抄笔记到 memos 则是写了一个脚本：<a href="https://github.com/Xunop/notes2memos">https://github.com/Xunop/notes2memos</a></p><p>那阅读设备呢？ <s>购买墨水屏设备</s></p><h2 id="最后">最后</h2><p>我只希望我的墨水屏设备不会吃灰，然后自己养成看书的习惯。</p>]]></content>
    
    
    <summary type="html">最近入了一个墨水屏设备用来看和写，用以改善我的阅读流程（只是自己想要一个墨水屏设备罢了）。这里分享一下我的阅读流程。PS&quot;:&quot; 墨水屏设备是「汉王 n10」</summary>
    
    
    
    <category term="reading" scheme="http://blog.fooo.in/categories/reading/"/>
    
    
  </entry>
  
  <entry>
    <title>TCP Fast Open</title>
    <link href="http://blog.fooo.in/2024/03/18/tcp/tcp-fast-open/"/>
    <id>http://blog.fooo.in/2024/03/18/tcp/tcp-fast-open/</id>
    <published>2024-03-18T00:00:00.000Z</published>
    <updated>2025-10-29T16:32:04.111Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>折腾 TCP 的同时看到了 TCP Fast Open 这个有趣的参数，它是对 TCP 的优化，无需等待 3 次握手，应用程序就可以通过 TCP 发送数据。</p><p>正常 TCP 建立连接过程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">    Client                                               Server</span><br><span class="line"></span><br><span class="line">1.  CLOSED                                               LISTEN</span><br><span class="line"></span><br><span class="line">2.  SYN-SENT    --&gt;           SYN M                  --&gt; SYN-RECEIVED</span><br><span class="line"></span><br><span class="line">3.  ESTABLISHED &lt;--        SYN N,ACK M+1             &lt;-- SYN-RECEIVED</span><br><span class="line"></span><br><span class="line">4.  ESTABLISHED --&gt;          ACK N+1                 --&gt; ESTABLISHED</span><br></pre></td></tr></table></figure><p>当前 TCP 实现的问题是，只有在连接发起方收到来自对等 TCP 的 ACK（确认）段后，才能在连接上交换数据。也就是说，只有在三次握手的第三步（发起方发送的ACK报文段）中，数据才能从客户端发送到服务器。因此，在对等点之间交换数据之前，就会损失一个完整的往返时间 (round trip time) 。这种丢失的 RTT 是短网络对话延迟的重要组成部分。TCP Fast Open 就是为了解决这个问题。</p><h2 id="消除-RTT">消除 RTT</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">    Client                                               Server</span><br><span class="line"></span><br><span class="line">1.  CLOSED                                               LISTEN</span><br><span class="line"></span><br><span class="line">2.  SYN-SENT    --&gt; SYN, with cookie + data          --&gt; SYN-RECEIVED---</span><br><span class="line">                                                                       | Server TCP validates cookie, passes data to application</span><br><span class="line">3.  Client      &lt;--        SYN-ACK                   &lt;-- SYN-RECEIVED---</span><br><span class="line"></span><br><span class="line">4.  Client      &lt;--        responses                 &lt;-- Server          Server can send responses before receiving client ACK</span><br><span class="line"></span><br><span class="line">4.  ESTABLISHED --&gt;          ACK                     --&gt; ESTABLISHED</span><br></pre></td></tr></table></figure><p>上图所示步骤如下：</p><ol><li><p>客户端 TCP 发送 SYN，其中包含 TFO cookie（指定为 TCP 选项）和来自客户端应用程序的数据。</p></li><li><p>服务器 TCP 通过基于新 SYN 的源 IP 地址重复加密过程来验证 TFO cookie。如果 cookie 被证明是有效的，那么服务器 TCP 就可以确信这个 SYN 来自它声称来自的地址。这意味着服务器TCP可以立即将应用程序数据传递给服务器应用程序。</p></li><li><p>从这里开始，TCP 会话正常进行：服务器 TCP 向客户端发送 SYN-ACK 段，然后客户端 TCP 进行确认，从而完成三向握手。服务器TCP还可以在收到客户端的 ACK <strong>之前</strong> 向客户端 TCP 发送响应数据段。</p></li></ol><p>这是一个使用 <code>tcpdump</code> 查看使用了 <code>TCP_FAST_OPEN</code> 选项的抓包记录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">1. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [S], seq 3550480872:3550480878, win 33280, options [mss 65495,sackOK,TS val 1437621030 ecr 0,nop,wscale 7,tfo  cookie ce80700cf8e6113c,nop,nop], length 6</span><br><span class="line">2. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [S.], seq 2245778431, ack 3550480873, win 33280, options [mss 65495,sackOK,TS val 1437621030 ecr 1437621030,nop,wscale 7], length 0</span><br><span class="line">3. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [P.], seq 3550480873:3550480879, ack 2245778432, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 6</span><br><span class="line">4. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [.], ack 3550480879, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">5. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [P.], seq 2245778432:2245778438, ack 3550480879, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 6</span><br><span class="line">6. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [.], ack 2245778438, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">7. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [F.], seq 2245778438, ack 3550480879, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">8. IP 127.0.0.1.51902 &gt; 127.0.0.1.8000: Flags [F.], seq 3550480879, ack 2245778439, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br><span class="line">9. IP 127.0.0.1.8000 &gt; 127.0.0.1.51902: Flags [.], ack 3550480880, win 260, options [nop,nop,TS val 1437621030 ecr 1437621030], length 0</span><br></pre></td></tr></table></figure><p>可以看到 <code>3</code> 这个数据段在 <code>4</code> 这个 ACK 数据包之前。并且 options 中有一个 <code>cookie</code> 字段。</p><p><a href="http://Server.py">Server.py</a>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">server_address = (<span class="string">&#x27;localhost&#x27;</span>, <span class="number">8880</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;Starting up on &#123;&#125; port &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*server_address))</span><br><span class="line">server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line">server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_FASTOPEN, <span class="number">1</span>)</span><br><span class="line">server_socket.bind(server_address)</span><br><span class="line"></span><br><span class="line">server_socket.listen(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;Waiting for a connection...&#x27;</span>)</span><br><span class="line">    connection, client_address = server_socket.accept()</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;Connection from&#x27;</span>, client_address)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">            data = connection.recv(<span class="number">1024</span>)</span><br><span class="line">            <span class="comment"># TCP_QUICKACK</span></span><br><span class="line">            connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, <span class="number">1</span>)</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&#x27;Received:&#x27;</span>, data.decode())</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> data:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;No data received from&#x27;</span>, client_address)</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">finally</span>:</span><br><span class="line">        connection.close()</span><br></pre></td></tr></table></figure><p><a href="http://Client.py">Client.py</a>:</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line">addr = (<span class="string">&quot;localhost&quot;</span>, <span class="number">8880</span>)</span><br><span class="line">s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span class="line">s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">s.sendto(<span class="string">&quot;hello!&quot;</span>.encode(), <span class="number">536870912</span>, addr)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(s.recv(<span class="number">1000</span>))</span><br></pre></td></tr></table></figure><blockquote><p><code>sendto</code> 需要提供 ip, 因为是 connectionless.</p></blockquote><p>具体参考：</p><p>[1] <a href="https://lwn.net/Articles/508865/">https://lwn.net/Articles/508865/</a></p>]]></content>
    
    
    <summary type="html">折腾 TCP 的同时看到了 TCP Fast Open 这个有趣的参数，它是对 TCP 的优化，无需等待 3 次握手，应用程序就可以通过 TCP 发送数据。正常 TCP 建立连接过程：当前 TCP 实现的问题是，只有在连接发起方收到来自对等 TCP 的 ACK（确认）段后，才能在连接上交换数据。也就是说，只有在三次握手的第三步（发起方发送的ACK报文段）中，数据才能从客户端发送到服务器。因此，在对等点之间交换数据之前，就会损失一个完整的往返时间 (round trip time) 。这种丢失的 RTT 是短网络对话延迟的重要组成部分。TCP Fast Open 就是为了解决这个问题。</summary>
    
    
    
    <category term="tcp" scheme="http://blog.fooo.in/categories/tcp/"/>
    
    
    <category term="tcp" scheme="http://blog.fooo.in/tags/tcp/"/>
    
    <category term="network" scheme="http://blog.fooo.in/tags/network/"/>
    
  </entry>
  
  <entry>
    <title>23 年写的一些小脚本合集</title>
    <link href="http://blog.fooo.in/2024/02/29/interesting-things/scripted-collections/"/>
    <id>http://blog.fooo.in/2024/02/29/interesting-things/scripted-collections/</id>
    <published>2024-02-29T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>在这里稍微记录一下 23 年自己写的我觉得有意思的脚本。</p><h2 id="生成博客文章">生成博客文章</h2><p>Links: <a href="https://github.com/Xunop/hexo-format">https://github.com/Xunop/hexo-format</a></p><p>我以前写博客的方式是直接更改博客的源码，在 hexo 中的 <code>_post</code> 文件夹中写博客，感觉这个工作流很麻烦，于是就写了一个小脚本来完成将笔记转成博客文章这件事。<br>目前我的博客仓库里面有两个 git submodule , 分别是脚本和笔记，在我的笔记仓库提交 commit 之后，github action 会触发博客仓库的 github action 将我的笔记同步进博客的源码里面。</p><p>不过这个脚本写的非常乱，我现在看起来非常头痛 &gt;_&lt;。还有一些莫名其妙的功能，比如可以往 <code>.fignore</code> 文件中添加不想转为博客文章的文件。</p><h2 id="清除没有礼貌的人">清除没有礼貌的人</h2><p>Links: <a href="https://github.com/Xunop/njupt-netlogout">https://github.com/Xunop/njupt-netlogout</a></p><p>我校的校园网需要登录账号才能上网，我的账号比较特殊，可以不断网，所以会有同学借我的账号来用，但是借的人多了达到了设备限制，使用者发现自己登不上就会去账号后台将其他人注销掉从而挤出一个名额给自己使用 :( 。<br>因为经常被他们挤下去，我也很烦，所以我扒了一下账号后台注销登录的接口，写成了一个脚本放在路由器上，将不在白名单内的用户都注销掉。</p><p>实施这个方案后确实没人跟我抢了，但是我想让一些人使用我的账号时需要我上路由器更新白名单就很麻烦，于是我在 <code>dev-scheduling</code> 分支中加了调度的功能。</p><p>如果登录的人数小于四个并且其中有一个我的设备，那么不需要注销用户。<br>后台会统计用户登录的时长跟被注销次数，由此公式：<code>登录时长 / 被注销次数</code> 维护一个队列，每次注销将注销队列头的设备。随着用户的登录时长越长，他被注销的概率就越大，但是被注销次数多了他的被注销概率也会变小。这个脚勉强可以用吧，反正我的设备都在白名单里面，不参与到队列计算中。</p><h2 id="自动查询成绩">自动查询成绩</h2><p>Links: <a href="https://github.com/Xunop/death-knell">https://github.com/Xunop/death-knell</a></p><p>在考完试之后不想每次都自己去查成绩（主要是不想过那种提心吊胆的生活），于是扒了一下接口写了一个小脚本来查询成绩，将查询结果用 webhook 发送到飞书的 bot 中。</p><h2 id="LeetCode-推送">LeetCode 推送</h2><p>Links:</p><ul><li><a href="https://github.com/Xunop/leetcode-api">https://github.com/Xunop/leetcode-api</a></li><li><a href="https://github.com/Xunop/LeetCode-Push">https://github.com/Xunop/LeetCode-Push</a></li></ul><p>fork 别人写的 LeetCode 国际版的接口仓库，将里面的内容修改了一下（基本上没有一个接口是复用的，参数完全不同），部署到 vercel 上，可以获取一些 LeetCode CN 的信息。这是第一步。</p><p>拿到信息之后需要对信息进行处理，用云函数来处理信息，并且发送到飞书。是的，可以用这个来打卡。</p><p>还有一个放不出来，用飞书的机器人助手写了一堆应用流程来方便 bot 进行推送和交互。</p><p>目前实现的一些功能：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">- 绑定 leetcode 用户名与飞书用户： @bot add username</span><br><span class="line">- 解除绑定：@bot delete</span><br><span class="line">绑定之后 bot 将会每天私信发送每日一题，在群组中也会发送。</span><br><span class="line">- 查看每日一题：@bot daily</span><br><span class="line">需要绑定才能使用的功能：</span><br><span class="line">- 查看刷题记录：@bot solved</span><br><span class="line">- 查看刷题使用的编程语言：@bot langs</span><br><span class="line">- Bot 会在晚上 11:30 采集数据，在 11:40 发送今天提交的 ranking</span><br></pre></td></tr></table></figure><p>我觉得还 … 挺有意思的。</p><h2 id="批量下载音乐">批量下载音乐</h2><p>Links: <a href="https://github.com/Xunop/dwn-songs">https://github.com/Xunop/dwn-songs</a></p><p>我第一次用到我平常学的东西帮到我身边很少接触计算机的人。</p><p>过年回家的时候，家里的发小给了我一个 u 盘，跟我说这个 u 盘他平时用来插到汽车上当车载音乐的，但是这里面的有些歌有问题无法正常播放，想让我帮他把里面的歌重新下一遍。</p><p>我当时以为里面没多少歌的，然后后面一直鸽他，直到他假期快结束的时候我才去看这里面的东西。看到 300 条我是彻底放弃自己手动帮他下歌了，后来花了十几分钟写了一个小小的脚本完成了这个工作，插到我发小车上播放时他非常满意。</p><p>这段代码很简单，就是发送几个请求，但是能用到自己学到的东西帮到身边的人还挺有意思的。</p><p>平时写的脚本还挺多的，大部分都是配合自己的电脑使用（因为用的是 Hyprland，欢迎尝试），脚本应该都是在我的 <code>dotfile</code> 里面。</p>]]></content>
    
    
    <summary type="html">在这里稍微记录一下 23 年自己写的我觉得有意思的脚本。</summary>
    
    
    
    <category term="interesting-things" scheme="http://blog.fooo.in/categories/interesting-things/"/>
    
    
    <category term="scripts" scheme="http://blog.fooo.in/tags/scripts/"/>
    
  </entry>
  
  <entry>
    <title>gdb 常用操作记录</title>
    <link href="http://blog.fooo.in/2023/10/02/gdb/gdb-memo/"/>
    <id>http://blog.fooo.in/2023/10/02/gdb/gdb-memo/</id>
    <published>2023-10-02T00:00:00.000Z</published>
    <updated>2024-08-21T00:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>因为写了一段时间内核中的 objtool，出现一些问题时需要使用 gdb 进行调试。这里记录一下自己的常用操作。学习建议还是看 <a href="https://sourceware.org/gdb/current/onlinedocs/gdb.html/">gdb manual</a>，这是一点点笔记，gdb 的使用那可真是太多了。</p><h2 id="gdb-配置">gdb 配置</h2><p>gdb 有一个配置文件 <code>.gdbinit</code> ，可以在里面修改 gdb 配置。我的配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">set pagination off</span><br><span class="line">set history save on</span><br><span class="line">set history expansion on</span><br></pre></td></tr></table></figure><blockquote><p>禁止分页<br>开启历史记录<br>可以使用 ! 执行上一条命令</p></blockquote><h2 id="常用操作">常用操作</h2><p>我调试的东西需要传递参数给它：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gdb --args ./tools/objtool/objtool -o arch/arm64/kernel/debug-monitors.o</span><br></pre></td></tr></table></figure><p><code>start</code>, <code>next</code> <code>info</code> <code>print</code> 一些就不记录在这了。</p><h3 id="断点">断点</h3><p>打断点：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">b check.c:1111</span><br></pre></td></tr></table></figure><p>针对某个函数：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">b func</span><br></pre></td></tr></table></figure><h3 id="栈帧">栈帧</h3><p>查看栈帧（backtrace/bt）：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bt num</span><br></pre></td></tr></table></figure><blockquote><p>0 表示当前正在执行的函数。<br>num 为查看栈帧的数量，不加 num 则显示全部。<br>如果 num 是正数，则栈帧从 0 开始计数，如果是负数，则从最大的栈帧编号开始计数。一个是正向，一个是反向。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(gdb) bt</span><br><span class="line">    insn@entry=0x555555e73f60, state=...) at check.c:3739</span><br><span class="line">    at check.c:4281</span><br><span class="line">    at builtin-check.c:225</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(gdb) bt -1</span><br></pre></td></tr></table></figure><p>查看帧信息：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">info frame num</span><br></pre></td></tr></table></figure><blockquote><p>num 为栈帧编号。<br>简写 <code>i f num</code></p></blockquote><p>查看当前帧的所有局部变量和函数参数：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">info locals</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">info args</span><br></pre></td></tr></table></figure><p>切换帧：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">frame num</span><br></pre></td></tr></table></figure><blockquote><p>num 是栈帧编号。简写 <code>f num</code></p></blockquote><h3 id="结构体">结构体</h3><p>查看结构体中的字段及类型：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ptype struct</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">(gdb) ptype cfi</span><br><span class="line">type = struct cfi_state &#123;</span><br><span class="line">    struct hlist_node hash;</span><br><span class="line">    struct cfi_reg regs[32];</span><br><span class="line">    struct cfi_reg vals[32];</span><br><span class="line">    struct cfi_reg cfa;</span><br><span class="line">    int stack_size;</span><br><span class="line">    int drap_reg;</span><br><span class="line">    int drap_offset;</span><br><span class="line">    unsigned char type;</span><br><span class="line">    _Bool bp_scratch;</span><br><span class="line">    _Bool drap;</span><br><span class="line">    _Bool signal;</span><br><span class="line">    _Bool end;</span><br><span class="line">&#125; *</span><br></pre></td></tr></table></figure><p>查看结构体中的所有字段的值：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p cfi</span><br><span class="line">$9 = (struct cfi_state *) 0x7fffffffd9b0</span><br><span class="line"></span><br><span class="line">(gdb) p *((struct cfi_state *) cfi)</span><br><span class="line">$10 = &#123;hash = &#123;next = 0x0, pprev = 0x0&#125;, regs = &#123;&#123;base = -1, offset = 0&#125; &lt;repeats 32 times&gt;&#125;, vals = &#123;&#123;base = -1,</span><br><span class="line">      offset = 0&#125; &lt;repeats 32 times&gt;&#125;, cfa = &#123;base = 31, offset = 0&#125;, stack_size = 0, drap_reg = -1, drap_offset = -1,</span><br><span class="line">  type = 2 &#x27;\002&#x27;, bp_scratch = false, drap = false, signal = false, end = false&#125;</span><br></pre></td></tr></table></figure><blockquote><p>强转一下。</p></blockquote><h3 id="调用函数">调用函数</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">p func</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p offstr(insn-&gt;sec, insn-&gt;offset)</span><br><span class="line">$13 = 0x555555ea0330 &quot;mdscr_write+0x0&quot;</span><br></pre></td></tr></table></figure><h3 id="多线程">多线程</h3><ul><li><code>info inferiors</code> 查看进程列表</li><li><code>attach pid</code>绑定进程 id</li><li><code>inferior num</code> 切换到指定进程上进行调试</li><li><code>print $_exitcode</code> 显示进程退出时的返回值</li><li><code>set follow-fork-mode child</code> 追踪子进程</li><li><code>set follow-fork-mode parent</code> 追踪父进程</li><li><code>set detach-on-fork on fork</code> 调用时只追踪其中一个进程</li><li><code>set detach-on-fork off fork</code> 调用时会同时追踪父子进程</li><li><code>set schedule-multiple on</code> 调试某进程时，其他进程正常执行</li></ul>]]></content>
    
    
    <summary type="html">gdb 有一个配置文件 .gdbinit ，可以在里面修改 gdb 配置。我的配置： 禁止分页</summary>
    
    
    
    <category term="gdb" scheme="http://blog.fooo.in/categories/gdb/"/>
    
    
    <category term="gdbgdbgdb" scheme="http://blog.fooo.in/tags/gdbgdbgdb/"/>
    
  </entry>
  
</feed>
