<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>WoodenRobot&#39;s Blog</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://woodenrobot.me/"/>
  <updated>2024-06-07T02:59:12.927Z</updated>
  <id>https://woodenrobot.me/</id>
  
  <author>
    <name>WoodenRobot</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Mac OS 系统无法双击使用 Chrome 浏览器打开 HTML 文件</title>
    <link href="https://woodenrobot.me/2022/06/06/open-html/"/>
    <id>https://woodenrobot.me/2022/06/06/open-html/</id>
    <published>2022-06-06T06:37:44.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<p>Mac OS 系统中文件经常会被附加上特有的扩展属性 ( extend attributes )，具体表现是用 ls -l 查看时会有 @ 的标记,比如:</p><figure class="highlight shell"><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="meta prompt_">$ </span><span class="language-bash"><span class="built_in">ls</span> -l</span></span><br><span class="line">.rw-rw-r--@  6.5k woodenrobot  3 Aug  2020 index.html</span><br></pre></td></tr></table></figure><p>这个 @ 属性是在 Finder 中对文件进行任意操作后被增加上的，把 @ 属性去掉后就可以正常双击 HTML 文件使用 Chrome 打开了。</p><p>清除一个文件的所有扩展属性的方法：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">xattr -c filename</span> </span><br></pre></td></tr></table></figure><p>清除目录所有文件扩展属性的方法：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">xattr -rc dirpath</span> </span><br></pre></td></tr></table></figure><p>清除当前目录下所有文件扩展属性的方法：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">xattr -rc .</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Mac OS 系统中文件经常会被附加上特有的扩展属性 ( extend attributes )，具体表现是用 ls -l 查看时会有 @ 的标记,比如:&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;g
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Mac OS" scheme="https://woodenrobot.me/tags/Mac-OS/"/>
    
  </entry>
  
  <entry>
    <title>群晖 SSH 公钥免密登录</title>
    <link href="https://woodenrobot.me/2021/02/20/syno-ssh/"/>
    <id>https://woodenrobot.me/2021/02/20/syno-ssh/</id>
    <published>2021-02-19T23:36:04.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<ol><li>首先 <code>SSH</code> 登录群晖，检测群晖当前用户主目录下是否有 <code>.ssh</code> 文件夹，如果没有使用下列命令创建：<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"><span class="built_in">mkdir</span> ~/.ssh</span><br></pre></td></tr></table></figure></li><li>使用 <code>vim ~/.ssh/authorized_keys</code> 将自己的 <code>SSH</code> 公钥粘贴进去，按 <code>ESC</code> 输入 <code>wq</code> 并回车保存。</li></ol><span id="more"></span><ol start="3"><li><p>如果没有公钥的话，需要在自己电脑上使用 <code>ssh-keygen -t rsa -C &quot;MyName&quot;</code> 创建一个密钥，然后使用 <code>cat ~/.ssh/id_rsa.pub</code> 获取公钥信息，粘贴到<strong>群晖</strong>的 <code>~/.ssh/authorized_keys</code> 文件中。</p></li><li><p>设置权限：</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">chmod</span> 755 ~</span><br><span class="line"><span class="built_in">chmod</span> 600 ~/.ssh/authorized_keys</span><br><span class="line"><span class="built_in">chmod</span> 700 ~/.ssh</span><br></pre></td></tr></table></figure></li></ol><p><strong>Ps: 群晖用户目录权限默认为 777，必须要修改为755才能免密登录</strong></p><ol start="5"><li>修改 <code>sshd_config</code> 配置文件:<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 vim /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure></li></ol><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></pre></td><td class="code"><pre><span class="line">RSAAuthentication yes</span><br><span class="line">PubkeyAuthentication yes</span><br><span class="line">AuthorizedKeysFile .ssh/authorized_keys</span><br></pre></td></tr></table></figure><ol start="6"><li><p>在群晖<code>控制面板 -&gt; 终端机和 SNMP</code> 关闭再开启 <code>SSH</code>，即可免密登录群晖。</p></li><li><p>如果设置成功后为了安全起见，建议在保存好 <code>密钥对（id_rsa 和 id_rsa.pub）</code>的情况下，关闭密码登录群晖 <code>SSH</code>。<br>修改 <code>sshd_config</code> 配置文件:</p></li></ol><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 vim /etc/ssh/sshd_config</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">PasswordAuthentication no</span><br></pre></td></tr></table></figure><p>然后步骤 6 重启群晖 <code>SSH</code> 即可关闭密码登录群晖 <code>SSH</code>。</p>]]></content>
    
    <summary type="html">
    
      &lt;ol&gt;
&lt;li&gt;首先 &lt;code&gt;SSH&lt;/code&gt; 登录群晖，检测群晖当前用户主目录下是否有 &lt;code&gt;.ssh&lt;/code&gt; 文件夹，如果没有使用下列命令创建：&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;mkdir&lt;/span&gt; ~/.ssh&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;vim ~/.ssh/authorized_keys&lt;/code&gt; 将自己的 &lt;code&gt;SSH&lt;/code&gt; 公钥粘贴进去，按 &lt;code&gt;ESC&lt;/code&gt; 输入 &lt;code&gt;wq&lt;/code&gt; 并回车保存。&lt;/li&gt;
&lt;/ol&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="群晖" scheme="https://woodenrobot.me/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>群晖 Moments 人物识别一键修复解决方案</title>
    <link href="https://woodenrobot.me/2021/02/11/easy-moments/"/>
    <id>https://woodenrobot.me/2021/02/11/easy-moments/</id>
    <published>2021-02-11T13:45:32.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><p>首先 SSH 登录群晖并进入 root 用户下：</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">sudo -i</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">sh -c &quot;$(wget -O- https://raw.githubusercontent.com/Wooden-Robot/documents-for-fun/master/Synology/moments_ai_patch.sh)&quot; -p install</span><br></pre></td></tr></table></figure><p>安装后重新启动 <code>Moments</code> 并<code>全部重建索引</code>。</p><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">sh -c &quot;$(wget -O- https://raw.githubusercontent.com/Wooden-Robot/documents-for-fun/master/Synology/moments_ai_patch.sh)&quot; -p uninstall</span><br></pre></td></tr></table></figure><p><strong>如果无法使用上面的 github 脚本！请关注公众号：<code>WoodenRobot</code> 回复 <code>moments</code>  获取国内安装和卸载命令！</strong></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;解决方案&quot;&gt;&lt;a href=&quot;#解决方案&quot; class=&quot;headerlink&quot; title=&quot;解决方案&quot;&gt;&lt;/a&gt;解决方案&lt;/h1&gt;&lt;p&gt;首先 SSH 登录群晖并进入 root 用户下：&lt;/p&gt;
&lt;figure class=&quot;highlight plaintex
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="群晖" scheme="https://woodenrobot.me/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>获取联通友华 PT952G 光猫超级密码和宽带拨号账号密码</title>
    <link href="https://woodenrobot.me/2020/11/14/pt925g/"/>
    <id>https://woodenrobot.me/2020/11/14/pt925g/</id>
    <published>2020-11-14T05:19:44.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="方法一"><a href="#方法一" class="headerlink" title="方法一"></a>方法一</h2><p>直接在浏览器地址栏里输入：<a href="http://192.168.1.1/backupsettings.conf">http://192.168.1.1/backupsettings.conf</a><br>，按下回车键后，将会把这个配置文件下载下来，这个配置文件就是光猫的配置文件，里面包含了管理员密码。</p><p>下载完毕后，将其打开，然后在里面搜索 <code>AdminPassword</code>，找到后，位于 <code>&lt;AdminPassword&gt;</code> 和 <code>&lt;/AdminPassword&gt;</code> 之间的部分就是管理员密码了，一般为CUAdmin+八位数字，比如CUAdmin12345678。一串乱码的话 <code>Q1VBZG1pbjEyMzQ1Njc4</code> 是管理员密码使用了 Base64 编码了，可以这个网站解码：<a href="https://tool.oschina.net/encrypt?type=3">在线加密解密</a></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></pre></td><td class="code"><pre><span class="line">&lt;WANPPPConnection instance=&quot;1&quot;&gt;</span><br><span class="line">          &lt;Enable&gt;TRUE&lt;/Enable&gt;</span><br><span class="line">          ...</span><br><span class="line">          &lt;Username&gt;XXXXXX&lt;/Username&gt;</span><br><span class="line">          &lt;Password&gt;YYYYYY&lt;/Password&gt;</span><br></pre></td></tr></table></figure><p>先找到 <code>WANPPPConnection instance=&quot;1&quot;</code>下面几行后有一个 <code>Username</code> 和 <code>Password</code> 里面的 <code>XXXXXX</code> 和 <code>YYYYYY</code> 分别就为宽带拨号的账号和密码，密码可能也是 Base64 编码的，使用<a href="https://tool.oschina.net/encrypt?type=3">在线加密解密</a>解码就可以了。</p><h1 id="方法二"><a href="#方法二" class="headerlink" title="方法二"></a>方法二</h1><p>进入路由器更改普通用户密码页面：<a href="http://192.168.1.1/main.html?page=4">http://192.168.1.1/main.html?page=4</a></p><p>F12 打开开发者工具在网页源码里搜 <code>pwdAdmin</code> 后面就是管理员密码。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;方法一&quot;&gt;&lt;a href=&quot;#方法一&quot; class=&quot;headerlink&quot; title=&quot;方法一&quot;&gt;&lt;/a&gt;方法一&lt;/h2&gt;&lt;p&gt;直接在浏览器地址栏里输入：&lt;a href=&quot;http://192.168.1.1/backupsettings.conf&quot;&gt;http
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="网络" scheme="https://woodenrobot.me/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>去除 iTerm2 下 Tmux 复制模式警告</title>
    <link href="https://woodenrobot.me/2020/07/07/iterm2-tmux/"/>
    <id>https://woodenrobot.me/2020/07/07/iterm2-tmux/</id>
    <published>2020-07-07T07:51:45.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h2><ul><li>iTerm2  3.3.11</li><li>Tmux  3.1b</li></ul><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p><img src="/images/iterm2-tmux.png" alt="tmux.png"></p><p>如图所示，在 iTerm2 中使用 Tmux 的复制模式就会报错。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><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">$ defaults write com.googlecode.iterm2 NoSyncNeverAskAboutMouseReportingFrustration -bool <span class="literal">true</span></span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://gitlab.com/gnachman/iterm2/-/issues/8905">Annoying banner: “Looks like you’re trying to copy to the pasteboard…” (#8905) · Issues · George Nachman / iterm2 · GitLab</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;环境&quot;&gt;&lt;a href=&quot;#环境&quot; class=&quot;headerlink&quot; title=&quot;环境&quot;&gt;&lt;/a&gt;环境&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;iTerm2  3.3.11&lt;/li&gt;
&lt;li&gt;Tmux  3.1b&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;问题&quot;&gt;&lt;a hre
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Tmux" scheme="https://woodenrobot.me/tags/Tmux/"/>
    
  </entry>
  
  <entry>
    <title>优化 oh my zsh 启动速度</title>
    <link href="https://woodenrobot.me/2020/06/23/ohmyzsh/"/>
    <id>https://woodenrobot.me/2020/06/23/ohmyzsh/</id>
    <published>2020-06-23T10:31:25.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>终于忍受不了越来越慢的 zsh 启动速度，优化了一下 zsh 的启动速度。</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><h3 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h3><ul><li>iTerm2 3.3.11</li><li>zsh 5.8</li><li>oh my zsh(好像没版本号 commit 5ffc0d036c587741fd25092e7809dad2b00b3677)<span id="more"></span></li></ul><h3 id="初始配置"><a href="#初始配置" class="headerlink" title="初始配置"></a>初始配置</h3><p><code>.zshrc</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="built_in">export</span> ZSH=/Users/woodenrobot/.oh-my-zsh</span><br><span class="line"></span><br><span class="line">ZSH_THEME=<span class="string">&quot;ys&quot;</span></span><br><span class="line"></span><br><span class="line">plugins=(</span><br><span class="line">    sudo</span><br><span class="line">    z</span><br><span class="line">    zsh_reload</span><br><span class="line">    safe-paste</span><br><span class="line">    extract</span><br><span class="line">    history-substring-search</span><br><span class="line">    colored-man-pages</span><br><span class="line">    git</span><br><span class="line">    <span class="built_in">history</span></span><br><span class="line">    tmux</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 第三方插件</span></span><br><span class="line">    zsh-autosuggestions</span><br><span class="line">    zsh-syntax-highlighting</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="built_in">source</span> <span class="variable">$ZSH</span>/oh-my-zsh.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># User configuration</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Bind key</span></span><br><span class="line"><span class="built_in">bindkey</span> <span class="string">&#x27;^P&#x27;</span> history-substring-search-up</span><br><span class="line"><span class="built_in">bindkey</span> <span class="string">&#x27;^N&#x27;</span> history-substring-search-down</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 语言配置</span></span><br><span class="line"><span class="built_in">export</span> LC_ALL=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"><span class="built_in">export</span> LC_CTYPE=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"><span class="built_in">export</span> LC_ALL=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"><span class="built_in">export</span> LANG=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># pyenv</span></span><br><span class="line"><span class="built_in">export</span> PYENV_ROOT=<span class="string">&quot;<span class="variable">$HOME</span>/.pyenv&quot;</span></span><br><span class="line"><span class="built_in">export</span> PATH=<span class="string">&quot;<span class="variable">$PYENV_ROOT</span>/bin:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># nvm</span></span><br><span class="line"><span class="built_in">export</span> NVM_DIR=<span class="string">&quot;<span class="subst">$([ -z <span class="string">&quot;<span class="variable">$&#123;XDG_CONFIG_HOME-&#125;</span>&quot;</span> ] &amp;&amp; printf %s <span class="string">&quot;<span class="variable">$&#123;HOME&#125;</span>/.nvm&quot;</span> || printf %s <span class="string">&quot;<span class="variable">$&#123;XDG_CONFIG_HOME&#125;</span>/nvm&quot;</span>)</span>&quot;</span></span><br><span class="line">[ -s <span class="string">&quot;<span class="variable">$NVM_DIR</span>/nvm.sh&quot;</span> ] &amp;&amp; \. <span class="string">&quot;<span class="variable">$NVM_DIR</span>/nvm.sh&quot;</span> <span class="comment"># This loads nvm</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># virtualenvwrapper</span></span><br><span class="line"><span class="built_in">export</span> WORKON_HOME=<span class="variable">$HOME</span>/.virtualenvs</span><br><span class="line"><span class="built_in">export</span> PROJECT_HOME=<span class="variable">$HOME</span>/Devel</span><br><span class="line"><span class="built_in">source</span> /usr/local/bin/virtualenvwrapper.sh</span><br></pre></td></tr></table></figure><p>其中 <code>zsh-syntax-highlighting</code> 和 <code>zsh-autosuggestions</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></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-autosuggestions <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-autosuggestions</span><br><span class="line"></span><br><span class="line">$ git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-syntax-highlighting.git <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-syntax-highlighting</span><br></pre></td></tr></table></figure><p>通过配置文件可以看出，我安装了几个 oh my zsh 自带的插件以及 <code>pyenv</code>、<code>nvm</code>、<code>virtualenvwrapper</code>，安装插件以及其他程序的启动脚本是需要耗费时间的，测试一下 此时 <code>zsh</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">$ \time zsh -i -c <span class="built_in">exit</span></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><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">1.54 real         0.78 user         0.73 sys</span><br><span class="line"></span><br><span class="line">1.55 real         0.80 user         0.72 sys</span><br><span class="line"></span><br><span class="line">1.83 real         0.83 user         0.80 sys</span><br></pre></td></tr></table></figure><h2 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h2><p>为了提高启动速度，先把 <code>pyenv</code>、<code>nvm</code>和<code>virtualenvwrapper</code>程序改为 <code>lazyload</code>，也就是不在 zsh 启动时就启动只在使用它们的时候启动。</p><h3 id="nvm-优化"><a href="#nvm-优化" class="headerlink" title="nvm 优化"></a>nvm 优化</h3><p>使用 <code>zsh-nvm</code> 插件实现 <code>nvm</code> 的 lazyload。</p><ul><li><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">$ git <span class="built_in">clone</span> https://github.com/lukechilds/zsh-nvm ~/.oh-my-zsh/custom/plugins/zsh-nvm</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>然后在 <code>.zshrc</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></pre></td><td class="code"><pre><span class="line">plugins=(</span><br><span class="line">  ...</span><br><span class="line">  zsh-nvm</span><br><span class="line">)</span><br></pre></td></tr></table></figure></li><li><p>删除 <code>.zshrc</code> 原有的 <code>nvm</code> 启动部分并开启 <code>zsh-nvm</code> 的 lazyload，加入：</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"><span class="comment"># zsh-nvm lazy load</span></span><br><span class="line"><span class="built_in">export</span> NVM_LAZY_LOAD=<span class="literal">true</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="pyenv-优化"><a href="#pyenv-优化" class="headerlink" title="pyenv 优化"></a>pyenv 优化</h3><ul><li><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">$ git <span class="built_in">clone</span> https://github.com/davidparsson/zsh-pyenv-lazy.git ~/.oh-my-zsh/custom/plugins/pyenv-lazy</span><br></pre></td></tr></table></figure></li><li><p>然后在 <code>.zshrc</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></pre></td><td class="code"><pre><span class="line">plugins=(</span><br><span class="line">  ...</span><br><span class="line">  pyenv-lazy</span><br><span class="line">)</span><br></pre></td></tr></table></figure></li><li><p>删除 <code>.zshrc</code> 原有的 <code>pyenv</code> 启动部分</p></li></ul><h3 id="virtualenvwrapper-优化"><a href="#virtualenvwrapper-优化" class="headerlink" title="virtualenvwrapper 优化"></a>virtualenvwrapper 优化</h3><ul><li>删除原有的 <code>virtualenvwrapper</code> 启动部分；</li><li>使用下列 lazyload:<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"><span class="built_in">export</span> WORKON_HOME=<span class="variable">$HOME</span>/.virtualenvs</span><br><span class="line"><span class="built_in">export</span> PROJECT_HOME=<span class="variable">$HOME</span>/Devel</span><br><span class="line"><span class="built_in">export</span> VIRTUALENVWRAPPER_SCRIPT=/usr/local/bin/virtualenvwrapper.sh</span><br><span class="line"><span class="built_in">source</span> /usr/local/bin/virtualenvwrapper_lazy.sh</span><br></pre></td></tr></table></figure></li></ul><h3 id="完整配置"><a href="#完整配置" class="headerlink" title="完整配置"></a>完整配置</h3><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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="built_in">export</span> ZSH=/Users/woodenrobot/.oh-my-zsh</span><br><span class="line"></span><br><span class="line">ZSH_THEME=<span class="string">&quot;ys&quot;</span></span><br><span class="line"></span><br><span class="line">plugins=(</span><br><span class="line">    sudo</span><br><span class="line">    z</span><br><span class="line">    zsh_reload</span><br><span class="line">    safe-paste</span><br><span class="line">    extract</span><br><span class="line">    history-substring-search</span><br><span class="line">    colored-man-pages</span><br><span class="line">    git</span><br><span class="line">    <span class="built_in">history</span></span><br><span class="line">    tmux</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 第三方插件</span></span><br><span class="line">    zsh-autosuggestions</span><br><span class="line">    zsh-syntax-highlighting</span><br><span class="line">    pyenv-lazy</span><br><span class="line">    zsh-nvm</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="built_in">source</span> <span class="variable">$ZSH</span>/oh-my-zsh.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># User configuration</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Bind key</span></span><br><span class="line"><span class="built_in">bindkey</span> <span class="string">&#x27;^P&#x27;</span> history-substring-search-up</span><br><span class="line"><span class="built_in">bindkey</span> <span class="string">&#x27;^N&#x27;</span> history-substring-search-down</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 语言配置</span></span><br><span class="line"><span class="built_in">export</span> LC_ALL=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"><span class="built_in">export</span> LC_CTYPE=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"><span class="built_in">export</span> LC_ALL=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"><span class="built_in">export</span> LANG=<span class="string">&quot;en_US.UTF-8&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># virtualenvwrapper lazy load</span></span><br><span class="line"><span class="built_in">export</span> WORKON_HOME=<span class="variable">$HOME</span>/.virtualenvs</span><br><span class="line"><span class="built_in">export</span> PROJECT_HOME=<span class="variable">$HOME</span>/Devel</span><br><span class="line"><span class="built_in">export</span> VIRTUALENVWRAPPER_SCRIPT=/usr/local/bin/virtualenvwrapper.sh</span><br><span class="line"><span class="built_in">source</span> /usr/local/bin/virtualenvwrapper_lazy.sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># zsh-nvm lazy load</span></span><br><span class="line"><span class="built_in">export</span> NVM_LAZY_LOAD=<span class="literal">true</span></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><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">0.30 real         0.16 user         0.11 sys</span><br><span class="line"></span><br><span class="line">0.26 real         0.14 user         0.10 sys</span><br><span class="line"></span><br><span class="line">0.28 real         0.15 user         0.11 sys</span><br></pre></td></tr></table></figure><p><strong><em>效果感人！！！</em></strong><br><strong><em>效果感人！！！</em></strong><br><strong><em>效果感人！！！</em></strong></p><h2 id="歪门邪道"><a href="#歪门邪道" class="headerlink" title="歪门邪道"></a>歪门邪道</h2><p>zsh 启动速度上来了但是新建一个 iTerm2 tab 好像还是有点慢做不到秒开。网上看到一个不知道什么原理的设置：</p><p>进入 iTerm2 的偏好设置里，在 Profiles 里编辑你的配置，在配置右侧的 General 选项卡里，Command 里选择为 Command，然后里边写入 /usr/bin/login -pfq xxx 其中 xxx 是你的用户名。</p><p>按照这个配置好，感觉是有那么一点点更快了（更慢了）？总之在我这里改动带来的体验并没有那么大，大家有兴趣可以去试试。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://github.com/lukechilds/zsh-nvm">GitHub - lukechilds/zsh-nvm: Zsh plugin for installing, updating and loading nvm</a></li><li><a href="https://github.com/davidparsson/zsh-pyenv-lazy.git">GitHub - davidparsson/zsh-pyenv-lazy: A zsh plugin for lazy loading of pyenv</a></li><li><a href="https://virtualenvwrapper.readthedocs.io/en/latest/install.html#lazy-loading">Installation — virtualenvwrapper 5.0.1.dev2 documentation</a></li><li><a href="https://blog.jonslow.com/iterm2-launch-accelerate/">iTerm 2、Terminal 启动加速</a></li><li><a href="https://www.logcg.com/archives/2376.html">让 iTrem 2 + zsh 启动不再等待！ | 落格博客</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;终于忍受不了越来越慢的 zsh 启动速度，优化了一下 zsh 的启动速度。&lt;/p&gt;
&lt;h2 id=&quot;正文&quot;&gt;&lt;a href=&quot;#正文&quot; class=&quot;headerlink&quot; title=&quot;正文&quot;&gt;&lt;/a&gt;正文&lt;/h2&gt;&lt;h3 id=&quot;环境&quot;&gt;&lt;a href=&quot;#环境&quot; class=&quot;headerlink&quot; title=&quot;环境&quot;&gt;&lt;/a&gt;环境&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;iTerm2 3.3.11&lt;/li&gt;
&lt;li&gt;zsh 5.8&lt;/li&gt;
&lt;li&gt;oh my zsh(好像没版本号 commit 5ffc0d036c587741fd25092e7809dad2b00b3677)
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Oh My Zsh" scheme="https://woodenrobot.me/tags/Oh-My-Zsh/"/>
    
  </entry>
  
  <entry>
    <title>优酷路由宝 YK-L1c 和 YK-L1 刷入 Breed 不死和 hiboy Padavan 固件</title>
    <link href="https://woodenrobot.me/2020/04/04/YK-L1c/"/>
    <id>https://woodenrobot.me/2020/04/04/YK-L1c/</id>
    <published>2020-04-04T06:46:40.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>之前买了一个优酷路由宝 YK-L1c 在此记录一下刷机过程。最近又买了一个 YK-L1 发现刷机固件两个是通用的。</p><span id="more"></span><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><ul><li>待刷优酷路由宝 YK-L1c 或者 YK-L1 一台  </li><li>刷机breed：<a href="https://github.com/Wooden-Robot/documents-for-fun/blob/master/%E4%BC%98%E9%85%B7%E8%B7%AF%E7%94%B1%E5%AE%9DYK-L1c/breed-mt7620-youku-yk1.bin">breed-mt7620-youku-yk1.bin</a></li><li>降级并获取root权限固件：<a href="https://github.com/Wooden-Robot/documents-for-fun/blob/master/%E4%BC%98%E9%85%B7%E8%B7%AF%E7%94%B1%E5%AE%9DYK-L1c/Youku-L1c-0818-root.bin">Youku-L1c-0818-root.bin</a></li><li>老毛子 Pandavan 固件：<a href="https://github.com/Wooden-Robot/documents-for-fun/blob/master/%E4%BC%98%E9%85%B7%E8%B7%AF%E7%94%B1%E5%AE%9DYK-L1c/RT-N14U-GPIO-1-youku1-128M_3.4.3.9-099.trx">RT-N14U-GPIO-1-youku1-128M_3.4.3.9-099.trx</a></li></ul><p>最新版的老毛子固件和 Breed 大家可以自行去下载，上述版本也可以使用，没有任何问题。</p><ul><li>老毛子固件下载地址：<a href="http://opt.cn2qq.com/padavan/">http://opt.cn2qq.com/padavan/</a></li><li>Breed 下载地址：<a href="https://breed.hackpascal.net/">https://breed.hackpascal.net/</a></li></ul><h2 id="降级并获得root权限"><a href="#降级并获得root权限" class="headerlink" title="降级并获得root权限"></a>降级并获得root权限</h2><p>注意，用网线将PC连接路由宝，登录管理WEB页面 <a href="http://wifi.youku.com.或者默认的/">http://wifi.youku.com。或者默认的</a> 192.168.11.1</p><p>步骤：更多设置——系统升级——手动升级——上传固件，上传的固件即为 <code>Youku-L1c-0818-root.bin</code>。 升级过程中不要中断路由宝与PC的连接，也不要中途断电。升级完成后路由器会重启，重启过程中耐心等待。</p><h2 id="刷入-Breed"><a href="#刷入-Breed" class="headerlink" title="刷入 Breed"></a>刷入 Breed</h2><p>刷完第一步的固件后，实际上也已经 root 了，用户名：root 密码：admin </p><p><em>Ps: 密码不对的话就在路由器开机状态下长按路由器后面的 <code>Reset</code> 键重置路由器就可以了。</em></p><p>首先上传 breed 固件到路由器：</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">scp breed-mt7620-youku-yk1.bin root@192.168.11.1:/tmp</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">ssh root@192.168.11.1</span><br></pre></td></tr></table></figure><p>在路由器 shell 中输入下列命令输入 breed:</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">mtd write /tmp/breed-mt7620-youku-yk1.bin Bootloader</span><br></pre></td></tr></table></figure><h2 id="使用-Breed-刷入老毛子"><a href="#使用-Breed-刷入老毛子" class="headerlink" title="使用 Breed 刷入老毛子"></a>使用 Breed 刷入老毛子</h2><p>路由断电后按住 reset 键，后通电，指示灯全闪后等 2 秒松开 reset 键，进入 Breed 管理界面 <a href="http://192.168.1.1/">http://192.168.1.1</a> ，选择固件更新，使用 <code>RT-N14U-GPIO-1-youku1-128M_3.4.3.9-099.trx</code> 刷入老毛子。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://www.znds.com/tv-488200-1-1.html">优酷路由宝降级图文教程【附固件】_智能路由器论坛_ZNDS</a></li><li><a href="https://www.znds.com/tv-488210-1-1.html">优酷路由宝第三方刷机图文教程+工具+固件_智能路由器论坛_ZNDS</a></li><li><a href="https://www.sqyai.com/post-562.html">优酷土豆路由宝 YK-L1c 刷机过程 - 生活杂谈 - 情醉中国风</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;之前买了一个优酷路由宝 YK-L1c 在此记录一下刷机过程。最近又买了一个 YK-L1 发现刷机固件两个是通用的。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Padavan" scheme="https://woodenrobot.me/tags/Padavan/"/>
    
  </entry>
  
  <entry>
    <title>Zoom 直播分享 Awesome pipeline 录像和资料下载</title>
    <link href="https://woodenrobot.me/2020/04/02/awesome-pipeline/"/>
    <id>https://woodenrobot.me/2020/04/02/awesome-pipeline/</id>
    <published>2020-04-02T06:37:13.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>上周末在赖信涛的邀请下，分享了 iredis 中 shell pipeline 实现的相关故事。第一次分享有点小紧张，提前好几天一直在准备 PPT 生怕自己讲不好，还好有惊无险完成了整个分享。这次分享主要由 iredis 的作者赖信涛和两位开发者 rhchen 和我通过 Zoom 的形式参与，内容辛姐帮我们录像上传到了 YouTube 和 B 站。</p><span id="more"></span><h2 id="视频"><a href="#视频" class="headerlink" title="视频"></a>视频</h2><div style="position: relative; width: 100%; height: 0; padding-bottom: 75%;">    <iframe src="//player.bilibili.com/player.html?aid=795073416&bvid=BV1BC4y147P6&cid=172197296&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;">     </iframe></div><ul><li>YouTube 视频链接： <a href="https://youtu.be/EFwGK3Lvr04">https://youtu.be/EFwGK3Lvr04</a></li><li>B 站视频链接： <a href="https://www.bilibili.com/video/BV1BC4y147P6/">https://www.bilibili.com/video/BV1BC4y147P6/</a></li><li>IRedis项目：<a href="https://github.com/laixintao/iredis">https://github.com/laixintao/iredis</a></li><li>项目主页：<a href="https://www.iredis.io/">https://www.iredis.io/</a></li></ul><p>分享中提到的内容，以及分享的 slide，在下文 github 上可以找到。</p><h2 id="三个演讲的大纲和PPT"><a href="#三个演讲的大纲和PPT" class="headerlink" title="三个演讲的大纲和PPT"></a>三个演讲的大纲和PPT</h2><h3 id="赖信涛：awesome-commandline"><a href="#赖信涛：awesome-commandline" class="headerlink" title="赖信涛：awesome commandline"></a>赖信涛：awesome commandline</h3><p>slide: <a href="https://github.com/laixintao/myslides/tree/master/awesome-commandline">https://github.com/laixintao/myslides/tree/master/awesome-commandline</a></p><ol><li>为什么命令行更加高效（演示demo，vim+tmux+shell命令可以互相配合）</li><li>大部分时间我们都在和 Vim，终端相处，但是日常的开发工作还离不开另一个角色：REPL</li><li>所以我们需要更好的命令行的REPL：mycli/pgcli/iredis</li><li>如何开发这样的工具？</li><li>开发理念？</li><li>What next？</li></ol><h3 id="WoodenRobot-awesome-pipeline-Ps-也就是我啦"><a href="#WoodenRobot-awesome-pipeline-Ps-也就是我啦" class="headerlink" title="WoodenRobot: awesome-pipeline(Ps: 也就是我啦)"></a>WoodenRobot: awesome-pipeline(Ps: 也就是我啦)</h3><p>slide: <a href="https://github.com/Wooden-Robot/myslides/">https://github.com/Wooden-Robot/myslides/</a></p><p>协助开发 iredis pipeline feature 的始末<br>shell 的 pipeline原理，常用操作<br>python 的 subprocess 接口<br>如何参与开源项目</p><h3 id="rhchen-awesome-BNF"><a href="#rhchen-awesome-BNF" class="headerlink" title="rhchen:awesome-BNF"></a>rhchen:awesome-BNF</h3><p>slide: <a href="https://github.com/laixintao/myslides/tree/master/bnf-by-rhchen">https://github.com/laixintao/myslides/tree/master/bnf-by-rhchen</a></p><p>什么是 BNF，为什么要用它，能用它做什么？(编译原理的实践应用)<br>针对 iRedis 的解析需求, 如何设计 BNF? (处理”未输入完全”的字符串)<br>使用 SLY 解析输入和 iRedis 当前的解析方式的不同点比较</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;上周末在赖信涛的邀请下，分享了 iredis 中 shell pipeline 实现的相关故事。第一次分享有点小紧张，提前好几天一直在准备 PPT 生怕自己讲不好，还好有惊无险完成了整个分享。这次分享主要由 iredis 的作者赖信涛和两位开发者 rhchen 和我通过 Zoom 的形式参与，内容辛姐帮我们录像上传到了 YouTube 和 B 站。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="分享" scheme="https://woodenrobot.me/tags/%E5%88%86%E4%BA%AB/"/>
    
  </entry>
  
  <entry>
    <title>Python 使用 Redis 实现分布式锁</title>
    <link href="https://woodenrobot.me/2020/03/09/redis-lock/"/>
    <id>https://woodenrobot.me/2020/03/09/redis-lock/</id>
    <published>2020-03-09T09:30:20.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>随着互联网技术的不断发展，用户量的不断增加，越来越多的业务场景需要用到分布式系统。而在分布式系统中访问共享资源就需要一种互斥机制，来防止彼此之间的互相干扰，以保证一致性，这个时候就需要使用分布式锁。</p><span id="more"></span><h2 id="业界常用解决方案"><a href="#业界常用解决方案" class="headerlink" title="业界常用解决方案"></a>业界常用解决方案</h2><ul><li>基于 MySql 等数据库的唯一索引</li><li>基于 ZooKeeper 临时有序节点</li><li>基于 Redis 的 <code>NX EX</code> 参数</li></ul><p>本文主要讲解基于 Redis 实现的分布式锁</p><h2 id="分布式锁的特点"><a href="#分布式锁的特点" class="headerlink" title="分布式锁的特点"></a>分布式锁的特点</h2><ul><li>互斥性。在任意时刻，只有一个客户端能持有锁</li><li>锁超时。即使一个客户端持有锁的期间崩溃而没有主动释放锁，也需要保证后续其他客户端能够加锁成功</li><li>加锁和解锁必须是同一个客户端，客户端自己不能把别人加的锁给释放了。</li></ul><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><h3 id="版本一"><a href="#版本一" class="headerlink" title="版本一"></a>版本一</h3><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># @DateTime : 2020/3/9 15:36</span></span><br><span class="line"><span class="comment"># @Author   : woodenrobot</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> uuid</span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> redis <span class="keyword">import</span> WatchError</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">acquire_lock_with_timeout</span>(<span class="params">conn, lock_name, acquire_timeout=<span class="number">3</span>, lock_timeout=<span class="number">2</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    基于 Redis 实现的分布式锁</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    :param conn: Redis 连接</span></span><br><span class="line"><span class="string">    :param lock_name: 锁的名称</span></span><br><span class="line"><span class="string">    :param acquire_timeout: 获取锁的超时时间，默认 3 秒</span></span><br><span class="line"><span class="string">    :param lock_timeout: 锁的超时时间，默认 2 秒</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    identifier = <span class="built_in">str</span>(uuid.uuid4())</span><br><span class="line">    lockname = <span class="string">f&#x27;lock:<span class="subst">&#123;lock_name&#125;</span>&#x27;</span></span><br><span class="line">    lock_timeout = <span class="built_in">int</span>(math.ceil(lock_timeout))</span><br><span class="line"></span><br><span class="line">    end = time.time() + acquire_timeout</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> time.time() &lt; end:</span><br><span class="line">        <span class="comment"># 如果不存在这个锁则加锁并设置过期时间，避免死锁</span></span><br><span class="line">        <span class="keyword">if</span> conn.setnx(lockname, identifier):</span><br><span class="line">            conn.expire(lockname, lock_timeout)</span><br><span class="line">            <span class="keyword">return</span> identifier</span><br><span class="line">        <span class="comment"># 如果存在锁，且这个锁没有过期时间则为其设置过期时间，避免死锁</span></span><br><span class="line">        <span class="keyword">elif</span> conn.ttl(lockname) == -<span class="number">1</span>:</span><br><span class="line">            conn.expire(lockname, lock_timeout)</span><br><span class="line"></span><br><span class="line">        time.sleep(<span class="number">0.001</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">release_lock</span>(<span class="params">conn, lockname, identifier</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    释放锁</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    :param conn: Redis 连接</span></span><br><span class="line"><span class="string">    :param lockname: 锁的名称</span></span><br><span class="line"><span class="string">    :param identifier: 锁的标识</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment"># python 中 redis 事务是通过pipeline的封装实现的</span></span><br><span class="line">    <span class="keyword">with</span> conn.pipeline() <span class="keyword">as</span> pipe:</span><br><span class="line">        lockname = <span class="string">&#x27;lock:&#x27;</span> + lockname</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">                <span class="comment"># watch 锁, multi 后如果该 key 被其他客户端改变, 事务操作会抛出 WatchError 异常</span></span><br><span class="line">                pipe.watch(lockname)</span><br><span class="line">                iden = pipe.get(lockname)</span><br><span class="line">                <span class="keyword">if</span> iden <span class="keyword">and</span> iden.decode(<span class="string">&#x27;utf-8&#x27;</span>) == identifier:</span><br><span class="line">                    <span class="comment"># 事务开始</span></span><br><span class="line">                    pipe.multi()</span><br><span class="line">                    pipe.delete(lockname)</span><br><span class="line">                    pipe.execute()</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">                pipe.unwatch()</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">except</span> WatchError:</span><br><span class="line">                <span class="keyword">pass</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure><h4 id="加锁过程"><a href="#加锁过程" class="headerlink" title="加锁过程"></a>加锁过程</h4><ol><li>首先需要为锁生成一个唯一的标识，这里使用 uuid;</li><li>然后使用 <code>setnx</code> 设置锁，如果该锁名之前不存在其他客户端的锁则加锁成功，接着设置锁的过期时间防止发生死锁并返回锁的唯一标示；</li><li>如果设置失败先判断一下锁名所在的锁是否有过期时间，因为 <code>setnx</code> 和 <code>expire</code> 两个命令执行不是原子性的，可能会出现加锁成功但是设置超时时间失败出现死锁。如果不存在就给锁重新设置过期时间，存在就不断循环知道加锁时间超时加锁失败。</li></ol><h4 id="解锁过程"><a href="#解锁过程" class="headerlink" title="解锁过程"></a>解锁过程</h4><ol><li>首先整个解锁操作需要在一个 Redis 的事务中进行；</li><li>使用 <code>watch</code> 监听锁，防止解锁时出现删除其他人的锁；</li><li>查询锁名所在的标识是否与本次解锁的标识相同；</li><li>如果相同则在事务中删除这个锁，如果删除过程中锁自动失效过期又被其他客户端拿到，因为设置了 <code>watch</code> 就会删除失败，这样就不会出现删除了其他客户端锁的情况。</li></ol><h3 id="版本二"><a href="#版本二" class="headerlink" title="版本二"></a>版本二</h3><p>如果你使用的 Redis 版本大于等于 <code>2.6.12</code> 版本，加锁的过程就可以进行简化。因为这个版本以后的 <code>Redis set</code> 操作支持 <code>EX</code> 和 <code>NX</code> 参数，是一个原子性的操作。</p><ul><li>EX seconds ： 将键的过期时间设置为 seconds 秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value 。</li><li>NX ： 只在键不存在时， 才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value 。</li></ul><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># @DateTime : 2020/3/9 15:36</span></span><br><span class="line"><span class="comment"># @Author   : woodenrobot</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> uuid</span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> redis <span class="keyword">import</span> WatchError</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">acquire_lock_with_timeout</span>(<span class="params">conn, lock_name, acquire_timeout=<span class="number">3</span>, lock_timeout=<span class="number">2</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    基于 Redis 实现的分布式锁</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    :param conn: Redis 连接</span></span><br><span class="line"><span class="string">    :param lock_name: 锁的名称</span></span><br><span class="line"><span class="string">    :param acquire_timeout: 获取锁的超时时间，默认 3 秒</span></span><br><span class="line"><span class="string">    :param lock_timeout: 锁的超时时间，默认 2 秒</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    identifier = <span class="built_in">str</span>(uuid.uuid4())</span><br><span class="line">    lockname = <span class="string">f&#x27;lock:<span class="subst">&#123;lock_name&#125;</span>&#x27;</span></span><br><span class="line">    lock_timeout = <span class="built_in">int</span>(math.ceil(lock_timeout))</span><br><span class="line"></span><br><span class="line">    end = time.time() + acquire_timeout</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> time.time() &lt; end:</span><br><span class="line">        <span class="comment"># 如果不存在这个锁则加锁并设置过期时间，避免死锁</span></span><br><span class="line">        <span class="keyword">if</span> conn.<span class="built_in">set</span>(lockname, identifier, ex=lock_timeout, nx=<span class="literal">True</span>):</span><br><span class="line">            <span class="keyword">return</span> identifier</span><br><span class="line"></span><br><span class="line">        time.sleep(<span class="number">0.001</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">release_lock</span>(<span class="params">conn, lockname, identifier</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    释放锁</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    :param conn: Redis 连接</span></span><br><span class="line"><span class="string">    :param lockname: 锁的名称</span></span><br><span class="line"><span class="string">    :param identifier: 锁的标识</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment"># python中redis事务是通过pipeline的封装实现的</span></span><br><span class="line">    <span class="keyword">with</span> conn.pipeline() <span class="keyword">as</span> pipe:</span><br><span class="line">        lockname = <span class="string">&#x27;lock:&#x27;</span> + lockname</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">                <span class="comment"># watch 锁, multi 后如果该 key 被其他客户端改变, 事务操作会抛出 WatchError 异常</span></span><br><span class="line">                pipe.watch(lockname)</span><br><span class="line">                iden = pipe.get(lockname)</span><br><span class="line">                <span class="keyword">if</span> iden <span class="keyword">and</span> iden.decode(<span class="string">&#x27;utf-8&#x27;</span>) == identifier:</span><br><span class="line">                    <span class="comment"># 事务开始</span></span><br><span class="line">                    pipe.multi()</span><br><span class="line">                    pipe.delete(lockname)</span><br><span class="line">                    pipe.execute()</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">                pipe.unwatch()</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">except</span> WatchError:</span><br><span class="line">                <span class="keyword">pass</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></table></figure><h3 id="版本三"><a href="#版本三" class="headerlink" title="版本三"></a>版本三</h3><p>可能你也发现了解锁过程在代码逻辑上稍微有点复杂，别着急，我们可以使用 <code>Lua</code> 脚本实现原子性操作从而简化解锁过程。</p><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># @DateTime : 2020/3/9 15:36</span></span><br><span class="line"><span class="comment"># @Author   : woodenrobot</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> uuid</span><br><span class="line"><span class="keyword">import</span> math</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">acquire_lock_with_timeout</span>(<span class="params">conn, lock_name, acquire_timeout=<span class="number">3</span>, lock_timeout=<span class="number">2</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    基于 Redis 实现的分布式锁</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    :param conn: Redis 连接</span></span><br><span class="line"><span class="string">    :param lock_name: 锁的名称</span></span><br><span class="line"><span class="string">    :param acquire_timeout: 获取锁的超时时间，默认 3 秒</span></span><br><span class="line"><span class="string">    :param lock_timeout: 锁的超时时间，默认 2 秒</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    identifier = <span class="built_in">str</span>(uuid.uuid4())</span><br><span class="line">    lockname = <span class="string">f&#x27;lock:<span class="subst">&#123;lock_name&#125;</span>&#x27;</span></span><br><span class="line">    lock_timeout = <span class="built_in">int</span>(math.ceil(lock_timeout))</span><br><span class="line"></span><br><span class="line">    end = time.time() + acquire_timeout</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> time.time() &lt; end:</span><br><span class="line">        <span class="comment"># 如果不存在这个锁则加锁并设置过期时间，避免死锁</span></span><br><span class="line">        <span class="keyword">if</span> conn.<span class="built_in">set</span>(lockname, identifier, ex=lock_timeout, nx=<span class="literal">True</span>):</span><br><span class="line">            <span class="keyword">return</span> identifier</span><br><span class="line"></span><br><span class="line">        time.sleep(<span class="number">0.001</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">release_lock</span>(<span class="params">conn, lock_name, identifier</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    释放锁</span></span><br><span class="line"><span class="string">    </span></span><br><span class="line"><span class="string">    :param conn: Redis 连接</span></span><br><span class="line"><span class="string">    :param lockname: 锁的名称</span></span><br><span class="line"><span class="string">    :param identifier: 锁的标识</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    unlock_script = <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    if redis.call(&quot;get&quot;,KEYS[1]) == ARGV[1] then</span></span><br><span class="line"><span class="string">        return redis.call(&quot;del&quot;,KEYS[1])</span></span><br><span class="line"><span class="string">    else</span></span><br><span class="line"><span class="string">        return 0</span></span><br><span class="line"><span class="string">    end</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    lockname = <span class="string">f&#x27;lock:<span class="subst">&#123;lock_name&#125;</span>&#x27;</span></span><br><span class="line">    unlock = conn.register_script(unlock_script)</span><br><span class="line">    result = unlock(keys=[lockname], args=[identifier])</span><br><span class="line">    <span class="keyword">if</span> result:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><p>截至到目前，我们已经有较好的方法获取锁和释放锁。基于Redis单实例，假设这个单实例总是可用，这种方法已经足够安全。但是如果 Redis 主节点挂了就会出现一些问题，比如主节点加锁后没有同步到从节点，从节点升为主节点，就会出现锁的丢失。如果你想要使用更加安全的 Redis 分布式锁实现可以参考一下 <a href="https://redis.io/topics/distlock/#the-redlock-algorithm">Redlock</a> 的实现。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li>《Redis 实战》中分布式锁的实现</li><li><a href="https://github.com/andymccurdy/redis-py/issues/387">SETNX with TTL · Issue #387 · andymccurdy/redis-py · GitHub</a></li><li> <a href="https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247486884&idx=1&sn=ecd79b960efe4638e5da5db0f6fe9def&chksm=fba6e5a7ccd16cb10ef3cb6e96df79d5c152b597763d9726ff6b855b2680d31f9c9ecf1325e6&mpshare=1&scene=1&srcid=&sharer_sharetime=1578449410632&sharer_shareid=457a571080dcfbb66a2b355b8b090c0a#rd">万字长文！不为人知的分布式锁实现，全都在这里了！</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;随着互联网技术的不断发展，用户量的不断增加，越来越多的业务场景需要用到分布式系统。而在分布式系统中访问共享资源就需要一种互斥机制，来防止彼此之间的互相干扰，以保证一致性，这个时候就需要使用分布式锁。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Python" scheme="https://woodenrobot.me/tags/Python/"/>
    
      <category term="Redis" scheme="https://woodenrobot.me/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>[转]Python 相对导入与绝对导入</title>
    <link href="https://woodenrobot.me/2020/01/19/python-import/"/>
    <id>https://woodenrobot.me/2020/01/19/python-import/</id>
    <published>2020-01-19T08:06:33.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<p>Python 相对导入与绝对导入，这两个概念是相对于包内导入而言的。包内导入即是包内的模块导入包内部的模块。</p><span id="more"></span><h2 id="Python-import-的搜索路径"><a href="#Python-import-的搜索路径" class="headerlink" title="Python import 的搜索路径"></a>Python import 的搜索路径</h2><ul><li>在当前目录下搜索该模块</li><li>在环境变量 PYTHONPATH 中指定的路径列表中依次搜索</li><li>在 Python 安装路径的 lib 库中搜索</li></ul><h2 id="Python-import-的步骤"><a href="#Python-import-的步骤" class="headerlink" title="Python import 的步骤"></a>Python import 的步骤</h2><p>python 所有加载的模块信息都存放在 <code>sys.modules</code> 结构中，当 import 一个模块时，会按如下步骤来进行</p><ul><li>如果是 <code>import A</code>，检查 sys.modules 中是否已经有 A，如果有则不加载，如果没有则为 A 创建 module 对象，并加载 A</li><li>如果是 <code>from A import B</code>，先为 A 创建 module 对象，再解析A，从中寻找B并填充到 A 的 <code>__dict__</code> 中</li></ul><h2 id="相对导入与绝对导入"><a href="#相对导入与绝对导入" class="headerlink" title="相对导入与绝对导入"></a>相对导入与绝对导入</h2><p>绝对导入的格式为 <code>import A.B</code> 或 <code>from A import B</code>，相对导入格式为 <code>from . import B</code> 或 <code>from ..A import B</code>，<code>.</code> 代表当前模块，<code>..</code> 代表上层模块，<code>...</code> 代表上上层模块，依次类推。</p><p>相对导入可以避免硬编码带来的维护问题，例如我们改了某一顶层包的名，那么其子包所有的导入就都不能用了。但是 <strong>存在相对导入语句的模块，不能直接运行</strong>，否则会有异常：</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">ImportError: attempted relative import with no known parent package</span><br></pre></td></tr></table></figure><p>这是什么原因呢？我们需要先来了解下导入模块时的一些规则：</p><p>在没有明确指定包结构的情况下，Python 是根据 <code>__name__</code> 来决定一个模块在包中的结构的，如果是 <code>__main__</code> 则它本身是顶层模块，没有包结构，如果是 <code>A.B.C</code> 结构，那么顶层模块是 A。基本上遵循这样的原则：</p><ul><li>如果是绝对导入，一个模块只能导入自身的子模块或和它的顶层模块同级别的模块及其子模块</li><li>如果是相对导入，一个模块必须有包结构且只能导入它的顶层模块内部的模块</li></ul><p>如果一个模块被直接运行，则它自己为顶层模块，不存在层次结构，所以找不到其他的相对路径。</p><p>Python2.x 缺省为相对路径导入，Python3.x 缺省为绝对路径导入。绝对导入可以避免导入子包覆盖掉标准库模块（由于名字相同，发生冲突）。如果在 Python2.x 中要默认使用绝对导入，可以在文件开头加入如下语句：</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">from __future__ import absolute_import</span><br></pre></td></tr></table></figure><h2 id="from-future-import-absolute-import"><a href="#from-future-import-absolute-import" class="headerlink" title="from future import absolute_import"></a>from <strong>future</strong> import absolute_import</h2><p>这句 import 并不是指将所有的导入视为绝对导入，而是指禁用 <code>implicit relative import</code>（隐式相对导入）, 但并不会禁掉 <code>explicit relative import</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><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">thing</span><br><span class="line">├── books</span><br><span class="line">│   ├── adventure.py</span><br><span class="line">│   ├── history.py</span><br><span class="line">│   ├── horror.py</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   └── lovestory.py</span><br><span class="line">├── furniture</span><br><span class="line">│   ├── armchair.py</span><br><span class="line">│   ├── bench.py</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── screen.py</span><br><span class="line">│   └── stool.py</span><br><span class="line">└── __init__.py</span><br></pre></td></tr></table></figure><p>那么如果在 stool 中引用 bench，则有如下几种方式:</p><figure class="highlight python"><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">import</span> bench                 <span class="comment"># 此为 implicit relative import</span></span><br><span class="line"><span class="keyword">from</span> . <span class="keyword">import</span> bench          <span class="comment"># 此为 explicit relative import</span></span><br><span class="line"><span class="keyword">from</span> furniture <span class="keyword">import</span> bench  <span class="comment"># 此为 absolute import</span></span><br></pre></td></tr></table></figure><p>隐式相对就是没有告诉解释器相对于谁，但默认相对与当前模块；而显示相对则明确告诉解释器相对于谁来导入。以上导入方式的第三种，才是官方推荐的，第一种是官方强烈不推荐的，<strong>Python3 中已经被废弃</strong>，这种方式只能用于导入 path 中的模块。</p><p>相对与绝对仅针对包内导入而言<br>最后再次强调，相对导入与绝对导入仅针对于包内导入而言，要不然本文所讨论的内容就没有意义。所谓的包，就是包含 <code>__init__.py</code> 文件的目录，该文件在包导入时会被首先执行，该文件可以为空，也可以在其中加入任意合法的 Python 代码。</p><p>相对导入可以避免硬编码，对于包的维护是友好的。绝对导入可以避免与标准库命名的冲突，实际上也不推荐自定义模块与标准库命令相同。</p><p>前面提到含有相对导入的模块不能被直接运行，实际上含有绝对导入的模块也不能被直接运行，会出现 ImportError：</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">ImportError: No module named XXX</span><br></pre></td></tr></table></figure><p>这与绝对导入时是一样的原因。要运行包中包含绝对导入和相对导入的模块，可以用 <code>python -m A.B.C</code> 告诉解释器模块的层次结构。</p><p>有人可能会问：假如有两个模块 a.py 和 b.py 放在同一个目录下，为什么能在 b.py 中 <code>import a</code> 呢？</p><p>这是因为这两个文件所在的目录不是一个包，那么每一个 python 文件都是一个独立的、可以直接被其他模块导入的模块，就像你导入标准库一样，它们不存在相对导入和绝对导入的问题。<strong>相对导入与绝对导入仅用于包内部</strong>。</p><blockquote><p>本文转载于：<a href="http://blog.konghy.cn/2016/07/21/python-import-relative-and-absolute/">Python 相对导入与绝对导入</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Python 相对导入与绝对导入，这两个概念是相对于包内导入而言的。包内导入即是包内的模块导入包内部的模块。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Python" scheme="https://woodenrobot.me/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>Mac 使用 Brew 升级 openssl@1.1 问题</title>
    <link href="https://woodenrobot.me/2020/01/11/brew-openssl-python/"/>
    <id>https://woodenrobot.me/2020/01/11/brew-openssl-python/</id>
    <published>2020-01-11T09:28:53.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>使用 <code>brew</code>升级 <code>openssl</code> 后打开 <code>zsh shell</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><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></pre></td><td class="code"><pre><span class="line">ERROR:root:code <span class="keyword">for</span> <span class="built_in">hash</span> md5 was not found.</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 147, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    globals()[__func_name] = __get_hash(__func_name)</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 97, <span class="keyword">in</span> __get_builtin_constructor</span><br><span class="line">    raise ValueError(<span class="string">&#x27;unsupported hash type &#x27;</span> + name)</span><br><span class="line">ValueError: unsupported <span class="built_in">hash</span> <span class="built_in">type</span> md5</span><br><span class="line">ERROR:root:code <span class="keyword">for</span> <span class="built_in">hash</span> sha1 was not found.</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 147, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    globals()[__func_name] = __get_hash(__func_name)</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 97, <span class="keyword">in</span> __get_builtin_constructor</span><br><span class="line">    raise ValueError(<span class="string">&#x27;unsupported hash type &#x27;</span> + name)</span><br><span class="line">ValueError: unsupported <span class="built_in">hash</span> <span class="built_in">type</span> sha1</span><br><span class="line">ERROR:root:code <span class="keyword">for</span> <span class="built_in">hash</span> sha224 was not found.</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 147, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    globals()[__func_name] = __get_hash(__func_name)</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 97, <span class="keyword">in</span> __get_builtin_constructor</span><br><span class="line">    raise ValueError(<span class="string">&#x27;unsupported hash type &#x27;</span> + name)</span><br><span class="line">ValueError: unsupported <span class="built_in">hash</span> <span class="built_in">type</span> sha224</span><br><span class="line">ERROR:root:code <span class="keyword">for</span> <span class="built_in">hash</span> sha256 was not found.</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 147, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    globals()[__func_name] = __get_hash(__func_name)</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 97, <span class="keyword">in</span> __get_builtin_constructor</span><br><span class="line">    raise ValueError(<span class="string">&#x27;unsupported hash type &#x27;</span> + name)</span><br><span class="line">ValueError: unsupported <span class="built_in">hash</span> <span class="built_in">type</span> sha256</span><br><span class="line">ERROR:root:code <span class="keyword">for</span> <span class="built_in">hash</span> sha384 was not found.</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 147, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    globals()[__func_name] = __get_hash(__func_name)</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 97, <span class="keyword">in</span> __get_builtin_constructor</span><br><span class="line">    raise ValueError(<span class="string">&#x27;unsupported hash type &#x27;</span> + name)</span><br><span class="line">ValueError: unsupported <span class="built_in">hash</span> <span class="built_in">type</span> sha384</span><br><span class="line">ERROR:root:code <span class="keyword">for</span> <span class="built_in">hash</span> sha512 was not found.</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 147, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    globals()[__func_name] = __get_hash(__func_name)</span><br><span class="line">  File <span class="string">&quot;/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hashlib.py&quot;</span>, line 97, <span class="keyword">in</span> __get_builtin_constructor</span><br><span class="line">    raise ValueError(<span class="string">&#x27;unsupported hash type &#x27;</span> + name)</span><br><span class="line">ValueError: unsupported <span class="built_in">hash</span> <span class="built_in">type</span> sha512</span><br></pre></td></tr></table></figure><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><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">$ brew reinstall python@2</span><br></pre></td></tr></table></figure><p>如果其他版本的 Python 也出现无法使用的情况请重新安装一次。例如使用 <code>pyenv</code> 安装的 python 需要使用下列命令重新安装：</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">pyenv install -f 3.6.3</span><br></pre></td></tr></table></figure><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://github.com/wting/autojump/issues/540">hash md5 error on Mac · Issue #540 · wting/autojump · GitHub</a></li><li><a href="https://stackoverflow.com/questions/59269208/errorrootcode-for-hash-md5-was-not-found-not-able-to-use-any-hg-mercurial-co">python - ERROR:root:code for hash md5 was not found - not able to use any hg mercurial commands - Stack Overflow</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h2&gt;&lt;p&gt;使用 &lt;code&gt;brew&lt;/code&gt;升级 &lt;code&gt;openssl&lt;/code&gt; 后打开 &lt;code&gt;zsh shell&lt;/code&gt;
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Mac" scheme="https://woodenrobot.me/tags/Mac/"/>
    
  </entry>
  
  <entry>
    <title>Linux通过源码编译安装 Python3.8</title>
    <link href="https://woodenrobot.me/2020/01/09/linux-install-python/"/>
    <id>https://woodenrobot.me/2020/01/09/linux-install-python/</id>
    <published>2020-01-09T06:22:37.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h2><ul><li>Ubuntu/Debian:<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></pre></td><td class="code"><pre><span class="line">$ sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \</span><br><span class="line">libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \</span><br><span class="line">xz-utils tk-dev libffi-dev liblzma-dev python-openssl git</span><br></pre></td></tr></table></figure></li><li>Fedora/CentOS/RHEL(aws ec2):<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 yum install @development zlib-devel bzip2 bzip2-devel readline-devel sqlite \</span><br><span class="line">sqlite-devel openssl-devel xz xz-devel libffi-devel findutils</span><br></pre></td></tr></table></figure></li></ul><h2 id="下载-Python3-8-源码包"><a href="#下载-Python3-8-源码包" class="headerlink" title="下载 Python3.8 源码包"></a>下载 Python3.8 源码包</h2><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">$ curl -O https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tar.xz</span><br></pre></td></tr></table></figure><h2 id="编译安装-Python3-8"><a href="#编译安装-Python3-8" class="headerlink" title="编译安装 Python3.8"></a>编译安装 Python3.8</h2><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">$ tar -Jxvf Python-3.8.1.tar.xz</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><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ ./configure --prefix=/usr/local/python3 --enable-optimizations</span><br><span class="line">$ make</span><br><span class="line">$ make install</span><br></pre></td></tr></table></figure><p><strong><em>注：<code>--enable-optimizations</code> 配置项用于提高 Python 安装后的性能，使用会导致编译速度稍慢</em></strong></p><h2 id="添加软连接"><a href="#添加软连接" class="headerlink" title="添加软连接"></a>添加软连接</h2><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">$ <span class="built_in">ln</span> -s /usr/local/python3/bin/python3.8 /usr/bin/python3</span><br><span class="line">$ <span class="built_in">ln</span> -s /usr/local/python3/bin/pip3.8 /usr/bin/pip3</span><br></pre></td></tr></table></figure><p>命令行输入 <code>python3 -V</code> 查看是否安装成功。</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><ol><li>本文是以 Python3.8.1 为例，如果安装其他版本可参考更改文中的 Python 下载地址，相关命令也需按需更改。</li><li>文中使用的命令如果出现权限不够请自行加上 <code>sudo</code>。</li></ol><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://github.com/pyenv/pyenv/wiki/Common-build-problems">Common build problems · pyenv/pyenv Wiki · GitHub</a></li><li><a href="https://segmentfault.com/a/1190000015628625">CentOS 7 下 安装 Python3.7 - 个人文章 - SegmentFault 思否</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;安装依赖&quot;&gt;&lt;a href=&quot;#安装依赖&quot; class=&quot;headerlink&quot; title=&quot;安装依赖&quot;&gt;&lt;/a&gt;安装依赖&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Ubuntu/Debian:&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Linux" scheme="https://woodenrobot.me/tags/Linux/"/>
    
      <category term="Python" scheme="https://woodenrobot.me/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>家庭网络漫游指南</title>
    <link href="https://woodenrobot.me/2019/09/27/home-internet/"/>
    <id>https://woodenrobot.me/2019/09/27/home-internet/</id>
    <published>2019-09-27T08:51:16.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="光猫"><a href="#光猫" class="headerlink" title="光猫"></a>光猫</h2><blockquote><p>光网络终端（英语：Optical Network Terminals，俗称光猫或光 modem），是指通过光纤介质进行传输，将光信号调制解调为其他协议信号的网络设备。光猫设备作为大型局域网、城域网和广域网的中继传输设备。</p></blockquote><p>光猫的主要功能为信号转换，它的后端接口除了连接电脑，还可以连接电视或电话。 </p><h2 id="路由器"><a href="#路由器" class="headerlink" title="路由器"></a>路由器</h2><blockquote><p>路由器（英语：Router，又称路径器）是一种电讯网络设备，提供路由与转送两种重要机制，可以决定数据包从来源端到目的端所经过的路由路径（host 到 host 之间的传输路径），这个过程称为路由；将路由器输入端的数据包移送至适当的路由器输出端（在路由器内部进行），这称为转送。路由工作在 OSI 模型的第三层——即网络层，例如网际协议（IP）。</p></blockquote><span id="more"></span><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><p>路由器就是连接两个以上个别网络的设备。</p><p>由于位于两个或更多个网络的交汇处，从而可在它们之间传递分组（一种数据的组织形式）。路由器与交换机在概念上有一定重叠但也有不同：交换机泛指工作于任何网络层次的数据中继设备（尽管多指网桥），而路由器则更专注于网络层。</p><p>路由器与交换机的差别，路由器是属于 OSI 第三层的产品，交換机是 OSI 第二层的产品。第二层的产品功能在于，将网络上各个计算机的 MAC 地址记在 MAC 地址表中，当局域网中的计算机要经过交換机去交换传递数据时，就查询交換机上的 MAC 地址表中的信息，将数据包发送给指定的计算机，而不会像第一层的产品（如集线器）每台在网络中的计算机都发送。而路由器除了有交換机的功能外，更拥有路由表作为发送数据包时的依据，在有多种选择的路径中选择最佳的路径。此外，并可以连接两个以上不同网段的网络，而交換机只能连接两个。并具有 IP 分享的功能，如：区分哪些数据包是要发送至 WAN。路由表存储了（向前往）某一网络的最佳路径，该路径的“路由度量值”以及下一个（跳路由器）。参考条目路由获得这个过程的详细描述。</p><h3 id="WAN-口和-LAN口"><a href="#WAN-口和-LAN口" class="headerlink" title="WAN 口和 LAN口"></a>WAN 口和 LAN口</h3><p>路由器通常包含一个 WAN 口多个 LAN 口(PS: 有些特殊的路由器包含多个 WAN 口)。</p><blockquote><p>广域网（英语：Wide Area Network，缩写为 WAN），又称广域网、外网、公网。是连接不同地区局域网或城域网计算机通信的远程网。通常跨接很大的物理范围，所覆盖的范围从几十公里到几千公里，它能连接多个地区、城市和国家，或横跨几个洲并能提供远距离通信，形成国际性的远程网络。广域网并不等同于互联网。</p></blockquote><blockquote><p>局域网（Local Area Network，简称 LAN）是连接住宅、学校、实验室、大学校园或办公大楼等有限区域内计算机的计算机网络。相比之下，广域网（WAN）不仅覆盖较大的地理距离，而且还通常涉及固接专线和对于互联网的链接。 相比来说互联网则更为广阔，是连接全球商业和个人计算机的系统。</p></blockquote><p>通常情况下，宽带安装好以后使用网线连接光猫和路由器 WAN 口，其他设备连接路由器 LAN 口或者无线网络即可浏览互联网。</p><h3 id="路由器的六种无线模式"><a href="#路由器的六种无线模式" class="headerlink" title="路由器的六种无线模式"></a>路由器的六种无线模式</h3><h4 id="无线访问节点-AP「Access-Point」"><a href="#无线访问节点-AP「Access-Point」" class="headerlink" title="无线访问节点 (AP「Access Point」)"></a>无线访问节点 (AP「Access Point」)</h4><p>该模式下路由器的无线网卡就像一个”无线 HUB”，负责建立无线路由器和电脑之间的数据链路（相当于无形的网线）。正常情况下，家用的无线路由器的无线连接都默认工作在此模式下。</p><h4 id="客户端-Client"><a href="#客户端-Client" class="headerlink" title="客户端 (Client)"></a>客户端 (Client)</h4><p>像笔记本电脑上的无线网卡那样工作，仅连接其它的无线网络，而不发射自己的无线网络信号。对于无线路由器来说，这种模式相当于启用了一个无线的 WAN 口，且下面的电脑只能通过有线方式接到此设备。内部的LAN口组成的局域网和连接上的无线网段处于相同的 IP 地址段。内部的 DHCP 请求也会被转发到主无线网络上。</p><h4 id="客户端网桥-Client-Bridge"><a href="#客户端网桥-Client-Bridge" class="headerlink" title="客户端网桥 (Client Bridge)"></a>客户端网桥 (Client Bridge)</h4><p>和“客户端”模式一样，相当于启用了一个无线的 WAN 口，且下面的电脑只能通过有线方式接到此设备。不过，该模式下无线路由器仍然提供 DHCP 及 NAT 功能，内部 LAN 口组成的单独 IP 地址段局域网，通过无线路由器上自己的网关，连上外部主网络。</p><h4 id="Adhoc"><a href="#Adhoc" class="headerlink" title="Adhoc"></a>Adhoc</h4><p>Adhoc 有个形象的比喻，就像是将两台电脑之间直接找根网线连起来，只不过在这里这根网线是个无线的。最常见的使用adhoc连接的设备多数是一些手持游戏机。该模式在无线路由器上使用的场合比较罕见。</p><h4 id="中继-Repeater"><a href="#中继-Repeater" class="headerlink" title="中继 (Repeater)"></a>中继 (Repeater)</h4><p>顾名思义，中继就是一边是接受信号，一边又发射自己的无线信号。在这种模式下无线路由器以无线网卡客户身份接入主 AP，然后再以新增虚拟界面(Virtual Interfaces)来为客户端提供无线接入。该模式的最大意义在于可以解决无线信号受到距离或者障碍物的影响不能传输到更远的问题。<br>接入到该无线路由器上的电脑终端，是和主无线网网络处在相同的 IP 地址段。内部的 DHCP 请求，也会被转发到主无线网络上。</p><h4 id="中继桥接-Repeater-Bridge"><a href="#中继桥接-Repeater-Bridge" class="headerlink" title="中继桥接 (Repeater Bridge)"></a>中继桥接 (Repeater Bridge)</h4><p>和”中继”模式一样，可以解决无线信号受到距离或者障碍物的影响不能传输到更远的问题。不过，这种模式下无线路由器仍然提供 DHCP 及 NAT 功能，即所有的内部 LAN 口以及无线客户接入组成的是一个单独的局域网网段。</p><h2 id="DHCP"><a href="#DHCP" class="headerlink" title="DHCP"></a>DHCP</h2><blockquote><p>动态主机设置协议（英语：Dynamic Host Configuration Protocol，缩写：DHCP）是一个用于局域网的网络协议，位于 OSI 模型的应用层，使用 UDP 协议工作，主要有两个用途：</p><ul><li>用于内部网或网络服务供应商自动分配IP地址给用户</li><li>用于内部网管理员作为对所有计算机作中央管理的手段</li></ul></blockquote><h3 id="解析-DHCP-的工作方式"><a href="#解析-DHCP-的工作方式" class="headerlink" title="解析 DHCP 的工作方式"></a>解析 DHCP 的工作方式</h3><p>当一台机器新加入一个网络的时候，肯定一脸懵，啥情况都不知道，只知道自己的 MAC 地址。怎么办？先吼一句，我来啦，有人吗？这时候的沟通基本靠“吼”。这一步，我们称为 DHCP Discover。</p><p>新来的机器使用 IP 地址 0.0.0.0 发送了一个广播包，目的 IP 地址为 255.255.255.255。广播包封装了 UDP，UDP 封装了 BOOTP。其实 DHCP 是 BOOTP 的增强版，但是如果你去抓包的话，很可能看到的名称还是 BOOTP 协议。</p><p>在这个广播包里面，新人大声喊：我是新来的（Boot request），我的 MAC 地址是这个，我还没有 IP，谁能给租给我个 IP 地址！</p><p>格式就像这样：<br><img src="/images/home-internet1.png" alt="48ff99d2.png"></p><p>如果一个网络管理员在网络里面配置了 DHCP Server 的话，他就相当于这些 IP 的管理员。他立刻能知道来了一个“新人”。这个时候，我们可以体会 MAC 地址唯一的重要性了。当一台机器带着自己的 MAC 地址加入一个网络的时候，MAC 是它唯一的身份，如果连这个都重复了，就没办法配置了。</p><p>只有 MAC 唯一，IP 管理员才能知道这是一个新人，需要租给它一个 IP 地址，这个过程我们称为 DHCP Offer。同时，DHCP Server 为此客户保留为它提供的 IP 地址，从而不会为其他 DHCP 客户分配此 IP 地址。</p><p>DHCP Offer 的格式就像这样，里面有给新人分配的地址。</p><p><img src="/images/home-internet2.png" alt="646daabe.png"><br>DHCP Server 仍然使用广播地址作为目的地址，因为，此时请求分配 IP 的新人还没有自己的 IP。DHCP Server 回复说，我分配了一个可用的 IP 给你，你看如何？除此之外，服务器还发送了子网掩码、网关和 IP 地址租用期等信息。</p><p>新来的机器很开心，它的“吼”得到了回复，并且有人愿意租给它一个 IP 地址了，这意味着它可以在网络上立足了。当然更令人开心的是，如果有多个 DHCP Server，这台新机器会收到多个 IP 地址，简直受宠若惊。</p><p>它会选择其中一个 DHCP Offer，一般是最先到达的那个，并且会向网络发送一个 DHCP Request 广播数据包，包中包含客户端的 MAC 地址、接受的租约中的 IP 地址、提供此租约的 DHCP 服务器地址等，并告诉所有 DHCP Server 它将接受哪一台服务器提供的 IP 地址，告诉其他 DHCP 服务器，谢谢你们的接纳，并请求撤销它们提供的 IP 地址，以便提供给下一个 IP 租用请求者。<br><img src="/images/home-internet3.png" alt="d17afbba.png"><br>此时，由于还没有得到 DHCP Server 的最后确认，客户端仍然使用 0.0.0.0 为源 IP 地址、255.255.255.255 为目标地址进行广播。在 BOOTP 里面，接受某个 DHCP Server 的分配的 IP。</p><p>当 DHCP Server 接收到客户机的 DHCP request 之后，会广播返回给客户机一个 DHCP ACK 消息包，表明已经接受客户机的选择，并将这一 IP 地址的合法租用信息和其他的配置信息都放入该广播包，发给客户机，欢迎它加入网络大家庭。</p><p><img src="/images/home-internet4.png" alt="6dc28c84.png"></p><p>最终租约达成的时候，还是需要广播一下，让大家都知道。</p><h3 id="IP-地址的收回和续租"><a href="#IP-地址的收回和续租" class="headerlink" title="IP 地址的收回和续租"></a>IP 地址的收回和续租</h3><p>既然是租房子，就是有租期的。租期到了，管理员就要将 IP 收回。</p><p>如果不用的话，收回就收回了。就像你租房子一样，如果还要续租的话，不能到了时间再续租，而是要提前一段时间给房东说。DHCP 也是这样。</p><p>客户机会在租期过去 50% 的时候，直接向为其提供 IP 地址的 DHCP Server 发送 DHCP request 消息包。客户机接收到该服务器回应的 DHCP ACK 消息包，会根据包中所提供的新的租期以及其他已经更新的 TCP/IP 参数，更新自己的配置。这样，IP 租用更新就完成了。</p><p>好了，一切看起来完美。DHCP 协议大部分人都知道，但是其实里面隐藏着一个细节，很多人可能不会去注意。接下来，我就讲一个有意思的事情：网络管理员不仅能自动分配 IP 地址，还能帮你自动安装操作系统！</p><h2 id="NAT"><a href="#NAT" class="headerlink" title="NAT"></a>NAT</h2><blockquote><p>网络地址转换（英语：Network Address Translation，缩写：NAT；又称网络掩蔽、IP 掩蔽）在计算机网络中是一种在IP数据包通过路由器或防火墙时重写来源 IP 地址或目的 IP 地址的技术。这种技术被普遍使用在有多台主机但只通过一个公有 IP 地址访问因特网的私有网络中。它是一个方便且得到了广泛应用的技术。当然，NAT 也让主机之间的通信变得复杂，导致了通信效率的降低。</p></blockquote><h3 id="NAT-转换规则"><a href="#NAT-转换规则" class="headerlink" title="NAT 转换规则"></a>NAT 转换规则</h3><ul><li><p>对于 TCP/UDP 使用<br>Host ‘s 私有 IPv4 + Port &lt;——&gt; NAT 公网 IPv4 + Port</p></li><li><p>对于ICMP使用<br>Host ‘s 私有 IPv4 + session ID &lt;——&gt; NAT 公网 IPv4 + session ID</p></li></ul><p>规则其实非常好理解，由于 session ID 在 NAT 设备上是独一无二的，所以NAT可以很容易区别局域网内部的不同 host。</p><p>至于其它传输协议，NAT 使用的也是类似 session ID 的转换规则，即使用可以将不同 host 轻易分辨出来的字段做键值（KEY），动态创建映射表项，做双向的地址+ KEY 的转换。</p><h3 id="不同类型的-NAT"><a href="#不同类型的-NAT" class="headerlink" title="不同类型的 NAT"></a>不同类型的 NAT</h3><h4 id="完全圆锥型-NAT（Full-cone-NAT），即一对一（one-to-one）NAT"><a href="#完全圆锥型-NAT（Full-cone-NAT），即一对一（one-to-one）NAT" class="headerlink" title="完全圆锥型 NAT（Full cone NAT），即一对一（one-to-one）NAT"></a>完全圆锥型 NAT（Full cone NAT），即一对一（one-to-one）NAT</h4><ul><li>一旦一个内部地址（iAddr:port）映射到外部地址（eAddr:port），所有发自 iAddr:port 的包都经由 eAddr:port 向外发送。任意外部主机都能通过给 eAddr:port 发包到达 iAddr:port（注：port不需要一样）</li></ul><p><img src="/images/home-internet5.png" alt="3417f32a.png"></p><h4 id="受限圆锥型-NAT（Address-Restricted-cone-NAT）"><a href="#受限圆锥型-NAT（Address-Restricted-cone-NAT）" class="headerlink" title="受限圆锥型 NAT（Address-Restricted cone NAT）"></a>受限圆锥型 NAT（Address-Restricted cone NAT）</h4><ul><li>内部客户端必须首先发送数据包到对方（IP=X.X.X.X），然后才能接收来自 X.X.X.X 的数据包。在限制方面，唯一的要求是数据包是来自 X.X.X.X。</li><li>内部地址（iAddr:port1）映射到外部地址（eAddr:port2），所有发自 iAddr:port1 的包都经由 eAddr:port2 向外发送。外部主机（hostAddr:any）能通过给 eAddr:port2 发包到达 iAddr:port1。（注：any指外部主机源端口不受限制，但是目的端口必须是 port2。只有外部主机数据包的目的 IP 为 内部客户端的所映射的外部 ip，且目的端口为 port2 时数据包才被放行。）<br><img src="/images/home-internet6.png" alt="077d46e4.png"></li></ul><h4 id="端口受限圆锥型NAT（Port-Restricted-cone-NAT）"><a href="#端口受限圆锥型NAT（Port-Restricted-cone-NAT）" class="headerlink" title="端口受限圆锥型NAT（Port-Restricted cone NAT）"></a>端口受限圆锥型NAT（Port-Restricted cone NAT）</h4><p>类似受限制锥形NAT（Restricted cone NAT），但是还有端口限制。</p><ul><li>一旦一个内部地址（iAddr:port1）映射到外部地址（eAddr:port2），所有发自 iAddr:port1 的包都经由 eAddr:port2 向外发送。</li><li>在受限圆锥型NAT基础上增加了外部主机源端口必须是固定的。<br><img src="/images/home-internet7.png" alt="e59a101f.png"></li></ul><h4 id="对称-NAT（Symmetric-NAT）"><a href="#对称-NAT（Symmetric-NAT）" class="headerlink" title="对称 NAT（Symmetric NAT）"></a>对称 NAT（Symmetric NAT）</h4><ul><li>每一个来自相同内部 IP 与端口，到一个特定目的地地址和端口的请求，都映射到一个独特的外部 IP 地址和端口。</li><li>同一内部 IP 与端口发到不同的目的地和端口的信息包，都使用不同的映射<br>只有曾经收到过内部主机数据的外部主机，才能够把数据包发回</li></ul><p><img src="/images/home-internet8.png" alt="9692d914.png"></p><h2 id="UPnP"><a href="#UPnP" class="headerlink" title="UPnP"></a>UPnP</h2><blockquote><p>通用即插即用（英语：Universal Plug and Play，简称UPnP）是由“通用即插即用论坛”（UPnP™ Forum）推广的一套网络协议。该协议的目标是使家庭网络（数据共享、通信和娱乐）和公司网络中的各种设备能够相互无缝连接，并简化相关网络的实现。UPnP 通过定义和发布基于开放、因特网通讯网协议标准的 UPnP 设备控制协议来实现这一目标。</p></blockquote><p>可以理解为有了 UPnP 软件可以根据需求让路由器进行动态地进行端口映射。而不是你去路由器后台一个个手动设置。</p><h2 id="提升-NAT-类型"><a href="#提升-NAT-类型" class="headerlink" title="提升 NAT 类型"></a>提升 NAT 类型</h2><p>一般来说，我们希望 NAT 层数越少越好。每多一层 NAT 就意味着更加复杂的情况与配置。依旧是典型的网络拓扑：</p><blockquote><p>入户光纤① → 猫② → 路由器③ → 终端设备</p></blockquote><p>我们目标是把 NAT 降到1层（只有③），当然这是目标，但不是必须的。</p><h3 id="拿到公网-IP"><a href="#拿到公网-IP" class="headerlink" title="拿到公网 IP"></a>拿到公网 IP</h3><p>拥有公网 IP 对于 P2P 应用来说绝对是一个基础要求，这可以省掉许多麻烦（使①不发生 NAT）。如何确定自己是不是公网 IP 也很简单。访问这里你可以得到一个 IP 地址，把它与路由器中显示的 WAN 口 IP 进行比较，如果一致那么就是公网 IP 了。</p><p>如果不一致，那么只能联系运营商，自己是没有办法的。一般来说一级运营商（电信/联通）比较容易，而一些二级甚至三级运营商（长城）就没什么希望了。如果拿不到公网 IP，只能期望运营商不要把 NAT 类型限制太死吧。</p><h3 id="光猫改为桥接模式"><a href="#光猫改为桥接模式" class="headerlink" title="光猫改为桥接模式"></a>光猫改为桥接模式</h3><h4 id="区分桥接与路由模式"><a href="#区分桥接与路由模式" class="headerlink" title="区分桥接与路由模式"></a>区分桥接与路由模式</h4><p>现在原来越多的猫“越权管理”，增加了路由功能，也就是说猫和路由器一体化了。每一个路由器可以理解为一层网络，我们不希望层数过多。同时猫的路由功能往往不完善，难以进行高级配置。而桥接模式就是让猫回归本质，只负责信号转换。</p><p>区分路由与桥接模式最方便的办法是：如果你的路由器（电脑）直接连到猫上就可以上网，那么是路由模式；如果路由器需要配置 PPPoE 拨号那么就是桥接模式。</p><h4 id="更改模式"><a href="#更改模式" class="headerlink" title="更改模式"></a>更改模式</h4><p>一般来说更改模式需要猫的超级密码，这个用户是没有的。请联系运营商客服请求修改。改为桥接后②也不会发生 NAT 了。</p><p><strong>警告</strong>：没能力折腾的不建议自己破解改。更改桥接模式之后记得重新配置路由器，输入宽带账号密码才可以正常上网。</p><h3 id="更改路由器设置"><a href="#更改路由器设置" class="headerlink" title="更改路由器设置"></a>更改路由器设置</h3><p>首先要修改 NAT 类型，并不是所有的路由器或者路由器系统都支持这一设置。打开 NAT 并将类型设置为最宽松的 NAT1（Full cone NAT）。</p><p>接着启用 UPnP，绝大部分路由器都支持的，耐心找一找。如果真的不支持那我建议换路由器。</p><p>如果不支持上述的 NAT 类型设置，我们还有一个大招。大部分的路由器都支持 DMZ （非军事化区），DMZ 指定的设备完全暴露在公网上。但是一个网络一般只能够设置1个 DMZ，显然如果设置多个路由器就不知道应该把数据包交给谁了。由于 DMZ 是和 IP 绑定的，而 IP 是动态分配的。所有首先我们将 IP 与 MAC 绑定（不同的路由器设置不同），然后将此 IP 设置为 DMZ 即可。</p><h3 id="更改系统设置"><a href="#更改系统设置" class="headerlink" title="更改系统设置"></a>更改系统设置</h3><p>最后如果你的系统启用了防火墙那么记得将需要的程序添加例外，或者关闭防火墙（不推荐）。最后进行测试，NAT 类型应该可以提升到完全圆锥型NAT（Full cone NAT）。</p><h2 id="无线中继和桥接"><a href="#无线中继和桥接" class="headerlink" title="无线中继和桥接"></a>无线中继和桥接</h2><p>以 hiboy 的老毛子 Padavan 系统路由器为例，介绍一下无线中继和桥接的异同：</p><ul><li>无线中继：选择 AP + AP Client，接 LAN 就要关闭本路由 DHCP，上级路由开启 DHCP，本路由的 IP 不能和上级一样，但要在一个网段里。例子：一个是 192.168.1.1，另一个是 192.168.1.2。</li><li>无线桥接：选择 AP + AP Client，接 WAN 就要开启本路由 DHCP，上级路由开启 DHCP，本路由的 IP 不能和上级同网段。例子：一个是 192.168.1.1，另一个是 192.168.2.1。</li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://zh.wikipedia.org/">维基百科，自由的百科全书</a></li><li><a href="https://www.chenhe.cc/p/41">NAT 科普与类型提升 – 晨鹤小站(｡･∀･)ﾉﾞ</a></li><li><a href="https://right.com.cn/forum/thread-182967-1-1.html">老毛子Padavan固件 无线中继实践心得分享 - Padavan - 恩山无线论坛 - Powered by Discuz!</a></li><li><a href="https://www.zhihu.com/question/20380724">无线网络的中继和桥接有什么区别？ - 知乎</a></li><li><a href="https://www.zhihu.com/question/31332694">NAT转换是怎么工作的？ - 知乎</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;光猫&quot;&gt;&lt;a href=&quot;#光猫&quot; class=&quot;headerlink&quot; title=&quot;光猫&quot;&gt;&lt;/a&gt;光猫&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;光网络终端（英语：Optical Network Terminals，俗称光猫或光 modem），是指通过光纤介质进行传输，将光信号调制解调为其他协议信号的网络设备。光猫设备作为大型局域网、城域网和广域网的中继传输设备。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;光猫的主要功能为信号转换，它的后端接口除了连接电脑，还可以连接电视或电话。 &lt;/p&gt;
&lt;h2 id=&quot;路由器&quot;&gt;&lt;a href=&quot;#路由器&quot; class=&quot;headerlink&quot; title=&quot;路由器&quot;&gt;&lt;/a&gt;路由器&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;路由器（英语：Router，又称路径器）是一种电讯网络设备，提供路由与转送两种重要机制，可以决定数据包从来源端到目的端所经过的路由路径（host 到 host 之间的传输路径），这个过程称为路由；将路由器输入端的数据包移送至适当的路由器输出端（在路由器内部进行），这称为转送。路由工作在 OSI 模型的第三层——即网络层，例如网际协议（IP）。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="网络" scheme="https://woodenrobot.me/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>修复黑群晖 Moments 1.3.3-0700 版本人物识别不能使用问题</title>
    <link href="https://woodenrobot.me/2019/09/26/fix-moments/"/>
    <id>https://woodenrobot.me/2019/09/26/fix-moments/</id>
    <published>2019-09-26T06:51:37.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<ol><li>下载 <a href="https://github.com/Wooden-Robot/documents-for-fun/raw/master/%E7%BE%A4%E6%99%96%20Synology%20DSM/libsynophoto-plugin-detection.so">libsynophoto-plugin-detection.so</a> 文件</li><li>SSH 登录群晖，并登录 root 账号</li><li>备份原文件<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">$ mv /var/packages/SynologyMoments/target/usr/lib/libsynophoto-plugin-detection.so /var/packages/SynologyMoments/target/usr/lib/libsynophoto-plugin-detection.so.bak</span><br></pre></td></tr></table></figure></li><li>将刚刚下载的文件放到 <code>/var/packages/SynologyMoments/target/usr/lib/</code> 路径下</li><li>重启 Moments 并重新创建索引。</li></ol><p><strong><em>注： 修复 bug 的 <code>libsynophoto-plugin-detection.so</code> 文件来源于 1.2.1-0646 版本的 Moments</em></strong></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://xpenology.com/forum/topic/20105-fix-the-problem-of-moments-13x-face-recognition-not-work/?tab=comments#comment-118808">Fix the problem of moments 1.3.x face recognition NOT work - Synology Packages - XPEnology Community</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;ol&gt;
&lt;li&gt;下载 &lt;a href=&quot;https://github.com/Wooden-Robot/documents-for-fun/raw/master/%E7%BE%A4%E6%99%96%20Synology%20DSM/libsynophoto-plugin-de
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="群晖" scheme="https://woodenrobot.me/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>TOTOLINK A3004NS 国行刷入 Breed 不死和 hiboy Padavan 固件</title>
    <link href="https://woodenrobot.me/2019/09/17/A3004NS/"/>
    <id>https://woodenrobot.me/2019/09/17/A3004NS/</id>
    <published>2019-09-17T10:38:26.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>蜗牛星际装了黑群晖后，加上家里的宽带是 200M 带宽，以前的 Newifi mini 百兆路由器已经不能满足需求了，于是闲鱼入手了 TOTOLINK A3004NS 千兆路由器。</p><span id="more"></span><h2 id="安装-breed"><a href="#安装-breed" class="headerlink" title="安装 breed"></a>安装 breed</h2><p><strong>1</strong>. <strong>进路由记录下LAN, WAN, 2.G, 5G 的 MAC 地址备用</strong>(Ps: 最好记录一下，后面可能会出现刷机后 Mac 地址改变，需要用来复原)</p><p><strong>2</strong>.原厂固件下刷入荒野无灯大神 <a href="https://github.com/Wooden-Robot/documents-for-fun/raw/master/A3004NS/STOCK_ROM_UPGRADE_A3004NS_3.4.3.9-099_20170307-0247.trx">A3004NS Padavan 直刷固件</a><br><img src="/images/a3004ns1.jpg" alt="a3004ns.jpg"><br>路由器地址为：<a href="192.168.88.1">192.168.88.1</a><br>登录账号密码为：<code>admin/admin</code></p><p><strong>3</strong>.查看固件分区</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">[A3004NS /opt/home/admin]# cat /proc/mtd</span><br><span class="line">dev:    size   erasesize  name</span><br><span class="line">mtd0: 00020000 00010000 &quot;Bootloader&quot;</span><br><span class="line">mtd1: 00010000 00010000 &quot;Config&quot;</span><br><span class="line">mtd2: 00010000 00010000 &quot;Factory&quot;</span><br><span class="line">mtd3: 00143250 00010000 &quot;Kernel&quot;</span><br><span class="line">mtd4: 00d7cdb0 00010000 &quot;RootFS&quot;</span><br><span class="line">mtd5: 00100000 00010000 &quot;Storage&quot;</span><br><span class="line">mtd6: 00ec0000 00010000 &quot;Firmware_Stub</span><br></pre></td></tr></table></figure><p><strong>4</strong>.重启，备份EEPROM<br>ssh 登录路由器，使用以下命令进行备份：</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">$ dd if=/dev/mtd2 of=/tmp/Factory.bin</span><br></pre></td></tr></table></figure><p><code>Factory</code> 就是 EEPROM。<br>此时可以将其他分区用同样的方法都备份一下，以备不时之需。备份好需要从路由器中将备份数据下载到本地，可以用 <code>ftp、scp</code>等命令行命令，也可以使用 <code>FileZilla</code> 工具。<br>这是我用 <code>dd</code> 命令备份的所有分区信息：<a href="https://github.com/Wooden-Robot/documents-for-fun/tree/master/A3004NS/dd%E5%A4%87%E4%BB%BD">dd 全分区备份</a>。</p><p><strong>5</strong>.下载最新版的 <a href="https://breed.hackpascal.net/breed-mt7621-totolink-a3004ns.bin">A3004NS breed 固件</a>并上传到路由器的 <code>/tmp</code> 路径下。</p><p><strong>6</strong>.通过 ssh 登录路由器执行：</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">$ mtd_write write /tmp/breed-mt7621-totolink-a3004ns.bin Bootloader</span><br></pre></td></tr></table></figure><p><strong>7</strong>.完了将路由器断电。然后按住前面板上的 <code>WPS</code> 按钮通电，多按一会儿。这时路由器网关又变成了 <a href="192.168.1.1">192.168.1.1</a>。使用浏览器打开这个地址就可以进入 breed 后台。<br><img src="/images/a3004ns2.png" alt="ba4544cd.png"></p><p><strong>8</strong>.进入 <code>Breed</code> 后，记得先备份 <code>EEPROM</code>。这个很重要，后面如果刷固件刷出问题需要这个来还原。<br><img src="/images/a3004ns3.png" alt="6f11b6b4.png"></p><h2 id="安装-hiboy-Padavan"><a href="#安装-hiboy-Padavan" class="headerlink" title="安装 hiboy Padavan"></a>安装 hiboy Padavan</h2><p><strong>1</strong>.下载 hiboy Padavan 最新版固件：<a href="http://opt.cn2qq.com/padavan/">http://opt.cn2qq.com/padavan/</a></p><p><strong>2</strong>.进入 Breed，点击“固件更新”-〉“常规固件”：</p><ul><li>勾选<code>固件</code>，并选择我们下载的固件。</li><li>勾选<code>EEPROM</code>，并选择刚刚用 <code>Breed</code> 备份的 <code>EEPROM</code></li><li>闪存布局选择：<code>公版(0x50000)</code></li><li>点击<code>上传</code>按钮进行安装。<br><img src="/images/a3004ns4.png" alt="1eb92b85.png"><br>路由器地址为：<a href="192.168.123.1">192.168.123.1</a><br>登录账号密码为：<code>admin/admin</code></li></ul><p><strong>3</strong>.如果MAC地址改变，<a href="https://www.right.com.cn/forum/thread-321228-1-1.html">进入 Breed 把记下来的 MAC 地址填进去并保存</a>。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://right.com.cn/forum/forum.php?mod=viewthread&tid=321428&page=1">国行A3004NS-Dual刷机实战（附编程器固件）</a></li><li><a href="http://hangge.com/blog/cache/detail_1857.html">Padavan老毛子固件刷机教程（TOTOLINK A3004NS 路由器）</a></li><li><a href="https://www.right.com.cn/forum/thread-321228-1-1.html">极路由4(B70)刷Padavan固件后在breed里修复MAC的方法</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;蜗牛星际装了黑群晖后，加上家里的宽带是 200M 带宽，以前的 Newifi mini 百兆路由器已经不能满足需求了，于是闲鱼入手了 TOTOLINK A3004NS 千兆路由器。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Padavan" scheme="https://woodenrobot.me/tags/Padavan/"/>
    
  </entry>
  
  <entry>
    <title>Nginx 搭建 Google 镜像站</title>
    <link href="https://woodenrobot.me/2019/08/26/google/"/>
    <id>https://woodenrobot.me/2019/08/26/google/</id>
    <published>2019-08-26T06:41:27.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在公司科学上网使用谷歌经常出现很长一段时间访问不了，严重影响工作效率，没办法只能自己搭建一个镜像网站。</p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><h2 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h2><ul><li>机房：搬瓦工</li><li>系统：Ubuntu 18.04</li></ul><h2 id="安装-nginx"><a href="#安装-nginx" class="headerlink" title="安装 nginx"></a>安装 nginx</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">sudo apt install nginx</span></span><br></pre></td></tr></table></figure><h2 id="增加-nginx-配置"><a href="#增加-nginx-配置" class="headerlink" title="增加 nginx 配置"></a>增加 nginx 配置</h2><p>在 <code>/etc/nginx/sites-enabled</code> 文件夹内新增 <code>google.conf</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><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></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">    server_name www.example.com;</span><br><span class="line"> </span><br><span class="line">    location / &#123;</span><br><span class="line">        proxy_pass https://www.google.com/;</span><br><span class="line"> </span><br><span class="line">        proxy_redirect https://www.google.com/ /;</span><br><span class="line">        proxy_cookie_domain google.com www.example.com;</span><br><span class="line"> </span><br><span class="line">        proxy_set_header User-Agent $http_user_agent;</span><br><span class="line">        proxy_set_header Cookie &quot;PREF=ID=047808f19f6de346:U=0f62f33dd8549d11:FF=2:LD=zh-CN:NW=1:TM=1325338577:LM=1332142444:GM=1:SG=2:S=rE0SyJh2W1IQ-Maw&quot;;</span><br><span class="line">        # 这里设置cookie，这里是别人给出的一段，必要时请放上适合你自己的cookie</span><br><span class="line">        # 设置这个可以避免一些情况下的302跳转，如果google服务器返回302 redirect，那么浏览器被跳转到google自己的域名，那就没的玩了</span><br><span class="line"> </span><br><span class="line">        proxy_set_header X-Real-IP $remote_addr;</span><br><span class="line">        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</span><br><span class="line"> </span><br><span class="line">        subs_filter  http://www.google.com http://www.example.com;</span><br><span class="line">        subs_filter  https://www.google.com http://www.example.com;</span><br><span class="line">        # 这里替换网页中的链接，因为我们的镜像站是http的，所以上面顺便把协议也一起替换了</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong><em>注：请手动更改配置中的 <a href="http://www.example.com/">www.example.com</a> 为自己的域名地址</em></strong></p><h2 id="载入配置"><a href="#载入配置" class="headerlink" title="载入配置"></a>载入配置</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">$ nginx -s reload</span><br></pre></td></tr></table></figure><h2 id="添加-DNS-解析记录"><a href="#添加-DNS-解析记录" class="headerlink" title="添加 DNS 解析记录"></a>添加 DNS 解析记录</h2><p>将自己的域名添加一条指向该台服务器 IP 的 DNS 解析记录，访问域名即可实现访问谷歌。</p><h2 id="增加-Basic-Auth-认证"><a href="#增加-Basic-Auth-认证" class="headerlink" title="增加 Basic Auth 认证"></a>增加 Basic Auth 认证</h2><p>如果不想自己的谷歌镜像被别人乱用，可以增加 Basic Auth 来限制其他人使用。</p><h3 id="生成密码"><a href="#生成密码" class="headerlink" title="生成密码"></a>生成密码</h3><p>使用下列命令生成密码：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">printf</span> <span class="string">&quot;your_username:<span class="subst">$(openssl passwd -crypt your_password)</span>\n&quot;</span> &gt;&gt; /etc/nginx/conf.d/passwd</span></span><br></pre></td></tr></table></figure><h3 id="配置-Nginx"><a href="#配置-Nginx" class="headerlink" title="配置 Nginx"></a>配置 Nginx</h3><p>用 vim 修改刚刚的配置文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">vim /etc/nginx/sites-enabled/google.conf</span></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></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"></span><br><span class="line">location / &#123;</span><br><span class="line">        auth_basic &quot;Hello World&quot;;</span><br><span class="line">        auth_basic_user_file conf.d/passwd;</span><br><span class="line"></span><br><span class="line">        proxy_pass https://www.google.com/;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>然后 <code>nginx -s reload</code> 重启 Nginx 生效。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://blog.stdio.io/689">Nginx 搭建 Google 镜像站</a></li><li><a href="https://segmentfault.com/a/1190000004694935">为 Nginx 添加 HTTP 基本认证(HTTP Basic Authentication)</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;在公司科学上网使用谷歌经常出现很长一段时间访问不了，严重影响工作效率，没办法只能自己搭建一个镜像网站。&lt;/p&gt;
&lt;h1 id=&quot;正文&quot;&gt;&lt;a
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Nginx" scheme="https://woodenrobot.me/tags/Nginx/"/>
    
  </entry>
  
  <entry>
    <title>中兴 ZXHN F677V2 光猫改桥接</title>
    <link href="https://woodenrobot.me/2019/08/26/bridge/"/>
    <id>https://woodenrobot.me/2019/08/26/bridge/</id>
    <published>2019-08-26T05:20:08.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>家里用蜗牛星际组装了一个黑群晖，之前用 Zerotier-one 做内网穿透从外网连接到群晖，后来发现 Zertier-one 的 <code>P2P</code> 的成功率很低，速度很慢，看了一下光猫发现宽带有公网 IP，于是更换光猫为桥接模式，使用路由器拨号上网，路由器做端口转发，直接通过公网 IP 连接群晖，在此记录一下 中兴 ZXHN F677V2 光猫改桥接的方法。Ps: 淘宝要几十块，穷就自己动手啦 : )</p><h1 id="教程"><a href="#教程" class="headerlink" title="教程"></a>教程</h1><ol><li><p>连接路由器的网络，访问 <a href="http://192.168.1.1/cu.html">http://192.168.1.1/cu.html</a>；</p></li><li><p>输入超级管理员密码进入超级管理员后台，密码默认为：<code>CUAdmin</code>(Ps: 我是打装宽带的师傅电话要的，不对的话可以问师傅要);</p></li><li><p>在基本配置中选择上行线路配置，记录名为 <code>x_INTERNET_R_VID_y</code> 原本 <code>PPPoE</code> 模式中的配置信息，特别是 <code>VLAN ID</code> 值（Ps: x和y的值每个人可能不一样）</p></li><li><p>添加一个新的 <code>Bridge</code> 模式的连接，除了模式之前其他的配置设置为和之前 <code>PPPoE</code> 模式相同，切记 <code>VLAN ID</code> 值一定要相同；</p></li><li><p>删除原来的两个连接，一个是 <code>x_TR069_R_VID_y</code>，一个是 <code>x_INTERNET_R_VID_y</code>。</p></li><li><p>路由器使用原本的宽带账号密码拨号上网即可；</p></li><li><p>如果拨号后连接不成功，可以关闭光猫几分钟再开启进行尝试。</p></li></ol><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://www.v2ex.com/t/583187">[求助] 联通光猫 ZXHN F477V2 桥接问题</a></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;家里用蜗牛星际组装了一个黑群晖，之前用 Zerotier-one 做内网穿透从外网连接到群晖，后来发现 Zertier-one 的 &lt;cod
      
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="网络" scheme="https://woodenrobot.me/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>[转] Python 的 Buffer 机制</title>
    <link href="https://woodenrobot.me/2019/08/20/python-buffer/"/>
    <id>https://woodenrobot.me/2019/08/20/python-buffer/</id>
    <published>2019-08-20T06:43:56.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文转载于：<a href="https://www.oyohyee.com/post/Note/python_buffer/">Python的Buffer机制</a></p></blockquote><h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><p>Python版本: <code>3.7.1</code>(3.7以上版本)<br>清空 <code>PYTHONUNBUFFERED</code> 环境变量(默认是空的,不过以防万一还是清空下)</p><p>cmd 清空</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">set PYTHONUNBUFFERED=&quot;&quot;</span><br></pre></td></tr></table></figure><p>powershell 清空</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">$env:PYTHONUNBUFFERED=&quot;&quot;</span><br></pre></td></tr></table></figure><p>bash 清空</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">export PYTHONUNBUFFERED=&quot;&quot;</span><br></pre></td></tr></table></figure><p>将下面内容<strong>保存到 <code>test.py</code> 中</strong>,执行下面的语句</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></pre></td><td class="code"><pre><span class="line">import sys</span><br><span class="line">sys.stdout.write(&quot;stdout1 &quot;)</span><br><span class="line">sys.stderr.write(&quot;stderr1 &quot;)</span><br><span class="line">sys.stdout.write(&quot;stdout2 &quot;)</span><br><span class="line">sys.stderr.write(&quot;stderr2 &quot;)</span><br></pre></td></tr></table></figure><p>执行 <code>python test.py</code>, 输出 <code>stderr1 stderr2 stdout1 stdout2</code><br>执行 <code>python -u test.py</code>, 输出 <code>stdout1 stderr1 stdout2 stderr2</code></p><span id="more"></span><h2 id="缓冲区"><a href="#缓冲区" class="headerlink" title="缓冲区"></a>缓冲区</h2><h3 id="现象解释"><a href="#现象解释" class="headerlink" title="现象解释"></a>现象解释</h3><p>对于 <code>stderr</code>,其名称是标准错误输出文件,标准里规定是无缓冲的,每次输出一个字符就直接显示到屏幕上<br>而对于 <code>stdout</code>,其名称是标准输出文件,<strong>UNIX标准里</strong>规定是行缓冲的,遇到换行或者积累到一定的大小一次性输出到屏幕上</p><p>由于默认情况下,缓冲区是开启的。<br>因此 <code>stdout</code> 的输出会先存入到一个 buffer 中,而 <code>stderr</code> 的内容是直接显示的,因此默认输出顺序是 <code>stderr1 stderr2 stdout1 stdout2</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></pre></td><td class="code"><pre><span class="line">import sys</span><br><span class="line">sys.stdout.write(&quot;stdout1\n&quot;)</span><br><span class="line">sys.stderr.write(&quot;stderr1\n&quot;)</span><br><span class="line">sys.stdout.write(&quot;stdout2\n&quot;)</span><br><span class="line">sys.stderr.write(&quot;stderr2\n&quot;)</span><br></pre></td></tr></table></figure><p>由于 <code>stdout</code> 是行缓冲,遇到换行后也是直接输出,因此此时输出内容就是正常顺序</p><p>对于 Python 而言，其规定如下：</p><ul><li>对于 Python2.x: stdout 行缓存, stderr 是无缓冲(和规范相同)</li><li>对于 Python3.x: stdout 和 stderr 都是行缓冲</li></ul><p>关于该改动的讨论: <a href="https://bugs.python.org/issue13597">Improve documentation of stdout/stderr buffering in Python 3.x</a></p><p>而对于 <code>-u</code> 参数:</p><ul><li>对于 3.6 以下版本: 二进制流使用 unbuffered (不使用 buffer ),但是文本流采用 line-buffer(<a href="https://docs.python.org/3.6/using/cmdline.html#cmdoption-u">3.6  相关文档</a>)</li><li>对于 3.7 以上版本: 全部采用 unbuffered(<a href="https://docs.python.org/3.7/using/cmdline.html#cmdoption-u">3.7 相关文档</a>)</li></ul><p>而当 <code>PYTHONPATH</code> 不为空,视为使用了 <code>-u</code> 参数</p><p>虽然输出结果与行缓冲与无缓冲的结果相同，但是仍然需要注意这里并非是如同规范那样定义</p><h3 id="什么时候不应该使用缓冲区"><a href="#什么时候不应该使用缓冲区" class="headerlink" title="什么时候不应该使用缓冲区"></a>什么时候不应该使用缓冲区</h3><p>一般来说,第一次接触缓冲区应该都是C语言读入部分</p><p>比如如果要有用户交互输入数据,需要分析输入内容,可能会牵扯到换行的处理  </p><p>当用户一次性输入很多数据时,这些数据都会被存放在输入缓冲区内,每次使用 <code>getchar()</code>;或者 <code>scanf(&quot;%c&quot;, &amp;c)</code>;会从中取出一个字符出来</p><p>这时,如果输入末尾有无效输入,比如用户多打了个空格才换行等奇怪的操作,可能会导致输入缓冲区留下一部分数据,从而导致下一次处理数据出错</p><p>这时,往往要使用 <code>fflush(stdin)</code>;在每次读入前清空缓冲区,以确保读到的数据是用户刚刚输入的内容</p><p>在 Python 中,最常见的操作是使用 flask、django 时,后台打印 log。</p><p>当同时有两个请求送达,两个打印 log 的操作同时写入输出缓冲区,很可能你看到的就是两个 log 混杂在一起的内容,这时为了保证 log 的完整以及有序,就应该关闭缓冲区</p><p>而在Python中,关闭缓冲区有三种方法:</p><ul><li><code>sys.stdout.flush()</code></li><li><code>python -u xxx.py</code></li><li><code>set PYTHONUNBUFFERED=&quot;&quot;</code></li></ul><h3 id="什么时候应该使用缓冲区"><a href="#什么时候应该使用缓冲区" class="headerlink" title="什么时候应该使用缓冲区"></a>什么时候应该使用缓冲区</h3><p>缓冲区(buffer)原本是用于中和内外存读取速度不同而设计的一个缓冲</p><p>因此当读写速度与运算、处理速度不匹配时就应该使用缓冲区</p><p>如大量写入文件,尽管可以使用 <code>&quot;n&quot;.join(data_list) </code>来将数据拼接成文本一次写入,但是有时候可能会由于异步、数据结构过于复杂，需要用多个 <code>f.write()</code> 来写入文件,这时就需要使用 buffer</p><p>将要写到文件的数据先存入内存中,关闭文件时一次性写入,从而减少了 io 操作次数</p><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">print(&quot;;&quot;.join([str(i) for i in range(10000)]))</span><br></pre></td></tr></table></figure><p>这段代码在 <strong>Windows</strong> 环境下的 <strong>Python3.9</strong> 以下版本使用 <code>python -u test.py</code> 会导致奇怪的截断</p><p>简单解释就是 Windows 7 以下的控制台对写出有限制(64KiB),因此 python 会尝试将输出内容除以 2 来输出<br>最终导致输出内容被截断</p><p>该“特性”将在Python3.9被移除<br>相关讨论: <a href="https://stackoverflow.com/questions/54266222/why-does-this-code-print-a-different-result-between-windows-and-linux">StackOverflow: Why does this code print a different result between Windows and Linux?</a></p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li>文章中已经存在的链接</li><li><a href="https://stackoverflow.com/questions/14258500/python-significance-of-u-option">StackOverflow: Python: significance of -u option?</a></li><li><a href="https://www.systutorials.com/241780/how-to-flush-stdout-buffer-in-python/">SystuTorials: How to flush STDOUT buffer in Python?</a></li><li><a href="https://oomake.com/question/1001203">码客: 为什么在多线程时关闭标准输出缓冲区？</a></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;本文转载于：&lt;a href=&quot;https://www.oyohyee.com/post/Note/python_buffer/&quot;&gt;Python的Buffer机制&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;环境准备&quot;&gt;&lt;a href=&quot;#环境准备&quot; class=&quot;headerlink&quot; title=&quot;环境准备&quot;&gt;&lt;/a&gt;环境准备&lt;/h2&gt;&lt;p&gt;Python版本: &lt;code&gt;3.7.1&lt;/code&gt;(3.7以上版本)&lt;br&gt;清空 &lt;code&gt;PYTHONUNBUFFERED&lt;/code&gt; 环境变量(默认是空的,不过以防万一还是清空下)&lt;/p&gt;
&lt;p&gt;cmd 清空&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;set PYTHONUNBUFFERED=&amp;quot;&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;powershell 清空&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;$env:PYTHONUNBUFFERED=&amp;quot;&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;bash 清空&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;export PYTHONUNBUFFERED=&amp;quot;&amp;quot;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;将下面内容&lt;strong&gt;保存到 &lt;code&gt;test.py&lt;/code&gt; 中&lt;/strong&gt;,执行下面的语句&lt;/p&gt;
&lt;figure class=&quot;highlight plaintext&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;import sys&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sys.stdout.write(&amp;quot;stdout1 &amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sys.stderr.write(&amp;quot;stderr1 &amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sys.stdout.write(&amp;quot;stdout2 &amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sys.stderr.write(&amp;quot;stderr2 &amp;quot;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;执行 &lt;code&gt;python test.py&lt;/code&gt;, 输出 &lt;code&gt;stderr1 stderr2 stdout1 stdout2&lt;/code&gt;&lt;br&gt;执行 &lt;code&gt;python -u test.py&lt;/code&gt;, 输出 &lt;code&gt;stdout1 stderr1 stdout2 stderr2&lt;/code&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="Python" scheme="https://woodenrobot.me/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>群晖 Video Station 支持 DTS 和 eac3 解决方案</title>
    <link href="https://woodenrobot.me/2019/08/12/syn-vediostation/"/>
    <id>https://woodenrobot.me/2019/08/12/syn-vediostation/</id>
    <published>2019-08-12T03:14:13.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<p><strong>新脚本已支持 DSM 6.2.4-25556  VideoStation 2.4.10 - 1632 不需要卸载Moments 和Advanced Media Extensions，请先用卸载命令卸载老的再重新安装一次就可以了，试过的同学请反馈一下！</strong></p><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>蜗牛星际装了黑群晖，下了几部蓝光电影，播放的时候确显示“不支持当前所选音频的文件格式，因此无法播放视频。请尝试其它音轨，确定其是否支持”。经过查询得知这两个音轨是需要授权使用的，群晖应该没有给授权费，所以在后续的 Video Station 版本中禁用了这些音轨。</p><span id="more"></span><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><h2 id="添加第三方套件中心"><a href="#添加第三方套件中心" class="headerlink" title="添加第三方套件中心"></a>添加第三方套件中心</h2><ol><li>（管理员账号）进入 DSM 桌面，打开<code>套件中心</code>，点击<code>设置</code> —&gt; <code>常规</code>，在<code>信任层级</code>中选择<code>任何发行者</code>;</li><li>接着点击 <code>套件来源</code>，选择 <code>新增</code> 添加第三方源 <a href="http://packages.synocommunity.com/">http://packages.synocommunity.com/</a> </li></ol><h2 id="安装-ffmpeg"><a href="#安装-ffmpeg" class="headerlink" title="安装 ffmpeg"></a>安装 ffmpeg</h2><p>在刚刚添加的第三方套件源中找到 <code>ffmpeg</code> 进行安装。</p><h3 id="方案一（不推荐）"><a href="#方案一（不推荐）" class="headerlink" title="方案一（不推荐）"></a>方案一（不推荐）</h3><h2 id="Video-Station-降级"><a href="#Video-Station-降级" class="headerlink" title="Video Station 降级"></a>Video Station 降级</h2><ol><li>首先在套件中心卸载最新版的 <code>Video Station</code>;</li><li>然后下载 <a href="https://global.download.synology.com/download/Package/spk/VideoStation/2.3.4-1468/VideoStation-x86_64-2.3.4-1468.spk"><code>video station 2.3.4-1468</code></a> 版本手动安装；</li><li>安装后保持当前版本不要升级。<br>安装后打开 <code>Video Station</code> 就可以播放 <code>DTS</code> 和 <code>eac3</code> 音轨的视频了。</li></ol><p><strong>PS：使用这个方案会失去最新版 video station 的特性，比如倍速播放、电视剧搜刮信息更全的遗憾</strong></p><h2 id="方案二"><a href="#方案二" class="headerlink" title="方案二"></a>方案二</h2><p><strong>适用于最新 <code>2.4.9-1626</code> 版 <code>Video Station</code></strong></p><p>首先 SSH 登录群晖并进入 root 用户下：</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">sudo -i</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">sh -c &quot;$(wget -O- https://raw.githubusercontent.com/Wooden-Robot/documents-for-fun/master/Synology/ffmpeg_dts_eac3_patch.sh)&quot; -p install</span><br></pre></td></tr></table></figure><p>安装后重新启动 <code>Video Station</code> 就可以播放 <code>DTS</code> 和 <code>eac3</code> 音轨的视频了。</p><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">sh -c &quot;$(wget -O- https://raw.githubusercontent.com/Wooden-Robot/documents-for-fun/master/Synology/ffmpeg_dts_eac3_patch.sh)&quot; -p uninstall</span><br></pre></td></tr></table></figure><p><strong>如果无法使用上面的 github 脚本！请关注公众号：<code>WoodenRobot</code> 回复 <code>dts</code>  获取国内安装和卸载命令！</strong></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://www.zhihu.com/question/271946974">群晖如何支持eac3？</a></li><li><a href="https://github.com/crazykuma/dsm_plugins/blob/master/ffmpeg_dts_eac3_patch.sh">ffmpeg_dts_eac3_patch.sh</a></li><li><a href="https://post.smzdm.com/p/a4w0l55w/">群晖Video Station不支持dts eac3的解决方案</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;strong&gt;新脚本已支持 DSM 6.2.4-25556  VideoStation 2.4.10 - 1632 不需要卸载Moments 和Advanced Media Extensions，请先用卸载命令卸载老的再重新安装一次就可以了，试过的同学请反馈一下！&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;蜗牛星际装了黑群晖，下了几部蓝光电影，播放的时候确显示“不支持当前所选音频的文件格式，因此无法播放视频。请尝试其它音轨，确定其是否支持”。经过查询得知这两个音轨是需要授权使用的，群晖应该没有给授权费，所以在后续的 Video Station 版本中禁用了这些音轨。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="群晖" scheme="https://woodenrobot.me/tags/%E7%BE%A4%E6%99%96/"/>
    
  </entry>
  
  <entry>
    <title>浏览器输入 URL 后的历程</title>
    <link href="https://woodenrobot.me/2019/08/02/browswer-url/"/>
    <id>https://woodenrobot.me/2019/08/02/browswer-url/</id>
    <published>2019-08-02T08:12:31.000Z</published>
    <updated>2024-06-07T02:59:12.927Z</updated>
    
    <content type="html"><![CDATA[<h1 id="输入-URL"><a href="#输入-URL" class="headerlink" title="输入 URL"></a>输入 URL</h1><p>以访问”baidu.com”为例，当你按下“b”键，浏览器接收到这个消息之后，会触发自动完成机制。浏览器根据自己的算法，以及你是否处于隐私浏览模式，会在浏览器的地址框下方给出输入建议。大部分算法会优先考虑根据你的搜索历史和书签等内容给出建议。</p><h1 id="检测-URL"><a href="#检测-URL" class="headerlink" title="检测 URL"></a>检测 URL</h1><ul><li>第一步是浏览器对用户输入的网址做初步的格式化检查。<br>如果输入的 URL 是 bai du.com 或者 <a href="mailto:&#x62;&#x61;&#105;&#x40;&#x64;&#117;&#x2e;&#99;&#111;&#109;">&#x62;&#x61;&#105;&#x40;&#x64;&#117;&#x2e;&#99;&#111;&#109;</a> 这些为不正确的 URL，当协议或主机名不合法时，浏览器会将地址栏中输入的文字传给默认的搜索引擎。</li></ul><h1 id="转换非-ASCII-的-Unicode-字符"><a href="#转换非-ASCII-的-Unicode-字符" class="headerlink" title="转换非 ASCII 的 Unicode 字符"></a>转换非 ASCII 的 Unicode 字符</h1><ul><li>浏览器检查输入是否含有不是 a-z， A-Z，0-9， - 或者 . 的字符</li><li>这里 URL 是 baidu.com ，所以没有非ASCII的字符；如果有的话，浏览器会对主机名部分使用 Punycode 编码</li></ul><h1 id="检查-HSTS-列表"><a href="#检查-HSTS-列表" class="headerlink" title="检查 HSTS 列表"></a>检查 HSTS 列表</h1><ul><li>浏览器检查自带的“预加载 HSTS（HTTP严格传输安全）”列表，这个列表里包含了哪些请求浏览器只使用HTTPS进行连接的网站</li><li>如果网站在这个列表里，浏览器会使用 HTTPS 而不是 HTTP 协议，否则，最初的请求会使用HTTP协议发送</li><li>注意，一个网站哪怕不在 HSTS 列表里，也可以要求浏览器对自己使用 HSTS 政策进行访问。浏览器向网站发出第一个 HTTP 请求之后，网站会返回浏览器一个响应，请求浏览器只使用 HTTPS 发送请求。然而，就是这第一个 HTTP 请求，却可能会使用户受到 downgrade attack 的威胁（参考：<a href="https://zhuanlan.zhihu.com/p/22917510">HTTPS 协议降级攻击原理</a>），这也是为什么现代浏览器都预置了 HSTS 列表。</li></ul><span id="more"></span><h1 id="DNS-查询"><a href="#DNS-查询" class="headerlink" title="DNS 查询"></a>DNS 查询</h1><ul><li>浏览器检查域名是否在缓存当中（要查看 Chrome 当中的缓存， 打开 chrome://net-internals/#dns）。</li><li>如果缓存中没有，就去调用 gethostbyname 库函数（操作系统不同函数也不同）进行查询。<br>gethostbyname 函数在试图进行DNS解析之前首先检查域名是否在本地 Hosts 里，Hosts 的位置 不同的操作系统有所不同</li><li>如果 gethostbyname 没有这个域名的缓存记录，也没有在 hosts 里找到，它将会向 DNS 服务器发送一条 DNS 查询请求。DNS 服务器是由网络通信栈提供的，通常是本地路由器或者 ISP 的缓存 DNS 服务器。</li><li>查询本地 DNS 服务器</li><li>如果 DNS 服务器和我们的主机在同一个子网内，系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询</li><li>如果 DNS 服务器和我们的主机在不同的子网，系统会按照下面的 ARP 过程对默认网关进行查询<h1 id="ARP-过程"><a href="#ARP-过程" class="headerlink" title="ARP 过程"></a>ARP 过程</h1>要想发送 ARP（地址解析协议）广播，我们需要有一个目标 IP 地址，同时还需要知道用于发送 ARP 广播的接口的 MAC 地址。</li><li>首先查询 ARP 缓存，如果缓存命中，我们返回结果：目标 IP = MAC<br>如果缓存没有命中：</li><li>查看路由表，看看目标 IP 地址是不是在本地路由表中的某个子网内。是的话，使用跟那个子网相连的接口，否则使用与默认网关相连的接口。</li><li>查询选择的网络接口的 MAC 地址</li><li>我们发送一个二层（ OSI 模型 中的数据链路层）ARP 请求：</li></ul><p><code>ARP Request</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></pre></td><td class="code"><pre><span class="line">Sender MAC: interface:mac:address:here</span><br><span class="line">Sender IP: interface.ip.goes.here</span><br><span class="line">Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)</span><br><span class="line">Target IP: target.ip.goes.here</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>根据连接主机和路由器的硬件类型不同，可以分为以下几种情况：</p><p>直连：</p><ul><li>如果我们和路由器是直接连接的，路由器会返回一个 ARP Reply （见下面）。</li></ul><p>集线器：</p><ul><li>如果我们连接到一个集线器，集线器会把 ARP 请求向所有其它端口广播，如果路由器也“连接”在其中，它会返回一个 <code>ARP Reply</code> 。</li></ul><p>交换机：</p><ul><li>如果我们连接到了一个交换机，交换机会检查本地 CAM/MAC 表，看看哪个端口有我们要找的那个 MAC 地址，如果没有找到，交换机会向所有其它端口广播这个 ARP 请求。</li><li>如果交换机的 MAC/CAM 表中有对应的条目，交换机会向有我们想要查询的 MAC 地址的那个端口发送 ARP 请求</li><li>如果路由器也“连接”在其中，它会返回一个 <code>ARP Reply</code></li></ul><p><code>ARP Reply</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></pre></td><td class="code"><pre><span class="line">Sender MAC: target:mac:address:here</span><br><span class="line">Sender IP: target.ip.goes.here</span><br><span class="line">Target MAC: interface:mac:address:here</span><br><span class="line">Target IP: interface.ip.goes.here</span><br></pre></td></tr></table></figure><p>现在我们有了 DNS 服务器或者默认网关的 IP 地址，我们可以继续 DNS 请求了：</p><ul><li>使用 53 端口向 DNS 服务器发送 UDP 请求包，如果响应包太大，会使用 TCP 协议</li><li>如果 LDNS 没有命中，就直接跳到 Root Server 域名服务器请求解析</li><li>根域名服务器返回给 LDNS 一个所查询域的主域名服务器（gTLD Server，国际顶尖域名服务器，如.com .cn .org等）地址</li><li>此时 LDNS 再发送请求给上一步返回的 gTLD</li><li>接受请求的 gTLD 查找并返回这个域名对应的 Name Server 的地址，这个 Name Server 就是网站注册的域名服务器</li><li>Name Server 根据映射关系表找到目标 ip，将 ip 连同一个 TTL 值返回给 DNS Server 域名服务器。</li><li>LDNS拿到ip和TTL会缓存起来，缓存时间由TTL值控制</li><li>把解析的结果返回给用户，用户根据TTL值缓存在本地系统缓存中，域名解析过程结束。</li></ul><h1 id="使用套接字"><a href="#使用套接字" class="headerlink" title="使用套接字"></a>使用套接字</h1><p>当浏览器得到了目标服务器的 IP 地址，以及 URL 中给出来端口号（http 协议默认端口号是 80， https 默认端口号是 443），它会调用系统库函数 <code>socket</code> ，请求一个 TCP 流套接字，对应的参数是 <code>AF_INET/AF_INET6</code> 和 <code>SOCK_STREAM</code>。</p><ul><li><p>这个请求首先被交给传输层，在传输层请求被封装成 TCP segment。目标端口会被加入头部，源端口会在系统内核的动态端口范围内选取（Linux下是ip_local_port_range)</p></li><li><p>TCP segment 被送往网络层，网络层会在其中再加入一个 IP 头部，里面包含了目标服务器的IP地址以及本机的IP地址，把它封装成一个IP packet。</p></li><li><p>这个 TCP packet 接下来会进入链路层，链路层会在封包中加入 frame 头部，里面包含了本地内置网卡的MAC地址以及网关（本地路由器）的 MAC 地址。像前面说的一样，如果内核不知道网关的 MAC 地址，它必须进行 ARP 广播来查询其地址。</p></li></ul><p>到了现在，TCP 封包已经准备好了，可以使用下面的方式进行传输：</p><ul><li>以太网</li><li>WiFi</li><li>蜂窝数据网络</li></ul><p>对于大部分家庭网络和小型企业网络来说，封包会从本地计算机出发，经过本地网络，再通过调制解调器把数字信号转换成模拟信号，使其适于在电话线路，有线电视光缆和无线电话线路上传输。在传输线路的另一端，是另外一个调制解调器，它把模拟信号转换回数字信号，交由下一个网络节点处理。节点的目标地址和源地址将在后面讨论。</p><p>大型企业和比较新的住宅通常使用光纤或直接以太网连接，这种情况下信号一直是数字的，会被直接传到下一个 网络节点 进行处理。</p><p>最终封包会到达管理本地子网的路由器。在那里出发，它会继续经过自治区域(autonomous system, 缩写 AS)的边界路由器，其他自治区域，最终到达目标服务器。一路上经过的这些路由器会从IP数据报头部里提取出目标地址，并将封包正确地路由到下一个目的地。IP数据报头部 time to live (TTL) 域的值每经过一个路由器就减1，如果封包的TTL变为0，或者路由器由于网络拥堵等原因封包队列满了，那么这个包会被路由器丢弃。</p><p>上面的发送和接受过程在 TCP 连接期间会发生很多次：</p><ul><li><p>客户端选择一个初始序列号(ISN)，将设置了 SYN 位的封包发送给服务器端，表明自己要建立连接并设置了初始序列号</p></li><li><p><strong><em>服务器端接收到 SYN 包，如果它可以建立连接：</em></strong></p><ul><li>服务器端选择它自己的初始序列号</li><li>服务器端设置 SYN 位，表明自己选择了一个初始序列号</li><li>服务器端把 (客户端ISN + 1) 复制到 ACK 域，并且设置 ACK 位，表明自己接收到了客户端的第一个封包</li></ul></li><li><p><strong><em>客户端通过发送下面一个封包来确认这次连接：</em></strong></p><ul><li>自己的序列号+1</li><li>接收端 ACK+1</li><li>设置 ACK 位</li></ul></li><li><p><strong><em>数据通过下面的方式传输：</em></strong></p><ul><li>当一方发送了N个 Bytes 的数据之后，将自己的 SEQ 序列号也增加N</li><li>另一方确认接收到这个数据包（或者一系列数据包）之后，它发送一个 ACK 包，ACK 的值设置为接收到的数据包的最后一个序列号</li></ul></li><li><p><strong><em>关闭连接时：</em></strong></p><ul><li>要关闭连接的一方发送一个 FIN 包</li><li>另一方确认这个 FIN 包，并且发送自己的 FIN 包</li><li>要关闭的一方使用 ACK 包来确认接收到了 FIN</li></ul></li></ul><h1 id="TLS-握手"><a href="#TLS-握手" class="headerlink" title="TLS 握手"></a>TLS 握手</h1><ul><li><p>客户端发送一个 <code>ClientHello</code> 消息到服务器端，消息中同时包含了它的 Transport Layer Security (TLS) 版本，可用的加密算法和压缩算法。</p></li><li><p>服务器端向客户端返回一个 <code>ServerHello</code> 消息，消息中包含了服务器端的TLS版本，服务器所选择的加密和压缩算法，以及数字证书认证机构（Certificate Authority，缩写 CA）签发的服务器公开证书，证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程，直到协商生成一个新的对称密钥</p></li><li><p>客户端根据自己的信任CA列表，验证服务器端的证书是否可信。如果认为可信，客户端会生成一串伪随机数，使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥</p></li><li><p>服务器端使用自己的私钥解密上面提到的随机数，然后使用这串随机数生成自己的对称主密钥</p></li><li><p>客户端发送一个 <code>Finished</code> 消息给服务器端，使用对称密钥加密这次通讯的一个散列值</p></li><li><p>服务器端生成自己的 hash 值，然后解密客户端发送来的信息，检查这两个值是否对应。如果对应，就向客户端发送一个 <code>Finished</code> 消息，也使用协商好的对称密钥加密</p></li><li><p>从现在开始，接下来整个 TLS 会话都使用对称秘钥进行加密，传输应用层（HTTP）内容</p></li></ul><h1 id="HTTP-协议"><a href="#HTTP-协议" class="headerlink" title="HTTP 协议"></a>HTTP 协议</h1><p>如果浏览器使用 HTTP 协议，它会向服务器发送这样的一个请求:</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">GET / HTTP/1.1</span><br><span class="line">Host: www.baidu.com</span><br><span class="line">Connection: keep-alive</span><br><span class="line">[其他头部]</span><br></pre></td></tr></table></figure><p>“其他头部”包含了一系列的由冒号分割开的键值对，它们的格式符合 HTTP 协议标准，它们之间由一个换行符分割开来。（这里我们假设浏览器没有违反 HTTP 协议标准的 bug，同时假设浏览器使用 <code>HTTP/1.1</code> 协议，不然的话头部可能不包含 <code>Host</code> 字段，同时 <code>GET</code> 请求中的版本号会变成 <code>HTTP/1.0</code> 或者 <code>HTTP/0.9</code> 。）</p><p>HTTP/1.1 定义了“关闭连接”的选项 “close”，发送者使用这个选项指示这次连接在响应结束之后会断开。例如：</p><blockquote><p>Connection:close</p></blockquote><p>不支持持久连接的 HTTP/1.1 应用必须在每条消息中都包含 “close” 选项。</p><p>在发送完这些请求和头部之后，浏览器发送一个换行符，表示要发送的内容已经结束了。</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></pre></td><td class="code"><pre><span class="line">200 OK</span><br><span class="line">[响应头部]</span><br></pre></td></tr></table></figure><p>然后是一个换行，接下来有效载荷(payload)，也就是 <code>www.baiud.com</code> 的HTML内容。服务器不会关闭连接，因为客户端请求保持连接，服务器端会保持连接打开，以供之后的请求重用。</p><p>如果浏览器发送的HTTP头部包含了足够多的信息（例如包含了 Etag 头部），以至于服务器可以判断出，浏览器缓存的文件版本自从上次获取之后没有再更改过，服务器可能会返回这样的响应:</p><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">304 Not Modified</span><br><span class="line">[响应头部]</span><br></pre></td></tr></table></figure><p>这个响应没有有效载荷，浏览器会从自己的缓存中取出想要的内容。</p><p>在解析完 HTML 之后，浏览器和客户端会重复上面的过程，直到HTML页面引入的所有资源（图片，CSS，favicon.ico等等）全部都获取完毕，区别只是头部的 <code>GET / HTTP/1.1</code> 会变成 <code>GET /$(相对www.baidu.com的URL) HTTP/1.1</code> 。</p><p>如果 HTML 引入了 <code>www.baidu.com</code> 域名之外的资源，浏览器会回到上面解析域名那一步，按照下面的步骤往下一步一步执行，请求中的 <code>Host</code> 头部会变成另外的域名。</p><h1 id="HTTP-服务器请求处理"><a href="#HTTP-服务器请求处理" class="headerlink" title="HTTP 服务器请求处理"></a>HTTP 服务器请求处理</h1><p>HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx，以及 Windows 上的 IIS。</p><ul><li><p>HTTPD 接收请求</p></li><li><p><strong><em>服务器把请求拆分为以下几个参数：</em></strong></p><ul><li>HTTP 请求方法(GET, POST, HEAD, PUT, DELETE, CONNECT, OPTIONS, 或者 TRACE)。直接在地址栏中输入 URL 这种情况下，使用的是 GET 方法</li><li>域名：baidu.com</li><li>请求路径/页面：/ (我们没有请求baidu.com下的指定的页面，因此 / 是默认的路径)</li></ul></li><li><p>服务器验证其上已经配置了 baidu.com 的虚拟主机</p></li><li><p>服务器验证 baidu.com 接受 GET 方法<br>服务器验证该用户可以使用 GET 方法(根据 IP 地址，身份信息等)</p></li><li><p>如果服务器安装了 URL 重写模块（例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite），服务器会尝试匹配重写规则，如果匹配上的话，服务器会按照规则重写这个请求</p></li><li><p>服务器根据请求信息获取相应的响应内容，这种情况下由于访问路径是 “/“ ,会访问首页文件（你可以重写这个规则，但是这个是最常用的）。</p></li><li><p>服务器会使用指定的处理程序分析处理这个文件，假如 Baidu 使用 PHP，服务器会使用 PHP 解析 index 文件，并捕获输出，把 PHP 的输出结果返回给请求者。</p></li></ul><h1 id="浏览器背后的故事"><a href="#浏览器背后的故事" class="headerlink" title="浏览器背后的故事"></a>浏览器背后的故事</h1><p>当服务器提供了资源之后（HTML，CSS，JS，图片等），浏览器会执行下面的操作：</p><ul><li>解析 —— HTML，CSS，JS</li><li>渲染 —— 构建 DOM 树 -&gt; 渲染 -&gt; 布局 -&gt; 绘制</li></ul><h1 id="浏览器"><a href="#浏览器" class="headerlink" title="浏览器"></a>浏览器</h1><p>浏览器的功能是从服务器上取回你想要的资源，然后展示在浏览器窗口当中。资源通常是 HTML 文件，也可能是 PDF，图片，或者其他类型的内容。资源的位置通过用户提供的 URI(Uniform Resource Identifier) 来确定。</p><p>浏览器解释和展示 HTML 文件的方法，在 HTML 和 CSS 的标准中有详细介绍。这些标准由 Web 标准组织 W3C(World Wide Web Consortium) 维护。</p><p>不同浏览器的用户界面大都十分接近，有很多共同的 UI 元素：</p><ul><li>一个地址栏</li><li>后退和前进按钮</li><li>书签选项</li><li>刷新和停止按钮</li><li>主页按钮</li></ul><h2 id="浏览器高层架构"><a href="#浏览器高层架构" class="headerlink" title="浏览器高层架构"></a>浏览器高层架构</h2><p>组成浏览器的组件有：</p><ul><li><p><strong>用户界面</strong> 用户界面包含了地址栏，前进后退按钮，书签菜单等等，除了请求页面之外所有你看到的内容都是用户界面的一部分</p></li><li><p><strong>浏览器引擎</strong> 浏览器引擎负责让 UI 和渲染引擎协调工作<br>渲染引擎 渲染引擎负责展示请求内容。如果请求的内容是 HTML，渲染引擎会解析 HTML 和 CSS，然后将内容展示在屏幕上</p></li><li><p><strong>网络组件</strong> 网络组件负责网络调用，例如 HTTP 请求等，使用一个平台无关接口，下层是针对不同平台的具体实现</p></li><li><p><strong>UI后端</strong> UI 后端用于绘制基本 UI 组件，例如下拉列表框和窗口。UI 后端暴露一个统一的平台无关的接口，下层使用操作系统的 UI 方法实现</p></li><li><p><strong>Javascript 引擎</strong> Javascript 引擎用于解析和执行 Javascript 代码</p></li><li><p><strong>数据存储</strong> 数据存储组件是一个持久层。浏览器可能需要在本地存储各种各样的数据，例如 Cookie 等。浏览器也需要支持诸如 localStorage，IndexedDB，WebSQL 和 FileSystem 之类的存储机制</p></li></ul><h1 id="HTML-解析"><a href="#HTML-解析" class="headerlink" title="HTML 解析"></a>HTML 解析</h1><p>浏览器渲染引擎从网络层取得请求的文档，一般情况下文档会分成8kB大小的分块传输。</p><p>HTML 解析器的主要工作是对 HTML 文档进行解析，生成解析树。</p><p>解析树是以 DOM 元素以及属性为节点的树。DOM是文档对象模型(Document Object Model)的缩写，它是 HTML 文档的对象表示，同时也是 HTML 元素面向外部(如Javascript)的接口。树的根部是”Document”对象。整个 DOM 和 HTML 文档几乎是一对一的关系。</p><h2 id="解析算法"><a href="#解析算法" class="headerlink" title="解析算法"></a>解析算法</h2><p>HTML不能使用常见的自顶向下或自底向上方法来进行分析。主要原因有以下几点:</p><ul><li><p>语言本身的“宽容”特性</p></li><li><p>HTML 本身可能是残缺的，对于常见的残缺，浏览器需要有传统的容错机制来支持它们</p></li><li><p>解析过程需要反复。对于其他语言来说，源码不会在解析过程中发生变化，但是对于 HTML 来说，动态代码，例如脚本元素中包含的 document.write() 方法会在源码中添加内容，也就是说，解析过程实际上会改变输入的内容<br>由于不能使用常用的解析技术，浏览器创造了专门用于解析 HTML 的解析器。解析算法在 HTML5 标准规范中有详细介绍，算法主要包含了两个阶段：标记化（tokenization）和树的构建。</p></li></ul><h2 id="解析结束之后"><a href="#解析结束之后" class="headerlink" title="解析结束之后"></a>解析结束之后</h2><p>浏览器开始加载网页的外部资源（CSS，图像，Javascript 文件等）。</p><p>此时浏览器把文档标记为可交互的（interactive），浏览器开始解析处于“推迟（deferred）”模式的脚本，也就是那些需要在文档解析完毕之后再执行的脚本。之后文档的状态会变为“完成（complete）”，浏览器会触发“加载（load）”事件。</p><p>注意解析 HTML 网页时永远不会出现“无效语法（Invalid Syntax）”错误，浏览器会修复所有错误内容，然后继续解析。</p><h1 id="CSS-解析"><a href="#CSS-解析" class="headerlink" title="CSS 解析"></a>CSS 解析</h1><ul><li>根据 CSS词法和句法 分析CSS文件和 <code>&lt;style&gt;</code> 标签包含的内容以及 style 属性的值</li><li>每个CSS文件都被解析成一个样式表对象（StyleSheet object），这个对象里包含了带有选择器的CSS规则，和对应CSS语法的对象</li><li>CSS解析器可能是自顶向下的，也可能是使用解析器生成器生成的自底向上的解析器</li></ul><h1 id="页面渲染"><a href="#页面渲染" class="headerlink" title="页面渲染"></a>页面渲染</h1><ul><li><p>通过遍历DOM节点树创建一个“Frame 树”或“渲染树”，并计算每个节点的各个CSS样式值</p></li><li><p>通过累加子节点的宽度，该节点的水平内边距(padding)、边框(border)和外边距(margin)，自底向上的计算”Frame 树”中每个节点的首选(preferred)宽度</p></li><li><p>通过自顶向下的给每个节点的子节点分配可行宽度，计算每个节点的实际宽度</p></li><li><p>通过应用文字折行、累加子节点的高度和此节点的内边距(padding)、边框(border)和外边距(margin)，自底向上的计算每个节点的高度</p></li><li><p>使用上面的计算结果构建每个节点的坐标</p></li><li><p>当存在元素使用 <code>floated</code>，位置有 <code>absolutely</code> 或 <code>relatively</code> 属性的时候，会有更多复杂的计算，详见<a href="http://dev.w3.org/csswg/css2/">http://dev.w3.org/csswg/css2/</a> 和 <a href="http://www.w3.org/Style/CSS/current-work">http://www.w3.org/Style/CSS/current-work</a></p></li><li><p>创建layer(层)来表示页面中的哪些部分可以成组的被绘制，而不用被重新栅格化处理。每个帧对象都被分配给一个层</p></li><li><p>页面上的每个层都被分配了纹理</p></li><li><p>每个层的帧对象都会被遍历，计算机执行绘图命令绘制各个层，此过程可能由CPU执行栅格化处理，或者直接通过D2D/SkiaGL在GPU上绘制</p></li><li><p>上面所有步骤都可能利用到最近一次页面渲染时计算出来的各个值，这样可以减少不少计算量</p></li><li><p>计算出各个层的最终位置，一组命令由 Direct3D/OpenGL发出，GPU命令缓冲区清空，命令传至GPU并异步渲染，帧被送到Window Server。</p></li></ul><h1 id="GPU-渲染"><a href="#GPU-渲染" class="headerlink" title="GPU 渲染"></a>GPU 渲染</h1><ul><li>在渲染过程中，图形处理层可能使用通用用途的 CPU，也可能使用图形处理器 GPU</li><li>当使用 GPU 用于图形渲染时，图形驱动软件会把任务分成多个部分，这样可以充分利用 GPU 强大的并行计算能力，用于在渲染过程中进行大量的浮点计算。</li></ul><h1 id="Window-Server"><a href="#Window-Server" class="headerlink" title="Window Server"></a>Window Server</h1><h1 id="后期渲染与用户引发的处理"><a href="#后期渲染与用户引发的处理" class="headerlink" title="后期渲染与用户引发的处理"></a>后期渲染与用户引发的处理</h1><p>渲染结束后，浏览器根据某些时间机制运行 JavaScript 代码或与用户交互(在搜索栏输入关键字获得搜索建议)。类似 Flash 和 Java 的插件也会运行，尽管 Baidu 主页里没有。这些脚本可以触发网络请求，也可能改变网页的内容和布局，产生又一轮渲染与绘制。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://github.com/skyline75489/what-happens-when-zh_CN">What-happens-when</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;输入-URL&quot;&gt;&lt;a href=&quot;#输入-URL&quot; class=&quot;headerlink&quot; title=&quot;输入 URL&quot;&gt;&lt;/a&gt;输入 URL&lt;/h1&gt;&lt;p&gt;以访问”baidu.com”为例，当你按下“b”键，浏览器接收到这个消息之后，会触发自动完成机制。浏览器根据自己的算法，以及你是否处于隐私浏览模式，会在浏览器的地址框下方给出输入建议。大部分算法会优先考虑根据你的搜索历史和书签等内容给出建议。&lt;/p&gt;
&lt;h1 id=&quot;检测-URL&quot;&gt;&lt;a href=&quot;#检测-URL&quot; class=&quot;headerlink&quot; title=&quot;检测 URL&quot;&gt;&lt;/a&gt;检测 URL&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;第一步是浏览器对用户输入的网址做初步的格式化检查。&lt;br&gt;如果输入的 URL 是 bai du.com 或者 &lt;a href=&quot;mailto:&amp;#x62;&amp;#x61;&amp;#105;&amp;#x40;&amp;#x64;&amp;#117;&amp;#x2e;&amp;#99;&amp;#111;&amp;#109;&quot;&gt;&amp;#x62;&amp;#x61;&amp;#105;&amp;#x40;&amp;#x64;&amp;#117;&amp;#x2e;&amp;#99;&amp;#111;&amp;#109;&lt;/a&gt; 这些为不正确的 URL，当协议或主机名不合法时，浏览器会将地址栏中输入的文字传给默认的搜索引擎。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;转换非-ASCII-的-Unicode-字符&quot;&gt;&lt;a href=&quot;#转换非-ASCII-的-Unicode-字符&quot; class=&quot;headerlink&quot; title=&quot;转换非 ASCII 的 Unicode 字符&quot;&gt;&lt;/a&gt;转换非 ASCII 的 Unicode 字符&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;浏览器检查输入是否含有不是 a-z， A-Z，0-9， - 或者 . 的字符&lt;/li&gt;
&lt;li&gt;这里 URL 是 baidu.com ，所以没有非ASCII的字符；如果有的话，浏览器会对主机名部分使用 Punycode 编码&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;检查-HSTS-列表&quot;&gt;&lt;a href=&quot;#检查-HSTS-列表&quot; class=&quot;headerlink&quot; title=&quot;检查 HSTS 列表&quot;&gt;&lt;/a&gt;检查 HSTS 列表&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;浏览器检查自带的“预加载 HSTS（HTTP严格传输安全）”列表，这个列表里包含了哪些请求浏览器只使用HTTPS进行连接的网站&lt;/li&gt;
&lt;li&gt;如果网站在这个列表里，浏览器会使用 HTTPS 而不是 HTTP 协议，否则，最初的请求会使用HTTP协议发送&lt;/li&gt;
&lt;li&gt;注意，一个网站哪怕不在 HSTS 列表里，也可以要求浏览器对自己使用 HSTS 政策进行访问。浏览器向网站发出第一个 HTTP 请求之后，网站会返回浏览器一个响应，请求浏览器只使用 HTTPS 发送请求。然而，就是这第一个 HTTP 请求，却可能会使用户受到 downgrade attack 的威胁（参考：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/22917510&quot;&gt;HTTPS 协议降级攻击原理&lt;/a&gt;），这也是为什么现代浏览器都预置了 HSTS 列表。&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="技术" scheme="https://woodenrobot.me/categories/%E6%8A%80%E6%9C%AF/"/>
    
    
      <category term="HTTP" scheme="https://woodenrobot.me/tags/HTTP/"/>
    
  </entry>
  
</feed>
