<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://simpleyyt.com/atom.xml" rel="self" type="application/atom+xml" /><link href="https://simpleyyt.com/" rel="alternate" type="text/html" /><updated>2026-03-08T02:58:00+00:00</updated><id>https://simpleyyt.com/atom.xml</id><title type="html">Simpleyyt</title><subtitle>Too simple, sometimes naive.</subtitle><author><name>Simpleyyt</name></author><entry><title type="html">我也复刻了一个 Manus，带高仿 WebUI 和沙盒</title><link href="https://simpleyyt.com/2026/03/07/rebuild-manus-with-webui-and-sandbox/" rel="alternate" type="text/html" title="我也复刻了一个 Manus，带高仿 WebUI 和沙盒" /><published>2026-03-07T00:00:00+00:00</published><updated>2026-03-07T00:00:00+00:00</updated><id>https://simpleyyt.com/2026/03/07/rebuild-manus-with-webui-and-sandbox</id><content type="html" xml:base="https://simpleyyt.com/2026/03/07/rebuild-manus-with-webui-and-sandbox/"><![CDATA[<p>最近在学习 LLM Agent，但终觉“纸上学来终觉浅，绝知此事要躬行”，所以想写个小项目试试手。在这个人均写一个 Manus 的时代，在这个半开卷的情况下，况且 Manus 的提词已经泄漏的情况下，是不是我也可以写一个，我整理了一下自己的需求如下：</p>

<ul>
  <li>可单主机云上部署，好部署在我家里的 Server 上。</li>
  <li>集成浏览器、Shell、Python、Node 等工具及 Ubuntu 沙盒环境，每一个任务分配一个沙盒。</li>
  <li>Web UI 与提示词直接借鉴官方 Manus 的。</li>
</ul>

<p>结合 Cursor 应该可以快速地写出一个 Manus 的示例，听说 OpenManus 4 小时就写出来了。</p>

<p><strong>项目地址：</strong><a href="https://github.com/Simpleyyt/ai-manus">https://github.com/Simpleyyt/ai-manus</a></p>

<!--more-->

<h1 id="demo-演示">Demo 演示</h1>

<p><strong>Code USE</strong></p>

<blockquote>
  <p>Prompt：写一个复杂的 python 示例</p>
</blockquote>

<video controls="" playsinline="" width="100%" src="https://raw.githubusercontent.com/Simpleyyt/picgo-image/master/code-use.mov"></video>

<p>原始链接：<a href="https://github.com/user-attachments/assets/5cb2240b-0984-4db0-8818-a24f81624b04">https://github.com/user-attachments/assets/5cb2240b-0984-4db0-8818-a24f81624b04</a></p>

<p><strong>Browser Use</strong></p>

<blockquote>
  <p>Prompt: 任务：llm 最新论文</p>
</blockquote>

<video controls="" playsinline="" width="100%" src="https://raw.githubusercontent.com/Simpleyyt/picgo-image/master/browser-use.mov"></video>

<p>原始链接：<a href="https://github.com/user-attachments/assets/8f7788a4-fbda-49f5-b836-949a607c64ac">https://github.com/user-attachments/assets/8f7788a4-fbda-49f5-b836-949a607c64ac</a></p>

<p>效果可以说是相当的凑合，但是目前是学习目的，提示词与 Agent 流程可能都需要优化一下，懒得再调了，就交给广大网友了。</p>

<h1 id="整体设计">整体设计</h1>

<p><img src="https://raw.githubusercontent.com/Simpleyyt/picgo-image/master/Pasted%20image%2020250507002019.png" alt="Pasted image 20250507002019" /></p>

<p>整体系统由三个模块组成：Web、Server 与 Sandbox，用户使用流程如下：</p>

<p><strong>当用户发起对话时：</strong></p>

<ol>
  <li>Web 向 Server 发送创建 Agent 请求，Server 通过<code class="language-plaintext highlighter-rouge">/var/run/docker.sock</code>创建出 Sandbox，并返回会话 ID。</li>
  <li>Sandbox 是一个 Ubuntu Docker 环境，里面会启动 chrome 浏览器及 File/Shell 等工具的 API 服务。</li>
  <li>Web 往会话 ID 中发送用户消息，Server 收到用户消息后，将消息发送给 PlanAct Agent 处理。</li>
  <li>PlanAct Agent 处理过程中会调用相关工具完成任务。</li>
  <li>Agent 处理过程中产生的所有事件通过 SSE 发回 Web。</li>
</ol>

<p><strong>当用户浏览工具时：</strong></p>

<ul>
  <li>浏览器：
    <ol>
      <li>Sandbox 的无头浏览器通过 xvfb 与 x11vnc 启动了 vnc 服务，并且通过 websockify 将 vnc 转化成 websocket。</li>
      <li>Web 的 NoVNC 组件通过 Server 的 Websocket Forward 转发到 Sandbox，实现浏览器查看。</li>
    </ol>
  </li>
  <li>其它工具：其它工具原理也是差不多。</li>
</ul>

<h1 id="ai-agent-设计模式">AI Agent 设计模式</h1>

<p>AI Agent 是什么？相信大家都听烂了，我在这里说的肯定不如别人说的好，简单来说就是：<strong>AI Agent =</strong> <strong>LLM</strong> <strong>+ Planning + Memory + Tools。</strong></p>

<p><img src="https://raw.githubusercontent.com/Simpleyyt/picgo-image/master/f6824e99273848cad1952a856ef73d42_MD5.jpeg" alt="f6824e99273848cad1952a856ef73d42_MD5" /></p>

<h2 id="functioncall-or-react-or-langchain">FunctionCall or ReAct or LangChain?</h2>

<p>先来说一下 Tool Use 部分，目前可以使用的方式是：1）高阶模型自身的 FunctionCall 能力；2）ReAct Prompt 框架；3）LangChain Agent 框架，其实它是前两者的高度封装。</p>

<p>最简单是使用 LangChain 这样高度封装的框架了，但是对于学习目的的项目来说，我一直强烈想知道它背后做了什么，所以这个先 Pass 掉了。</p>

<table>
  <tbody>
    <tr>
      <td>使用 FunctionCall 来使用工具要求比较高阶的模型，但 ReAct 的设计又太繁琐，思来想去，还是先使用 FunctionCall，先用好的模型开发，后面再来研究 ReAct 框架。关于 ReAct ，感兴趣可以看看：[ReAct 框架</td>
      <td>Prompt Engineering Guide](https://www.promptingguide.ai/zh/techniques/react)。</td>
    </tr>
  </tbody>
</table>

<p><img src="https://raw.githubusercontent.com/Simpleyyt/picgo-image/master/691f4e2b500cc68a723578ce8dec2740_MD5.jpeg" alt="691f4e2b500cc68a723578ce8dec2740_MD5" /></p>

<p>因此，该项目对 LLM 的要求如下：</p>

<ol>
  <li>兼容 OpenAI 接口</li>
  <li>支持 FunctionCall</li>
  <li>支持 Json 格式输出（因为抛弃了 LangChain 又想省事）</li>
</ol>

<h2 id="plan-and-act-agent-设计模式">Plan-and-Act Agent 设计模式</h2>

<p>整体使用 Plan-and-Act 的 Agent 设计模式，相关论文：<a href="https://arxiv.org/html/2503.09572v1">Plan-and-Act : Improving Planning of Agents for Long-Horizon Tasks</a>，它的相关流程如下：</p>

<p><img src="https://raw.githubusercontent.com/Simpleyyt/picgo-image/master/2a830e4e2e3aec207d0a998e59c395d3_MD5.jpeg" alt="2a830e4e2e3aec207d0a998e59c395d3_MD5" /></p>

<p>即将系统分成 Planner 和 Executor，Planner 将任务进行规划拆分，Executor 负责任务分步执行，将执行结果返回给 Planner 重新规划。</p>

<p>项目中的状态流转图如下：</p>

<p><img src="https://raw.githubusercontent.com/Simpleyyt/picgo-image/master/9483c075d0f84969ae95286e48529717_MD5.jpg" alt="9483c075d0f84969ae95286e48529717_MD5" /></p>

<p>系统支持被打断，所有打断的消息都会流向 Planner Agent，Planner Agent 为根据用户的打断消息重新规划。</p>

<h1 id="sandbox-设计">Sandbox 设计</h1>

<p>为了实现每个任务使用单独的 Docker 沙盒，我们 Server 通过<code class="language-plaintext highlighter-rouge">/var/run/docker.sock</code>进行机器上 Docker 沙盒的创建与销毁。</p>

<h2 id="sandbox-进程与生命周期管理">Sandbox 进程与生命周期管理</h2>

<p>整个 Sandbox 通过 supervisord 进程管理，并且通过 supervisord 实现 Sandbox 的 TTL 管理。因为 Agent 目前没有主动销毁机制，所以需要 Sandbox 自动过期自销毁，并实现续时等接口。</p>

<h2 id="file--shell-工具">File &amp; Shell 工具</h2>

<p>文件操作与 Shell 命令执行没有什么难度，Cursor 最擅长这些，我把 Manus 的工具描述丢给 Cursor 后，很快就用 FastAPI 帮我生成了一整套代码，不得不说很稳定，基本没有怎么改过。</p>

<h2 id="browser-工具">Browser 工具</h2>

<p>目前遍地的 Manus 都在使用 browser-use（https://github.com/browser-use/browser-use）这个库，为了学习和研究的目的，我还是决定使用 Playwright + Chrome 自己搞一个。由于目前还没有能力使用视觉模型，所以还是以文字模型为基础来操作浏览器。</p>

<p>为了让 Sandbox 更加纯粹，Sandbox 只启动 Chrome，并且暴露 CDP 和 VNC 让 Server 来操作。</p>

<p><strong>启动 Browser</strong></p>

<p>坑点一：启动参数</p>

<p>Chrome browser 的启动有很多参数，遇到问题再找参数有点费时费力，直接站在巨人的肩膀上，参考 browser-use 的启动参数：https://github.com/browser-use/browser-use/blob/main/browser_use/browser/chrome.py。</p>

<p>坑点二：CDP 监听地址不支持<code class="language-plaintext highlighter-rouge">0.0.0.0</code></p>

<p>新版本 Chrome 似乎已经不支持<code class="language-plaintext highlighter-rouge">--remote-debugging-address</code>参数（参考：https://issues.chromium.org/issues/327558594），解决方案可以通过端口转发：</p>

<pre><code class="language-Bash"># 假设 CDP 监听在 127.0.0.1:8222
socat TCP-LISTEN:9222,bind=0.0.0.0,fork,reuseaddr TCP:127.0.0.1:8222
</code></pre>

<p>坑点三：CDP 地址不能通过域名访问</p>

<p>即 Http Header 的 Host 字段只能是 IP 或者 localhost，只需要在反向代理中把相关字段给替换掉即可。</p>

<p><strong>VNC</strong> <strong>访问</strong></p>

<p>由于 Docker 镜像内没有 X Server 等图形环境，所有通过虚拟 X11 显示服务器<code class="language-plaintext highlighter-rouge">Xvfb</code>来给 Chrome 绘制窗口，并通过<code class="language-plaintext highlighter-rouge">x11vnc</code>提供 VNC Server：</p>

<pre><code class="language-Bash"># 启动 Xvfb 在 Display :1
Xvfb :1 -screen 0 1280x1029x24

# Chrome 浏览器指定 Display
google-chrome \
    --display=:1 \
    ...

# 启动 VNC 服务
x11vnc -display :1 -nopw -listen 0.0.0.0 -xkb -forever -rfbport 5900
</code></pre>

<p>由于 VNC 的四层端口对反向代理转发不友好，所以这里还使用<code class="language-plaintext highlighter-rouge">websockify</code>将 VNC 转成七层<code class="language-plaintext highlighter-rouge">Websocket</code>：</p>

<pre><code class="language-Bash"># 将暴露 5901 端口 Websocket 服务
websockify 0.0.0.0:5901 localhost:5900
</code></pre>

<p>以便于后面<code class="language-plaintext highlighter-rouge">NoVNC</code>连接。</p>

<p><strong>AI</strong> <strong>网页元素操作与信息提取</strong></p>

<p>一开始天真地把整个 html 丢给大模型，发现是行不通的，一丢过去就爆 Token 了。小调研了一下，发现现在主流的做法是：1）可交互元素提取；2）网页信息提取。</p>

<p>首先是提取可见的、可交互的元素，以便让大模型识别哪些可以输入、点击等。一般将元素提取成<code class="language-plaintext highlighter-rouge">index &lt;tag&gt;text&lt;/tag&gt;</code>，例如：</p>

<pre><code class="language-Bash">1 &lt;input&gt;手机号&lt;/input&gt;
2 &lt;input&gt;密码&lt;/input&gt;
3 &lt;button&gt;确认&lt;/button&gt;
...
</code></pre>

<p>并在原标签中把 ID 号标注上去，这里是 Cursor 给我生成的代码，我也没有细看，但它能 work：</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">interactiveElements</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">const</span> <span class="nx">viewportHeight</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">viewportWidth</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span><span class="p">;</span>

<span class="c1">// Get all potentially relevant interactive elements</span>
<span class="kd">const</span> <span class="nx">elements</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">button, a, input, textarea, select, [role="button"], [tabindex]:not([tabindex="-1"])</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">let</span> <span class="nx">validElementIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// For generating consecutive indices</span>

<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">elements</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nx">elements</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
    <span class="c1">// Check if the element is in the viewport and visible</span>
    <span class="kd">const</span> <span class="nx">rect</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">();</span>
    
    <span class="c1">// Element must have some dimensions</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">rect</span><span class="p">.</span><span class="nx">width</span> <span class="o">===</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">rect</span><span class="p">.</span><span class="nx">height</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
    
    <span class="c1">// Element must be within the viewport</span>
    <span class="k">if</span> <span class="p">(</span>
        <span class="nx">rect</span><span class="p">.</span><span class="nx">bottom</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> 
        <span class="nx">rect</span><span class="p">.</span><span class="nx">top</span> <span class="o">&gt;</span> <span class="nx">viewportHeight</span> <span class="o">||</span>
        <span class="nx">rect</span><span class="p">.</span><span class="nx">right</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">||</span> 
        <span class="nx">rect</span><span class="p">.</span><span class="nx">left</span> <span class="o">&gt;</span> <span class="nx">viewportWidth</span>
    <span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
    
    <span class="c1">// Check if the element is visible (not hidden by CSS)</span>
    <span class="kd">const</span> <span class="nx">style</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">getComputedStyle</span><span class="p">(</span><span class="nx">element</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span>
        <span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">none</span><span class="dl">'</span> <span class="o">||</span> 
        <span class="nx">style</span><span class="p">.</span><span class="nx">visibility</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span> <span class="o">||</span> 
        <span class="nx">style</span><span class="p">.</span><span class="nx">opacity</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span>
    <span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
    
    
    <span class="c1">// Get element type and text</span>
    <span class="kd">let</span> <span class="nx">tagName</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">tagName</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">();</span>
    <span class="kd">let</span> <span class="nx">text</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
    
    <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">value</span> <span class="o">&amp;&amp;</span> <span class="p">[</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">textarea</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">select</span><span class="dl">'</span><span class="p">].</span><span class="nx">includes</span><span class="p">(</span><span class="nx">tagName</span><span class="p">))</span> <span class="p">{</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
        
        <span class="c1">// Add label and placeholder information for input elements</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">tagName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Get associated label text</span>
            <span class="kd">let</span> <span class="nx">labelText</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">`label[for="</span><span class="p">${</span><span class="nx">element</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">"]`</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="nx">label</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">labelText</span> <span class="o">=</span> <span class="nx">label</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span>
                <span class="p">}</span>
            <span class="p">}</span>
            
            <span class="c1">// Look for parent or sibling label</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">labelText</span><span class="p">)</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">parentLabel</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">closest</span><span class="p">(</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="nx">parentLabel</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">labelText</span> <span class="o">=</span> <span class="nx">parentLabel</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">().</span><span class="nx">replace</span><span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span> <span class="dl">''</span><span class="p">).</span><span class="nx">trim</span><span class="p">();</span>
                <span class="p">}</span>
            <span class="p">}</span>
            
            <span class="c1">// Add label information</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">labelText</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">text</span> <span class="o">=</span> <span class="s2">`[Label: </span><span class="p">${</span><span class="nx">labelText</span><span class="p">}</span><span class="s2">] </span><span class="p">${</span><span class="nx">text</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
            <span class="p">}</span>
            
            <span class="c1">// Add placeholder information</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">placeholder</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">text</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">text</span><span class="p">}</span><span class="s2"> [Placeholder: </span><span class="p">${</span><span class="nx">element</span><span class="p">.</span><span class="nx">placeholder</span><span class="p">}</span><span class="s2">]`</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">innerText</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">().</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">\\</span><span class="sr">s+/g</span><span class="p">,</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">alt</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// For image buttons</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">alt</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">title</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// For elements with title</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">placeholder</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// For placeholder text</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="s2">`[Placeholder: </span><span class="p">${</span><span class="nx">element</span><span class="p">.</span><span class="nx">placeholder</span><span class="p">}</span><span class="s2">]`</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// For input type</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="s2">`[</span><span class="p">${</span><span class="nx">element</span><span class="p">.</span><span class="kd">type</span><span class="p">}</span><span class="s2">]`</span><span class="p">;</span>
        
        <span class="c1">// Add label and placeholder information for text-less input elements</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">tagName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">input</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Get associated label text</span>
            <span class="kd">let</span> <span class="nx">labelText</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">`label[for="</span><span class="p">${</span><span class="nx">element</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">"]`</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="nx">label</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">labelText</span> <span class="o">=</span> <span class="nx">label</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span>
                <span class="p">}</span>
            <span class="p">}</span>
            
            <span class="c1">// Look for parent or sibling label</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">labelText</span><span class="p">)</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="nx">parentLabel</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">closest</span><span class="p">(</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="nx">parentLabel</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nx">labelText</span> <span class="o">=</span> <span class="nx">parentLabel</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">();</span>
                <span class="p">}</span>
            <span class="p">}</span>
            
            <span class="c1">// Add label information</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">labelText</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">text</span> <span class="o">=</span> <span class="s2">`[Label: </span><span class="p">${</span><span class="nx">labelText</span><span class="p">}</span><span class="s2">] </span><span class="p">${</span><span class="nx">text</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span>
            <span class="p">}</span>
            
            <span class="c1">// Add placeholder information</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">placeholder</span><span class="p">)</span> <span class="p">{</span>
                <span class="nx">text</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">text</span><span class="p">}</span><span class="s2"> [Placeholder: </span><span class="p">${</span><span class="nx">element</span><span class="p">.</span><span class="nx">placeholder</span><span class="p">}</span><span class="s2">]`</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">[No text]</span><span class="dl">'</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="c1">// Maximum limit on text length to keep it clear</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">text</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">text</span> <span class="o">=</span> <span class="nx">text</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">97</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">...</span><span class="dl">'</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="c1">// Only add data-manus-id attribute to elements that meet the conditions</span>
    <span class="nx">element</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-manus-id</span><span class="dl">'</span><span class="p">,</span> <span class="s2">`manus-element-</span><span class="p">${</span><span class="nx">validElementIndex</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
                                            
    <span class="c1">// Build selector - using only data-manus-id</span>
    <span class="kd">const</span> <span class="nx">selector</span> <span class="o">=</span> <span class="s2">`[data-manus-id="manus-element-</span><span class="p">${</span><span class="nx">validElementIndex</span><span class="p">}</span><span class="s2">"]`</span><span class="p">;</span>
    
    <span class="c1">// Add element information to the array</span>
    <span class="nx">interactiveElements</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
        <span class="na">index</span><span class="p">:</span> <span class="nx">validElementIndex</span><span class="p">,</span>  <span class="c1">// Use consecutive index</span>
        <span class="na">tag</span><span class="p">:</span> <span class="nx">tagName</span><span class="p">,</span>
        <span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
        <span class="na">selector</span><span class="p">:</span> <span class="nx">selector</span>
    <span class="p">});</span>
    
    <span class="nx">validElementIndex</span><span class="o">++</span><span class="p">;</span> <span class="c1">// Increment valid element counter</span>
<span class="p">}</span>

<span class="k">return</span> <span class="nx">interactiveElements</span><span class="p">;</span>
</code></pre></div></div>

<p>这样大模型就可以根据 ID 号来操作元素了。</p>

<p>还要进行网页信息提取，目前主流做法是先去掉不可见元素后，先转成 markdown，再给大模型进行提取，以节省 Token，如下：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Convert to Markdown
</span><span class="n">markdown_content</span> <span class="o">=</span> <span class="n">markdownify</span><span class="p">(</span><span class="n">visible_content</span><span class="p">)</span>

<span class="n">max_content_length</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="mi">50000</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">markdown_content</span><span class="p">))</span>
<span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="p">.</span><span class="n">llm</span><span class="p">.</span><span class="n">ask</span><span class="p">([{</span>
    <span class="s">"role"</span><span class="p">:</span> <span class="s">"system"</span><span class="p">,</span>
    <span class="s">"content"</span><span class="p">:</span> <span class="s">"You are a professional web page information extraction assistant. Please extract all information from the current page content and convert it to Markdown format."</span>
<span class="p">},</span>
<span class="p">{</span>
    <span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span>
    <span class="s">"content"</span><span class="p">:</span> <span class="n">markdown_content</span><span class="p">[:</span><span class="n">max_content_length</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">])</span>
</code></pre></div></div>

<p>至此，大模型就可以与网页交互与阅读网页信息内容了。</p>

<h1 id="web-ui-设计">Web UI 设计</h1>

<p>Web UI 编写虽然属于我的软肋，但属于 Cursor 的强项，结合对正版 Manus 的借鉴也可以搞得七七八八，页面比较简单。</p>

<h1 id="如何部署">如何部署？</h1>

<h2 id="环境要求">环境要求</h2>

<p>本项目主要依赖 Docker 进行开发与部署，需要安装较新版本的 Docker：</p>

<ul>
  <li>Docker 20.10+</li>
  <li>Docker Compose</li>
</ul>

<p>模型能力也是要求比较高：</p>

<ul>
  <li>兼容 OpenAI 接口</li>
  <li>支持 FunctionCall</li>
  <li>支持 Json Format 输出</li>
</ul>

<p>推荐 Deepseek 与 ChatGPT。</p>

<h2 id="部署">部署</h2>

<p>推荐使用 Docker Compose 进行部署，</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
  <span class="na">frontend</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">hub.byted.org/byvirt/manus-frontend</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">5173:80"</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">backend</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">manus-network</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">BACKEND_URL=http://backend:8000</span>

  <span class="na">backend</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">hub.byted.org/byvirt/manus-backend</span>
    <span class="na">depends_on</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">sandbox</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/var/run/docker.sock:/var/run/docker.sock:ro</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">manus-network</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="c1"># OpenAI API base URL</span>
      <span class="pi">-</span> <span class="s">API_BASE=https://api.openai.com/v1</span>
      <span class="c1"># OpenAI API key, replace with your own</span>
      <span class="pi">-</span> <span class="s">API_KEY=sk-xxxx</span>
      <span class="c1"># LLM model name</span>
      <span class="pi">-</span> <span class="s">MODEL_NAME=gpt-4o</span>
      <span class="c1"># LLM temperature parameter, controls randomness</span>
      <span class="pi">-</span> <span class="s">TEMPERATURE=0.7</span> 
      <span class="c1"># Maximum tokens for LLM response</span>
      <span class="pi">-</span> <span class="s">MAX_TOKENS=2000</span>
      <span class="c1"># Google Search API key for web search capability</span>
      <span class="c1">#- GOOGLE_SEARCH_API_KEY=</span>
      <span class="c1"># Google Custom Search Engine ID</span>
      <span class="c1">#- GOOGLE_SEARCH_ENGINE_ID=</span>
      <span class="c1"># Application log level</span>
      <span class="pi">-</span> <span class="s">LOG_LEVEL=INFO</span>
      <span class="c1"># Docker image used for the sandbox</span>
      <span class="pi">-</span> <span class="s">SANDBOX_IMAGE=hub.byted.org/byvirt/manus-sandbox</span>
      <span class="c1"># Prefix for sandbox container names</span>
      <span class="pi">-</span> <span class="s">SANDBOX_NAME_PREFIX=sandbox</span>
      <span class="c1"># Time-to-live for sandbox containers in minutes</span>
      <span class="pi">-</span> <span class="s">SANDBOX_TTL_MINUTES=30</span>
      <span class="c1"># Docker network for sandbox containers</span>
      <span class="pi">-</span> <span class="s">SANDBOX_NETWORK=manus-network</span>

  <span class="na">sandbox</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">hub.byted.org/byvirt/manus-sandbox</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">/bin/sh -c "exit 0"</span>  <span class="c1"># prevent sandbox from starting, ensure image is pulled</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s2">"</span><span class="s">no"</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">manus-network</span>

<span class="na">networks</span><span class="pi">:</span>
  <span class="na">manus-network</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">manus-network</span>
    <span class="na">driver</span><span class="pi">:</span> <span class="s">bridge</span>
</code></pre></div></div>

<h1 id="如何开发">如何开发？</h1>

<h2 id="环境准备">环境准备</h2>

<p>环境要求在部署章节已经做了说明。</p>

<p>下载项目：</p>

<pre><code class="language-Bash">git clone https://github.com/Simpleyyt/ai-manus.git
cd ai-manus
</code></pre>

<p>复制配置文件：</p>

<pre><code class="language-Bash">cp .env.example .env
</code></pre>

<p>修改配置文件：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Model provider configuration</span>
<span class="nv">API_KEY</span><span class="o">=</span>
<span class="nv">API_BASE</span><span class="o">=</span>https://api.openai.com/v1

<span class="c"># Model configuration</span>
<span class="nv">MODEL_NAME</span><span class="o">=</span>gpt-4o
<span class="nv">TEMPERATURE</span><span class="o">=</span>0.7
<span class="nv">MAX_TOKENS</span><span class="o">=</span>2000

<span class="c"># Optional: Google search configuration</span>
<span class="c">#GOOGLE_SEARCH_API_KEY=</span>
<span class="c">#GOOGLE_SEARCH_ENGINE_ID=</span>

<span class="c"># Sandbox configuration</span>
<span class="nv">SANDBOX_IMAGE</span><span class="o">=</span>simpleyyt/sandbox
<span class="nv">SANDBOX_NAME_PREFIX</span><span class="o">=</span>sandbox
<span class="nv">SANDBOX_TTL_MINUTES</span><span class="o">=</span>30
<span class="nv">SANDBOX_NETWORK</span><span class="o">=</span>manus-network

<span class="c"># Log configuration</span>
<span class="nv">LOG_LEVEL</span><span class="o">=</span>INFO
</code></pre></div></div>

<h2 id="开发">开发</h2>

<blockquote>
  <p>开发模式下只会全局启动一个沙盒。</p>
</blockquote>

<p>运行调试：</p>

<pre><code class="language-Bash"># 相当于 docker compose -f docker-compose-development.yaml up
./dev.sh up
</code></pre>

<p>Web、Sandbox、Server 都会以 reload 模式运行，即代码改动会自动 reload。暴露的端口如下：</p>

<ul>
  <li><strong>5173:</strong> Web 前端端口。</li>
  <li><strong>8000:</strong> Server API 服务端口。</li>
  <li><strong>8080:</strong> Sandbox API 服务端口。</li>
  <li><strong>5900:</strong> Sandbox VNC 端口。</li>
  <li><strong>9222:</strong> Sandbox Chrome 浏览器 CDP 端口。</li>
</ul>

<p>当依赖变化时，即<code class="language-plaintext highlighter-rouge">requirements.txt</code>或者<code class="language-plaintext highlighter-rouge">package.json</code>变化时，可以清理并重新构建一下：</p>

<pre><code class="language-Bash"># 清理掉所有相关资源
./dev.sh down -v

# 重新构建镜像
./dev.sh build

# 调试运行
./dev.sh up
</code></pre>

<h2 id="发布">发布</h2>

<pre><code class="language-Bash">export IMAGE_REGISTRY=xxxx
export IMAGE_TAG=latest

# 构建镜像
./run build

# 推送到相应的镜像仓库
./run push
</code></pre>

<h1 id="写在最后">写在最后</h1>

<p>本项目主要用于学习与研究目的，共同学习和进步，也是代码工程师未来跃变提词工程师做点准备。</p>]]></content><author><name>Simpleyyt</name></author><category term="AI" /><category term="llm" /><category term="agent" /><category term="manus" /><summary type="html"><![CDATA[最近在学习 LLM Agent，但终觉“纸上学来终觉浅，绝知此事要躬行”，所以想写个小项目试试手。在这个人均写一个 Manus 的时代，在这个半开卷的情况下，况且 Manus 的提词已经泄漏的情况下，是不是我也可以写一个，我整理了一下自己的需求如下： 可单主机云上部署，好部署在我家里的 Server 上。 集成浏览器、Shell、Python、Node 等工具及 Ubuntu 沙盒环境，每一个任务分配一个沙盒。 Web UI 与提示词直接借鉴官方 Manus 的。 结合 Cursor 应该可以快速地写出一个 Manus 的示例，听说 OpenManus 4 小时就写出来了。 项目地址：https://github.com/Simpleyyt/ai-manus]]></summary></entry><entry><title type="html">Google Maglev 简介</title><link href="https://simpleyyt.com/2017/07/05/introduction-to-google-maglev/" rel="alternate" type="text/html" title="Google Maglev 简介" /><published>2017-07-05T00:00:00+00:00</published><updated>2017-07-05T00:00:00+00:00</updated><id>https://simpleyyt.com/2017/07/05/introduction-to-google-maglev</id><content type="html" xml:base="https://simpleyyt.com/2017/07/05/introduction-to-google-maglev/"><![CDATA[<p>Google Maglev 是一个牛逼的负载均衡器，之所以牛逼，是因为它不用部署专门的物理设备，不像 LVS 一样工作在内核，它是运行在通用 Linux 服务器上的大型分布式软件系统。</p>

<!--more-->

<h1 id="google-maglev-工作流程">Google Maglev 工作流程</h1>

<p><img src="https://nos.netease.com/knowledge/3db49483-ce09-4384-9eec-4963db7c0950" alt="Google Maglev 工作流程" /></p>

<p>每个 Google 服务都有一个或者多个 VIP，一个 VIP 和物理 IP 的区别在于 VIP 没有绑给某个特定的网卡。</p>

<p><strong>VIP 宣告：</strong></p>

<p>Maglev 关联每个 VIP 到具体的 Endpoint，然后通过 BGP 将 VIP 宣告给上游路由器，然后路由器再把 VIP 宣告给 Google 的骨干网，这样使得 VIP 能被访问到。</p>

<p><strong>当用户访问 www.google.com 时：</strong></p>

<ul>
  <li>浏览器先发送一个 DNS 请求， DNS 服务返回 VIP。 然后浏览器尝试与该 VIP 建立连接。</li>
  <li>当路由器接收到 VIP 数据包，通过 ECMP 将数据包路由到 Maglev 集群中的某台机器上。</li>
  <li>当 Maglev 的机器接收到数据包， 从关联到该 VIP 的 Endpoint 中选择一个， 然后用 GRE 封包发送，外层的 IP 即 Endpoint 的物理 IP。</li>
  <li>当 Endpoint 处理完数据包进行响应时，源地址用 VIP 填充，目的地址为用户 IP。 使用直接服务返回(Direct Server Return， DSR) ，将响应直接发送给路由器， 这样 Maglev 无需处理响应包。</li>
</ul>

<h1 id="google-maglev-结构">Google Maglev 结构</h1>

<p><img src="https://nos.netease.com/knowledge/987eed70-e65e-4bac-a9e5-81ade6deb8a5" alt="Maglev 结构" /></p>

<p>Maglev 由控制器（Controller）和 转发器（Forwarder）组成：</p>

<ul>
  <li><strong>控制器向路由器宣告 VIP。</strong>控制器周期性地检查转发器的健康状态，来宣告或者撤回 VIP。确保路由器只转发包到健康的 Magvel。</li>
  <li><strong>转发器转发 VIP 流量到 Endpoint。</strong>每个 VIP 都有一个后端池（BP），BP 可能包含 Endpoint 的 IP，也有可能包含其它 BP。每个 BP 会对 Endpoint 进行健康检查，保证数据包转发到健康的 Endpoint。</li>
</ul>

<h1 id="转发器的设计和实现">转发器的设计和实现</h1>

<h2 id="转发器结构">转发器结构</h2>

<p><img src="https://nos.netease.com/knowledge/68165b8d-e769-499c-8b75-ecca8681600b" alt="转发器结构" /></p>

<p>转发器直接从网卡接收数据包，通过 GRE/IP 封包，再将它们发回网卡。 Linux 内核不参与这个过程。</p>

<ul>
  <li>Steering 处理从 NIC（网卡）接收来的数据包，通过五元组（IP地址，源端口，目的IP地址，目的端口和传输层协议）哈希，然后交给不同的接收队列。</li>
  <li>包重写线程从接收队列取包，然后进行后端选择，用 GRE 封包后，发送到传输队列。</li>
  <li>Muxing 轮询从所有的传输队列取包，再发送给网卡。</li>
</ul>

<h2 id="快速包处理">快速包处理</h2>

<p><img src="https://nos.netease.com/knowledge/9688207c-2875-4f4c-81d1-81e8681a1fbb" alt="转发器包处理" /></p>

<p>Maglev 在包处理时绕开了 Linux 内核，因为内核开销非常严重。</p>

<p>如图，转发器和网卡共用数据包池（Packet Pool），转发器中的 Steering 和 Muxing 都通过指针的环形队列，指向该池。</p>

<p><strong>对于 Seering：</strong></p>

<ul>
  <li>当网卡接收到数据包时，放在 Recieved 所指位置，并向前移动指针。</li>
  <li>分发包到接收队列时，向前移动Processed 指针。</li>
  <li>预留未使用数据包，放入队列中并向前移动 Reserved 指针。</li>
</ul>

<p><strong>对于 Muxing：</strong></p>

<ul>
  <li>网卡将 Sent 所指的数据包发出去，并向前移动指针。</li>
  <li>将被重写的数据包放入队列中，并向前移动 Ready 指针。</li>
  <li>同时将已被发送的包归回给数据包池，并向前移动 Recycled 指针。</li>
</ul>

<p>整个过程都没有包拷贝。</p>

<h2 id="后端选择">后端选择</h2>

<p>Maglev首先检查本地的连接跟踪表，看看该数据包是否属于任何一个已有的连接，如果连接已经建立，则直接将数据包发到该连接对应的服务器上去。如果连接没有建立，此时就需要一致性哈希函数选择一个后端服务器了，并添加到连接跟踪表。</p>

<p>Maglev 一致性哈希的基本思想就是：</p>

<ul>
  <li>有一个共享的<code class="language-plaintext highlighter-rouge">Entry</code>表，可以通过<code class="language-plaintext highlighter-rouge">Entry[Hash % M]</code>选择对应后端，<code class="language-plaintext highlighter-rouge">M</code>为<code class="language-plaintext highlighter-rouge">Entry</code>表大小。</li>
  <li>每个后端对所有<code class="language-plaintext highlighter-rouge">Entry</code>表位置有自己的优先级排序，存在<code class="language-plaintext highlighter-rouge">permutation</code>表里。</li>
  <li>所有的后端通过优先级顺序轮流填充<code class="language-plaintext highlighter-rouge">Entry</code> 中的空白位置，直至填满。每次都填充自己优先级最高的空位置。</li>
</ul>

<p>例如，假设<code class="language-plaintext highlighter-rouge">M = 6</code>，且</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>B0: permutation[] = { 3, 0, 4, 1, 5, 2, 6 }
B1: permutation[] = { 0, 2, 4, 6, 1, 3, 5 }
B2: permutation[] = { 3, 4, 5, 6, 0, 1, 2 }
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">permutation</code>优先级是从大到小，那么最后填充后的<code class="language-plaintext highlighter-rouge">Entry</code>表为：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Entry[] = { B1, B0, B1, B0, B2, B2, B0 }
</code></pre></div></div>

<p>说明：</p>

<blockquote>
  <ul>
    <li>B0 的优先级最高的位置是<code class="language-plaintext highlighter-rouge">Entry[3]</code>，其为空，则<code class="language-plaintext highlighter-rouge">Entry[3] = B0</code>。</li>
    <li>B1 是<code class="language-plaintext highlighter-rouge">Entry[0]</code>，则<code class="language-plaintext highlighter-rouge">Entry[0]=B1</code>。</li>
    <li>B2 是<code class="language-plaintext highlighter-rouge">Entry[3]</code>，但是已被占，下一个是<code class="language-plaintext highlighter-rouge">Entry[4]</code>，为空，则<code class="language-plaintext highlighter-rouge">Entry[4]=B2</code>。</li>
    <li>以此类推，直至填满。</li>
  </ul>
</blockquote>

<h1 id="操作经验">操作经验</h1>

<h2 id="vip-匹配">VIP 匹配</h2>

<p><img src="https://nos.netease.com/knowledge/cbe69316-d0fd-469a-9692-95948f0cfb51" alt="VIP匹配" /></p>

<p>有时候，我们需要利用 Maglev 封包将流量重定向到其他的集群中的相同服务，这就有点麻烦了，因为集群间是独立的，我们不知道其它集群相同服务的 VIP。</p>

<p>VIP 可以通过最长前缀匹配，来决定集群，利用最长后缀匹配决定那个后端池。</p>

<p>如图中的例子，当请求 173.194.71.1 时，通过最长前缀（173.194.71.0/24）选择 C2 集群，通过最长后缀（0.0.0.1/8）决定是 Service 1 后端池。</p>

<p>假定 Maglev 需要将流量转发到 C3（173.194.72.0/24），只需要用相同的后缀构造出 VIP （173.194.72.1）进行转发，即可转发到 Maglev 的 Service 1 后端池。</p>

<h2 id="分片处理">分片处理</h2>

<p>分片时，非首个分片只包含三元组（目的IP地址，目的端口和传输层协议），这便无法正确决定如何转发，因为转发根据的是五元组。</p>

<p><strong>解决方法是：</strong></p>

<p>每个 Maglev 配置了一个特殊的后端池，包含所有的 Maglev 机器。</p>

<p>一旦接收到分片， Maglev 用三元组哈希选择特定的 Maglev 作为后端进行转发，将它们重定向给相同的 Maglev。</p>

<p>Maglev 为未分片数据包和第二跳的首分片使用相同的后端决策算法，以保证非分片、首个分片和非首个分片选择同一个后端。</p>

<p>Magelv 维护了一个固定大小的分片表，记录了首分片的转发决策。 当 Maglev 收到一个第二跳非首分片， 会从分片表中查找，若匹配则立即转发； 否则，会缓存到分片表中，直到首分片收到或者老化。</p>]]></content><author><name>Simpleyyt</name></author><category term="Network" /><category term="network" /><summary type="html"><![CDATA[Google Maglev 是一个牛逼的负载均衡器，之所以牛逼，是因为它不用部署专门的物理设备，不像 LVS 一样工作在内核，它是运行在通用 Linux 服务器上的大型分布式软件系统。]]></summary></entry><entry><title type="html">Macbook 中毒小记</title><link href="https://simpleyyt.com/2017/07/01/remove-malware-on-macbook/" rel="alternate" type="text/html" title="Macbook 中毒小记" /><published>2017-07-01T00:00:00+00:00</published><updated>2017-07-01T00:00:00+00:00</updated><id>https://simpleyyt.com/2017/07/01/remove-malware-on-macbook</id><content type="html" xml:base="https://simpleyyt.com/2017/07/01/remove-malware-on-macbook/"><![CDATA[<p>本以为 macbook 很难中毒的，但是就是发生了，我也很无奈。</p>

<!--more-->

<p>中毒的表现为网页被劫持，老是弹 MacKeeper 的广告，中毒的原因可以是我装了一些流氓软件。</p>

<p>浏览器时不时地会弹 ssp.zryydi.com 这个网站，本来我以为很好解决，上网搜了一下，都说用 MalwareBytes 扫描一下就好，然而我扫描不出来任何东西，有点慌了。</p>

<p>我重置了 Safari 和 Chrome，关掉了代理服器，换路由，都没有用，不过频率减小了。因此应该是启动了不该启的进程。</p>

<p>网上说，通过<code class="language-plaintext highlighter-rouge">/Library/LaunchDaemon</code>，<code class="language-plaintext highlighter-rouge">/Library/LaunchAgent</code>和<code class="language-plaintext highlighter-rouge">~/Library/LaunchAgent</code>来查找开机启动的流氓软件。找了一通只删掉了名字奇怪的文件，哎，当时就不应该心慈手软，删了之后还是没有起作用。</p>

<p>正准备重装系统时，我找到了 Adware Removal Pro 这个软件，真是救星，还真的扫出了流氓软件，就是我当时不够狠，没有删的那几个文件，清理之后恢复正常。</p>]]></content><author><name>Simpleyyt</name></author><category term="Daily" /><category term="daily" /><summary type="html"><![CDATA[本以为 macbook 很难中毒的，但是就是发生了，我也很无奈。]]></summary></entry><entry><title type="html">Ngnix 是如何解决 epoll 惊群的</title><link href="https://simpleyyt.com/2017/06/25/how-ngnix-solve-thundering-herd/" rel="alternate" type="text/html" title="Ngnix 是如何解决 epoll 惊群的" /><published>2017-06-25T00:00:00+00:00</published><updated>2017-06-25T00:00:00+00:00</updated><id>https://simpleyyt.com/2017/06/25/how-ngnix-solve-thundering-herd</id><content type="html" xml:base="https://simpleyyt.com/2017/06/25/how-ngnix-solve-thundering-herd/"><![CDATA[<p>Ngnix 的 master 进程在创建 socket，<code class="language-plaintext highlighter-rouge">bind()</code>和<code class="language-plaintext highlighter-rouge">listen()</code>之后，<code class="language-plaintext highlighter-rouge">fork()</code>出多个 worker，worker 会 将这个 socket 加入 epoll 中，用<code class="language-plaintext highlighter-rouge">epoll_wait()</code>来处理事件，当有一个新的连接来的时候，所有 worker 都会被唤醒，这就是所谓的 epoll 惊群。</p>

<!--more-->

<h2 id="epoll-惊群">epoll 惊群</h2>

<p>epoll 有两种工作方式：LT（水平触发） 和 ET（边缘触发）。LT 即只要有事件就通知，而 ET 则只有状态变化时才会通知。</p>

<p>LT 状态下，只要有通知，所有监听这个 socket 的线程都会被唤醒。</p>

<p>ET 状态下，内核只会通知一次（一个线程），因此无论是<code class="language-plaintext highlighter-rouge">accept()</code>、<code class="language-plaintext highlighter-rouge">read()</code>还是<code class="language-plaintext highlighter-rouge">write()</code>都要循环操作到底层返回<code class="language-plaintext highlighter-rouge">EAGAIN</code>为止。</p>

<p>但是 ET 也会有竞争问题：线程A的<code class="language-plaintext highlighter-rouge">epoll_wait()</code>返回后，线程 A 不断的调用<code class="language-plaintext highlighter-rouge">accept()</code>处理连接请求，当内核的<code class="language-plaintext highlighter-rouge">accept queue</code>队列中的请求恰好处理完时候，内核会重新将该 socket 置为不可读状态，以便可以重新被触发；此时如果新来了一个连接，那么另外一个线程 B 可能被唤醒，然后执行<code class="language-plaintext highlighter-rouge">accept()</code>操作，不过此时之前的线程 A 还需要重新再执行一次<code class="language-plaintext highlighter-rouge">accept()</code>以确认<code class="language-plaintext highlighter-rouge">accept queue</code>已经被处理完了，此时如果线程A成功<code class="language-plaintext highlighter-rouge">accept</code>的话，线程 B 就被惊醒了（线程 B 没有<code class="language-plaintext highlighter-rouge">accept</code>成功）。</p>

<p>历史上还存在过 accept 惊群，但现在的内核已经解决了这个问题，内核只会唤醒一个进程。</p>

<h2 id="ngnix-的解决方法">Ngnix 的解决方法</h2>

<p>Ngnix 目前有几种方法解决惊群问题。</p>

<h3 id="accept_mutex-锁">accept_mutex 锁</h3>

<p>如果开启了<code class="language-plaintext highlighter-rouge">accept_mutex</code>锁，每个 worker 都会先去抢自旋锁，只有抢占成功了，才把 socket 加入到 epoll 中，accept 请求，然后释放锁。<code class="language-plaintext highlighter-rouge">accept_mutex</code>锁也有负载均衡的作用。</p>

<p><code class="language-plaintext highlighter-rouge">accept_mutex</code>效率低下，特别是在长连接的时候。因为长连接时，一个进程长时间占用<code class="language-plaintext highlighter-rouge">accept_mutex</code>锁，使得其它进程得不到 accept 的机会。因此不建议使用，默认是关闭的。</p>

<h3 id="epollexclusive-标识">EPOLLEXCLUSIVE 标识</h3>

<p><code class="language-plaintext highlighter-rouge">EPOLLEXCLUSIVE</code>是4.5+内核新添加的一个 epoll 的标识，Ngnix  在 1.11.3 之后添加了<code class="language-plaintext highlighter-rouge">NGX_EXCLUSIVE_EVENT</code>。</p>

<p><code class="language-plaintext highlighter-rouge">EPOLLEXCLUSIVE</code>标识会保证一个事件发生时候只有一个线程会被唤醒，以避免多侦听下的“惊群”问题。不过任一时候只能有一个工作线程调用 accept，限制了真正并行的吞吐量。</p>

<h3 id="so_reuseport-选项">SO_REUSEPORT 选项</h3>

<p><code class="language-plaintext highlighter-rouge">SO_REUSEPORT</code> 是惊群最好的解决方法，Ngnix 在 1.9.1 中加入了这个选项，每个 worker 都有自己的 socket，这些 socket 都<code class="language-plaintext highlighter-rouge">bind</code>同一个端口。当新请求到来时，内核根据四元组信息进行负载均衡，非常高效。</p>

<p><em>参考：</em></p>

<ul>
  <li><a href="http://xiaorui.cc/2015/12/02/使用socket-so_reuseport提高服务端性能/">http://xiaorui.cc/2015/12/02/使用socket-so_reuseport提高服务端性能/</a></li>
  <li><a href="http://pureage.info/2015/12/22/thundering-herd.html">http://pureage.info/2015/12/22/thundering-herd.html</a></li>
  <li><a href="http://www.cnblogs.com/sxhlinux/p/6254396.html">http://www.cnblogs.com/sxhlinux/p/6254396.html</a></li>
  <li><a href="http://m.blog.csdn.net/russell_tao/article/details/7204260">http://m.blog.csdn.net/russell_tao/article/details/7204260</a></li>
  <li><a href="https://www.zhihu.com/question/51618274?utm_medium=social&amp;utm_source=wechat_session&amp;from=groupmessage&amp;isappinstalled=1">https://www.zhihu.com/question/51618274?utm_medium=social&amp;utm_source=wechat_session&amp;from=groupmessage&amp;isappinstalled=1</a></li>
  <li><a href="http://www.sohu.com/a/148006569_470018">http://www.sohu.com/a/148006569_470018</a></li>
</ul>]]></content><author><name>Simpleyyt</name></author><category term="Network" /><category term="network" /><category term="linux" /><summary type="html"><![CDATA[Ngnix 的 master 进程在创建 socket，bind()和listen()之后，fork()出多个 worker，worker 会 将这个 socket 加入 epoll 中，用epoll_wait()来处理事件，当有一个新的连接来的时候，所有 worker 都会被唤醒，这就是所谓的 epoll 惊群。]]></summary></entry><entry><title type="html">Hello World 的编译链接概述</title><link href="https://simpleyyt.com/2017/02/12/helloworld-compile-and-link/" rel="alternate" type="text/html" title="Hello World 的编译链接概述" /><published>2017-02-12T00:00:00+00:00</published><updated>2017-02-12T00:00:00+00:00</updated><id>https://simpleyyt.com/2017/02/12/helloworld-compile-and-link</id><content type="html" xml:base="https://simpleyyt.com/2017/02/12/helloworld-compile-and-link/"><![CDATA[<p>《程序员的自我修养》读书笔记，简单概述一个 Hello World 程序的编译链接过程。</p>

<!--more-->

<p>有如下的<code class="language-plaintext highlighter-rouge">hello.c</code>源文件：</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Hello World</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>可以用 GCC 来编译运行：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$gcc</span> hello.c
<span class="nv">$.</span>/a.out
Hello World
</code></pre></div></div>

<p>上述步骤可以分解为 4 个步骤，分别是<strong>预处理（Prepressing）</strong>、<strong>编译（Compilation）</strong>、<strong>汇编（Assembly）</strong>和<strong>链接（Linking）</strong>。</p>

<h2 id="被隐藏了的过程">被隐藏了的过程</h2>

<h3 id="预处理">预处理</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$gcc</span> <span class="nt">-E</span> hello.c <span class="nt">-o</span> hello.i
</code></pre></div></div>

<p>或者：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$cpp</span> hello.c <span class="o">&gt;</span> hello.i
</code></pre></div></div>

<p>预编译过程主要处理那些源代码文件听以“#”开始的预编译指令。</p>

<p>经过预编译后的 .i 文件不包含任何宏定义，因为所有的宏已经被展开，并且包含的文件也已经被插入到 .i 文件中。</p>

<h3 id="编译">编译</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$gcc</span> <span class="nt">-S</span> hello.i <span class="nt">-o</span> hello.S
</code></pre></div></div>

<p>或者使用<code class="language-plaintext highlighter-rouge">cc1</code>：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$/</span>usr/lib/gcc/i486-linux-gnu/4.1/cc1 hello.c
</code></pre></div></div>

<p>编译过程就是把预处理完的文件进行一系列<strong>词法分析</strong>、<strong>语法分析</strong>、<strong>语义分析</strong>及<strong>优化</strong>后产生的相应的汇编代码文件。</p>

<p>实际上 gcc 这个命令只是这些后台程序的包装，它会根据不同的参数要求去调用预编译编译程序 cc1、汇编器 as、链接器 ld。</p>

<h3 id="汇编">汇编</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$as</span> hello.s <span class="nt">-o</span> hello.c
</code></pre></div></div>
<p>或者：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$gcc</span> <span class="nt">-c</span> hello.s <span class="nt">-o</span> hello.o
</code></pre></div></div>

<p>汇编器是将汇编代码转变成机器可以执行的指令，每一个汇编语句几乎都对应一条机器指令。</p>

<h3 id="链接">链接</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ld</span> <span class="nt">-static</span> crt1.o crti.o crtbeginT.o hello.o <span class="nt">-start-group</span> <span class="nt">-lgcc</span> <span class="nt">-lgcc_eh</span> <span class="nt">-lc</span> <span class="nt">-end-group</span> crtend. crtn.o
</code></pre></div></div>

<h2 id="编译器做了什么">编译器做了什么</h2>

<p>编译过程一般可以分为6步：扫描、语法分析、语义分析、源代码优化、代码生成和目标代码优化。</p>

<p>以如下代码为例：</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">array</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">index</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">6</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="词法分析">词法分析</h3>

<p>源代码被输入到<strong>扫描器（Scanner）</strong>，进行词法分析，生成一系列的<strong>记号（Token）</strong>。</p>

<table>
  <thead>
    <tr>
      <th>记号</th>
      <th>类型</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>array</td>
      <td>标识符</td>
    </tr>
    <tr>
      <td>[</td>
      <td>左方括号</td>
    </tr>
    <tr>
      <td>index</td>
      <td>标识符</td>
    </tr>
    <tr>
      <td>]</td>
      <td>右方括号</td>
    </tr>
    <tr>
      <td>=</td>
      <td>赋值</td>
    </tr>
    <tr>
      <td>(</td>
      <td>左圆括号</td>
    </tr>
    <tr>
      <td>index</td>
      <td>标识符</td>
    </tr>
    <tr>
      <td>+</td>
      <td>加号</td>
    </tr>
    <tr>
      <td>4</td>
      <td>数字</td>
    </tr>
    <tr>
      <td>)</td>
      <td>右圆括号</td>
    </tr>
    <tr>
      <td>*</td>
      <td>乘号</td>
    </tr>
    <tr>
      <td>(</td>
      <td>左圆括号</td>
    </tr>
    <tr>
      <td>2</td>
      <td>数字</td>
    </tr>
    <tr>
      <td>+</td>
      <td>加号</td>
    </tr>
    <tr>
      <td>6</td>
      <td>数字</td>
    </tr>
    <tr>
      <td>)</td>
      <td>右圆括号</td>
    </tr>
  </tbody>
</table>

<p>在识别记号的同时，扫描器也将标识符存放到符号表，将数字、字符串常量存放到文字表等。</p>

<p>有一个叫 lex 的程序可以实现词法分析，它会按照用户之前描述好的词法规则将输入的字符串分割成一个个记号。</p>

<h3 id="语法分析">语法分析</h3>

<p><strong>语法分析器（Grammar Parser）</strong>将上述产生的记号进行语法分析，从而生成<strong>语法树（Syntax Tree）</strong>。</p>

<p>有一个现有的工具叫 yacc（Yet Another Compiler Compiler）可以构建出语法树。</p>

<h3 id="语义分析">语义分析</h3>

<p><strong>语义分析器（Semantic Analyzer）</strong>完成了对表达式的语法层面的分析，即<strong>静态语义（Static Semantic）</strong>，包括声明和类型匹配，类型的转换。</p>

<p>语义分析阶段后，语法树的表达式都被标识了类型，如有隐式转换，则插入相应的转换节点。</p>

<h3 id="中间语言的生成">中间语言的生成</h3>

<p><strong>源码优化器（Source Code Optimizer）</strong>将整个语法树转换成<strong>中间代码（Intermediate Code）</strong>，常见的有<strong>三地址码（Three-address Code）</strong>和<strong>P-代码（P-Code）</strong>，再进行源码级别的优化。</p>

<p>中间代码使用编译器可以被分为前端和后端。编译器前端负责产生机器无关的中间代码，编译器后端将中间代码转换成目标机器代码。</p>

<h3 id="目标代码生成与优化">目标代码生成与优化</h3>

<p><strong>代码生成（Code Generator）</strong>将中间代码转换成目标机器代码，<strong>目标代码化化器（Target Code Optimizer）</strong>则对目标代码进行优化。</p>

<h2 id="静态链接">静态链接</h2>

<p><strong>链接（Linking）</strong>的主要内容就是把各个模块之间相互引用的部分都处理好。</p>

<p>链接过程主要包括了<strong>地址和空间分配（Address and Storage Allocation）</strong>、<strong>符号决议（Symbol Resolution）</strong>和<strong>重定位（Relocation）</strong>这些步骤。</p>]]></content><author><name>Simpleyyt</name></author><category term="Program" /><category term="cpp" /><category term="compiler" /><summary type="html"><![CDATA[《程序员的自我修养》读书笔记，简单概述一个 Hello World 程序的编译链接过程。]]></summary></entry><entry><title type="html">彩虹表原理</title><link href="https://simpleyyt.com/2016/12/24/how-rainbow-table-work/" rel="alternate" type="text/html" title="彩虹表原理" /><published>2016-12-24T00:00:00+00:00</published><updated>2016-12-24T00:00:00+00:00</updated><id>https://simpleyyt.com/2016/12/24/how-rainbow-table-work</id><content type="html" xml:base="https://simpleyyt.com/2016/12/24/how-rainbow-table-work/"><![CDATA[<p>以前一直以为彩虹表就是一个大的数据库，破解的时候直接通过查表获得明文。今天发现我错了，彩虹表的原理还是挺有趣的。</p>

<!--more-->

<p>如果直接暴力破解 Hash 的明文太费时，如果通过查表的方法则太耗存储。彩虹表则是暴力破解与查表破解的折中。</p>

<h2 id="原理">原理</h2>

<p>假设有这么一个函数<code class="language-plaintext highlighter-rouge">R</code>，可以将 Hash 值转化与字符串（非明文，而是另一个字符串），称其为衰减函数。假设<code class="language-plaintext highlighter-rouge">p</code>为明文，<code class="language-plaintext highlighter-rouge">h</code>为明文的 Hash 值，<code class="language-plaintext highlighter-rouge">H</code>为 Hash 函数。彩虹表在建表时，先选一个<code class="language-plaintext highlighter-rouge">p[0]</code>，做如下计算：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>h[0] = H(p[0])
p[1] = R(h[0])
h[1] = H(p[1])
...
h[k]  = H(p[k])
...
h[n - 1] = H(p[n - 1])
p[n] = R(h[n - 1])
</code></pre></div></div>

<p><em>其中<code class="language-plaintext highlighter-rouge">n</code>为迭代的次数。</em></p>

<p>经过，这种运算之后，会形成一条 Hash 链：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>p[0]---&gt;h[0]---&gt;p[1]---&gt;...---&gt;p[k]---&gt;h[k]---&gt;...---&gt;p[n]
</code></pre></div></div>

<p>这条链上已经有<code class="language-plaintext highlighter-rouge">n</code>对明文到 Hash 值的对应关系了，但我们只保存首尾<code class="language-plaintext highlighter-rouge">p[0]</code>和<code class="language-plaintext highlighter-rouge">p[n]</code>，因为中间的值可以用<code class="language-plaintext highlighter-rouge">p[0]</code>重新通过上述的计算运算出来。</p>

<p>彩虹表中有好多条这样的链<code class="language-plaintext highlighter-rouge"> p[0]--&gt;p[n]</code>，<code class="language-plaintext highlighter-rouge">n</code>值取足够大。这样就可以用<code class="language-plaintext highlighter-rouge">p[0]</code>和<code class="language-plaintext highlighter-rouge">p[n]</code>的存储空间，存储<code class="language-plaintext highlighter-rouge">n</code>对的明文到 Hash 值的对应关系。</p>

<h2 id="破解">破解</h2>

<p>假设待破解的 Hash 值为<code class="language-plaintext highlighter-rouge">h'</code>，那么我们可以反复地迭代运算 <code class="language-plaintext highlighter-rouge">p' = R(h'), h‘= H(p')</code>，直到<code class="language-plaintext highlighter-rouge">p'</code>与彩虹表中的某个<code class="language-plaintext highlighter-rouge">p[n]</code>相等。</p>

<p>该链为<code class="language-plaintext highlighter-rouge">p[0]--&gt;p[n]</code>，那么<code class="language-plaintext highlighter-rouge">h'</code>的明文很有可能在这条链中，因为这条链是通过<code class="language-plaintext highlighter-rouge">p[0]</code>反复迭代得到<code class="language-plaintext highlighter-rouge">p[n]</code>的，<code class="language-plaintext highlighter-rouge">h'</code>也是通过反复迭代得到<code class="language-plaintext highlighter-rouge">p[n]</code>。</p>

<p>我们只要知道<code class="language-plaintext highlighter-rouge">h'</code>迭代到<code class="language-plaintext highlighter-rouge">p[n]</code>的次数，只在找到链中迭代到<code class="language-plaintext highlighter-rouge">p[n]</code>相同次数的<code class="language-plaintext highlighter-rouge">h[k]</code>，就可以获得其明文<code class="language-plaintext highlighter-rouge">p[k]</code>（在链中，<code class="language-plaintext highlighter-rouge">h[k] = H(p[k])</code>）。</p>]]></content><author><name>Simpleyyt</name></author><category term="Network" /><category term="network" /><summary type="html"><![CDATA[以前一直以为彩虹表就是一个大的数据库，破解的时候直接通过查表获得明文。今天发现我错了，彩虹表的原理还是挺有趣的。]]></summary></entry><entry><title type="html">支付宝 AR 红包破解</title><link href="https://simpleyyt.com/2016/12/21/alipay-ar-rb-crack/" rel="alternate" type="text/html" title="支付宝 AR 红包破解" /><published>2016-12-21T00:00:00+00:00</published><updated>2016-12-21T00:00:00+00:00</updated><id>https://simpleyyt.com/2016/12/21/alipay-ar-rb-crack</id><content type="html" xml:base="https://simpleyyt.com/2016/12/21/alipay-ar-rb-crack/"><![CDATA[<p>网上有好多支付宝的 AR 红包的破解方法，试了一下都不尽人意，所以自己撸了一下简单的。</p>

<!--more-->

<p>破解的想法非常简单：</p>

<ul>
  <li>遍历每一行，求 RGB 值的方差。</li>
  <li>如果方差小于某个阈值则该行为小横条，用上面几行替换。</li>
</ul>

<p>网上还有好多其他的方法，比如先手工获得小横条位置、识别小横条的灰色，我就不折腾了。</p>

<p>最后的效果没有那么好，有些识别不出来，亮色点的图片识别率会高一点。我识别出了周围的4个红包，还是挺麻烦的，玩玩就好。</p>

<p>代码如下：</p>

<script src="https://gist.github.com/02683accd59e309d574e9b499edafc7d.js"> </script>]]></content><author><name>Simpleyyt</name></author><category term="Daily" /><category term="daily" /><summary type="html"><![CDATA[网上有好多支付宝的 AR 红包的破解方法，试了一下都不尽人意，所以自己撸了一下简单的。]]></summary></entry><entry><title type="html">C++ 函数重载与函数匹配</title><link href="https://simpleyyt.com/2016/12/17/function-overload-and-match/" rel="alternate" type="text/html" title="C++ 函数重载与函数匹配" /><published>2016-12-17T00:00:00+00:00</published><updated>2016-12-17T00:00:00+00:00</updated><id>https://simpleyyt.com/2016/12/17/function-overload-and-match</id><content type="html" xml:base="https://simpleyyt.com/2016/12/17/function-overload-and-match/"><![CDATA[<p>《C++ Primer》笔记，整理关于函数重载与函数匹配的笔记。</p>

<!--more-->

<h2 id="函数重载">函数重载</h2>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">);</span> <span class="c1">//原函数</span>

<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">double</span> <span class="n">a</span><span class="p">);</span> <span class="c1">//正确：形参类型不同</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">);</span> <span class="c1">// 正确：形参个数不同</span>
<span class="kt">int</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">);</span> <span class="c1">//错误：只有返回类型不同</span>

<span class="k">typedef</span> <span class="kt">int</span> <span class="n">int32</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="n">int32</span> <span class="n">a</span><span class="p">);</span> <span class="c1">//与原函数等价：形参类型相同</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="k">const</span> <span class="kt">int</span> <span class="n">a</span><span class="p">);</span> <span class="c1">//与原函数等价：顶层 const 将被忽略</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span><span class="p">);</span> <span class="c1">//与原函数等价：只是省略了形参名字</span>
</code></pre></div></div>

<p><strong>函数重载有如下的规则：</strong></p>

<ul>
  <li>名字相同，形参类型不一样。</li>
  <li>不允许两个函数除了返回类型外其他所有的要素都相同。</li>
  <li>顶层<code class="language-plaintext highlighter-rouge">const</code>的形参无法和没有顶层<code class="language-plaintext highlighter-rouge">const</code>的形参区分。</li>
</ul>

<p>其中返回类型不同时编译时会出错，而类型别名、项层<code class="language-plaintext highlighter-rouge">const</code>、省略形参名字只是重复声明而已，只要不定义，编译就不会出错，比如：</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//只定义了其中一个</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="k">const</span> <span class="kt">int</span> <span class="n">a</span><span class="p">)</span> <span class="p">{}</span>
</code></pre></div></div>

<h2 id="函数匹配">函数匹配</h2>

<h3 id="名字查找">名字查找</h3>

<p>函数匹配的第一步便是<strong>名字查找（name lookup）</strong>，确定<strong>候选函数</strong>。</p>

<p>名字查找有两方面：</p>

<ul>
  <li>常规查找（normal lookup）</li>
  <li>实参决定的查找（argument-dependent lookup，ADL）</li>
</ul>

<p>所有函数调用都会进行常规查找，只有函数的实参包括类类型对象或指向类类型对象的指针/引用的时候，才会进行实参决定的查找。</p>

<p><strong>常规查找</strong></p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">);</span>	<span class="c1">//1</span>
<span class="k">namespace</span> <span class="n">N</span>
<span class="p">{</span>
	<span class="c1">//作用域</span>
	<span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="p">{}</span>	<span class="c1">//2</span>
	<span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="kt">double</span> <span class="n">a</span><span class="p">)</span> <span class="p">{}</span>	<span class="c1">//3</span>
	<span class="p">...</span>
	<span class="kt">void</span> <span class="n">test1</span><span class="p">()</span>
	<span class="p">{</span>
		<span class="n">func</span><span class="p">();</span> <span class="c1">//候选函数为函数2和3</span>
	<span class="p">}</span>
	
	<span class="kt">void</span> <span class="n">test2</span><span class="p">()</span>
	<span class="p">{</span>
		<span class="k">using</span> <span class="o">::</span><span class="n">func</span><span class="p">;</span> <span class="c1">//将函数1加入当前作用域</span>
		<span class="n">func</span><span class="p">();</span> <span class="c1">//候选函数为函数1</span>
	<span class="p">}</span>
	<span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>从函数被调用的局部作用域开始，逐渐向上层寻找被调用的名字，一旦找到就停止向上寻找，将找到的所有名字加入候选函数。</p>

<p>此外，using语句可以将其他作用域的名字引用到当前作用域。</p>

<p><strong>ADL查找</strong></p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">func</span><span class="p">()</span> <span class="p">{}</span> <span class="c1">//1</span>
<span class="c1">//第一个实参所在命名空间</span>
<span class="k">namespace</span> <span class="n">Name1</span> <span class="p">{</span>
    <span class="k">class</span> <span class="nc">T</span> <span class="p">{</span>
        <span class="k">friend</span> <span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="n">T</span><span class="o">&amp;</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//2</span>
    <span class="p">};</span>
    <span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="n">T</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//3</span>
<span class="p">}</span>
<span class="c1">//第二个实参的间接父类所在命名空间</span>
<span class="k">namespace</span> <span class="n">Name00</span> <span class="p">{</span>
    <span class="k">class</span> <span class="nc">T00</span> <span class="p">{</span>
        <span class="k">friend</span> <span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//4</span>
    <span class="p">};</span>
    <span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="p">{}</span> <span class="c1">//5</span>
<span class="p">}</span>
<span class="c1">//第二个实参父类所在命名空间</span>
<span class="k">namespace</span> <span class="n">Name0</span> <span class="p">{</span>
    <span class="k">class</span> <span class="nc">T0</span><span class="o">:</span><span class="k">public</span> <span class="n">Name00</span><span class="o">::</span><span class="n">T00</span> <span class="p">{</span>
        <span class="k">friend</span> <span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//6</span>
    <span class="p">};</span>
    <span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="p">{}</span> <span class="c1">//7</span>
<span class="p">}</span>
<span class="c1">//第二个实参所在命名空间</span>
<span class="k">namespace</span> <span class="n">Name2</span> <span class="p">{</span>
    <span class="k">class</span> <span class="nc">T</span><span class="o">:</span><span class="k">public</span> <span class="n">Name0</span><span class="o">::</span><span class="n">T0</span> <span class="p">{</span>
        <span class="k">friend</span> <span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="n">T</span><span class="o">&amp;</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//8</span>
    <span class="p">};</span>
    <span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="n">T</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//9</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">test</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">Name1</span><span class="o">::</span><span class="n">T</span> <span class="n">t1</span><span class="p">;</span>
    <span class="n">Name2</span><span class="o">::</span><span class="n">T</span> <span class="n">t2</span><span class="p">;</span>
    <span class="c1">//9个函数全是候选函数  </span>
    <span class="c1">//第1个函数是normal lookup找到的</span>
    <span class="c1">//后8个函数全是argument-dependent lookup找到的</span>
    <span class="n">func</span><span class="p">(</span><span class="o">&amp;</span><span class="n">t1</span><span class="p">,</span><span class="n">t2</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>从第一个类类型参数开始，依次遍历所有类类型参数。对于每一个参数，进入其类型定义所在的作用域（类内友元函数也包括在内），并依次进入其基类、间接基类……定义所在的作用域，查找同名函数，并加入候选函数。</p>

<p><strong>注意：</strong>在继承体系中上升的过程中，不会因为找到同名函数就停止上升，这不同于常规查找。</p>

<p>类中的运算符重载也遵循 ADL 查找，其候选函数集既包括成员函数，也应该包括非成员函数。</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">namespace</span> <span class="n">N</span>
<span class="p">{</span>
	<span class="k">class</span> <span class="nc">A</span>
	<span class="p">{</span>
	<span class="nl">public:</span>
		<span class="kt">void</span> <span class="k">operator</span><span class="o">+</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//1</span>
	<span class="p">};</span>
	<span class="kt">void</span> <span class="k">operator</span><span class="o">+</span><span class="p">(</span><span class="n">A</span> <span class="o">&amp;</span><span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//2</span>
<span class="p">};</span>
<span class="kt">void</span> <span class="k">operator</span><span class="o">+</span><span class="p">(</span><span class="n">A</span> <span class="o">&amp;</span><span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//3</span>

<span class="kt">void</span> <span class="n">test</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">N</span><span class="o">::</span><span class="n">A</span> <span class="n">a</span><span class="p">;</span>
	<span class="n">a</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">//1、2、3都是候选函数</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="确定可行函数">确定可行函数</h3>

<p>第二步便是从候选函数中选出可行函数，选择的标准如下：</p>

<ul>
  <li>形参数量与本次调用提供的实参数量相等</li>
  <li>每个实参的类型与对应的形参类型相同，或者能转换成形参类型</li>
</ul>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//以下为候选函数</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">double</span> <span class="n">b</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//可行函数</span>
<span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//可行函数：实参可转化成形参类型</span>
<span class="kt">int</span> <span class="n">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">double</span> <span class="n">b</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//可行函数</span>

<span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">)</span> <span class="p">{}</span> <span class="c1">//非可行函数：形参数量不匹配</span>
<span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">[])</span> <span class="p">{}</span> <span class="c1">//非可行函数：实参不能转换成形参</span>

<span class="kt">void</span> <span class="n">test</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">func</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="寻找最佳匹配">寻找最佳匹配</h3>

<p>从可行函数中选择最匹配的函数，如果有多个形参，则最佳匹配条件为：</p>

<ul>
  <li>该函数每个实参的匹配都不劣于其他可行函数需要的匹配。</li>
  <li>至少有一个实参的匹配优于其他可行函数提供的匹配。</li>
</ul>

<p>否则，发生二义性调用错误。</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//可行函数</span>
<span class="kt">void</span> <span class="nf">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">float</span> <span class="n">b</span><span class="p">)</span> <span class="p">{}</span>
<span class="kt">void</span> <span class="n">func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{}</span>

<span class="kt">void</span> <span class="n">test</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">func</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mf">0.1</span><span class="p">);</span> <span class="c1">//二义性错误：double 向 int 的转换与向 float 的转换一样好</span>
	<span class="n">func</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span> <span class="c1">//调用 void func(int a, int b)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>为了确定最佳匹配，实参类型到形参类型的转换等级如下：</p>

<ol>
  <li>精确匹配：
    <ul>
      <li>实参类型和形参类型相同。</li>
      <li>实参从数组类型或函数类型转换成对应的指针类型。</li>
      <li>向实参添加顶层<code class="language-plaintext highlighter-rouge">const</code>或者从实参中删除顶层<code class="language-plaintext highlighter-rouge">const</code>。</li>
    </ul>
  </li>
  <li>通过<code class="language-plaintext highlighter-rouge">const</code>转换实现的匹配。</li>
  <li>通过类型提升实现的匹配。</li>
  <li>通过算术类型转换或指针转换实现的匹配。</li>
  <li>通过类类型转换实现的匹配。</li>
</ol>

<p><em>一般不会存在这个阶段不会同时存在两个以上的精确匹配，因为两个精确的匹配在本质上是等价的，在定义重载函数时，编译器可能就报出重定义的错误了。</em></p>

<p>挑几个重点的来详细说一下。</p>

<p><strong>指针转换实现的匹配</strong></p>

<ul>
  <li>0 或<code class="language-plaintext highlighter-rouge">nullptr</code>能转换成任意指针类型。</li>
  <li><code class="language-plaintext highlighter-rouge">T *</code> 能转换成 <code class="language-plaintext highlighter-rouge">void *</code>，<code class="language-plaintext highlighter-rouge">const void *</code>转换成<code class="language-plaintext highlighter-rouge">const void*</code>。</li>
  <li>派生类向基类类型的转换。</li>
  <li>函数与函数指针的形参类型必须精确匹配。</li>
</ul>

<p><strong>类类型转换实现的匹配</strong></p>

<p>两个类型提供相同的类型转换将产生二义性问题。</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">B</span><span class="p">;</span>
<span class="k">struct</span> <span class="nc">A</span>
<span class="p">{</span>
	<span class="n">A</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>
	<span class="n">A</span><span class="p">(</span><span class="k">const</span> <span class="n">B</span><span class="o">&amp;</span><span class="p">);</span>	<span class="c1">//把一个 B 转换成 A</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="nc">B</span>
<span class="p">{</span>
	<span class="k">operator</span> <span class="n">A</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span> <span class="c1">// 也是把一个 B 转换成 A</span>
<span class="p">};</span>

<span class="n">A</span> <span class="n">f</span><span class="p">(</span><span class="k">const</span> <span class="n">A</span><span class="o">&amp;</span><span class="p">);</span>

<span class="n">B</span> <span class="n">b</span><span class="p">;</span>
<span class="n">A</span> <span class="n">a</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="n">b</span><span class="p">);</span> <span class="c1">//二义性错误：f(B::operator A()) 还是 f(A::A(const B&amp;))</span>

<span class="n">A</span> <span class="n">a1</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="n">b</span><span class="p">.</span><span class="k">operator</span> <span class="n">A</span><span class="p">());</span> <span class="c1">//正确：使用 B 的类型转换运算</span>
<span class="n">A</span> <span class="n">a2</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="n">A</span><span class="p">(</span><span class="n">b</span><span class="p">));</span> <span class="c1">//正确：使用 A 的构造函数</span>
</code></pre></div></div>

<p>类当中定义了多个参数都是算术类型的构造函数或类型转换运算符，也会产生二义性问题。</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">A</span>
<span class="p">{</span>
	<span class="n">A</span><span class="p">(</span><span class="kt">int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">);</span>
	<span class="n">A</span><span class="p">(</span><span class="kt">double</span><span class="p">);</span>
	<span class="k">operator</span> <span class="kt">int</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
	<span class="k">operator</span> <span class="kt">double</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="n">f</span><span class="p">(</span><span class="kt">long</span> <span class="kt">double</span><span class="p">);</span>

<span class="n">A</span> <span class="n">a</span><span class="p">;</span>
<span class="n">f</span><span class="p">(</span><span class="n">a</span><span class="p">);</span> <span class="c1">//二义性错误：f(A::operator int()) 还是 f(A::operator double())？</span>

<span class="kt">long</span> <span class="n">l</span><span class="p">;</span>
<span class="n">A</span> <span class="n">a2</span><span class="p">(</span><span class="n">l</span><span class="p">);</span> <span class="c1">//二义性错误：A::A(int) 还是 A::A(double)？</span>

<span class="kt">short</span> <span class="n">s</span><span class="p">;</span>
<span class="n">A</span> <span class="n">a3</span><span class="p">(</span><span class="n">s</span><span class="p">);</span> <span class="c1">//正确：使用 A::A(int)</span>
</code></pre></div></div>

<p>当我们使用两个用户定义的类型转换时，如果转换函数之前或之后存在标准类型转换，则标准类型转换将决定最佳匹配到底是哪个。</p>]]></content><author><name>Simpleyyt</name></author><category term="Program" /><category term="cpp" /><summary type="html"><![CDATA[《C++ Primer》笔记，整理关于函数重载与函数匹配的笔记。]]></summary></entry><entry><title type="html">C++ 函数匹配及实参推断</title><link href="https://simpleyyt.com/2016/11/21/function-matching-and-argument-deduction/" rel="alternate" type="text/html" title="C++ 函数匹配及实参推断" /><published>2016-11-21T00:00:00+00:00</published><updated>2016-11-21T00:00:00+00:00</updated><id>https://simpleyyt.com/2016/11/21/function-matching-and-argument-deduction</id><content type="html" xml:base="https://simpleyyt.com/2016/11/21/function-matching-and-argument-deduction/"><![CDATA[<p>记录一下函数重载、函数匹配、名字查找与实参类型推断的规则。</p>

<!--more-->

<h2 id="函数重载">函数重载</h2>

<h3 id="函数重载规则">函数重载规则</h3>

<ul>
  <li>名字相同，形参类型不一样。</li>
  <li>不允许两个函数除了返回类型外其他所有的要素都相同。</li>
  <li>顶层<code class="language-plaintext highlighter-rouge">const</code>的形参无法和没有顶层<code class="language-plaintext highlighter-rouge">const</code>的形参区分。</li>
</ul>

<h3 id="重载与作用域">重载与作用域</h3>

<ul>
  <li>内层作用域中声明的名字，将隐藏外层作用域中声明的同名实体。</li>
  <li>名字查找发生在类型检查之前。</li>
</ul>

<h2 id="名字查找">名字查找</h2>

<ul>
  <li>首先，在名字所在的块中寻找其声明语句，只考虑在名字使用之前出现的声明。</li>
  <li>如果没有找到，继续查找外层作用域。</li>
  <li>如果最终没有找到匹配的声明，则程序报错。</li>
</ul>

<p>对于类的定义：</p>

<ul>
  <li>首先，编译成员的声明。</li>
  <li>直到类全部可见后才编译函数体。</li>
</ul>

<h2 id="函数匹配">函数匹配</h2>

<h3 id="函数匹配步骤">函数匹配步骤</h3>

<ul>
  <li><strong>确定候选函数和可行函数</strong></li>
</ul>

<blockquote>
  <p>选择函数名相同的、声明在调用点可见的函数作用候选函数。
在候选函数中选出形参数量匹配并且类型可转换的函数作用可行函数。</p>
</blockquote>

<ul>
  <li><strong>寻找最佳匹配</strong></li>
</ul>

<blockquote>
  <p>从可行函数中选择最匹配的函数，如果有多个形参，则最佳匹配条件为：</p>

  <ul>
    <li>该函数每个实参的匹配都不劣于其他可行函数需要的匹配。</li>
    <li>至少有一个实参的匹配优于其他可行函数提供的匹配。</li>
  </ul>

  <p>否则，发生二义性调用错误。</p>
</blockquote>

<h2 id="实参类型转换等级">实参类型转换等级</h2>

<p>具体排序如下：</p>

<ul>
  <li>精确匹配：
    <ul>
      <li>实参类型和形参类型相同。</li>
      <li>实参从数组类型或函数类型转换成对应的指针类型。</li>
      <li>向实参添加顶层<code class="language-plaintext highlighter-rouge">const</code>或者从实参中删除顶层<code class="language-plaintext highlighter-rouge">const</code>。</li>
    </ul>
  </li>
  <li>通过<code class="language-plaintext highlighter-rouge">const</code>转换实现的匹配。</li>
  <li>通过类型提升实现的匹配。</li>
  <li>通过算术类型转换或指针转换实现的匹配。</li>
</ul>

<h2 id="函数指针">函数指针</h2>

<p>函数与函数指针的形参类型必须精确匹配。</p>

<h2 id="类类型转换运算符匹配">类类型转换运算符匹配</h2>

<p><strong>如果类类型和目标类型之间存在多种转换的方式，则可能发生二义性错误：</strong></p>

<ul>
  <li>两个类型提供相同的类型转换。</li>
  <li>类定义了多个转换的规则。</li>
</ul>

<p>当我们使用两个用户定义的类型转换时，如果转换函数之前或之后存在标准类型转换，则标准类型转换将决定最佳匹配到底是哪个。</p>

<h2 id="重载函数与转换构造函数">重载函数与转换构造函数</h2>

<p>如果两个或多个类型转换都提供了同一种可行的匹配，则这些类型转换一样好。</p>

<p>在调用重载函数时，如果需要额外的标准类型转换，则该转换的级别只有当所有可行函数请求同一个用户定义的类型转换时才有用。如果所需的用户定义的类型转换不止一个，则该调用具有二义性。</p>

<h2 id="函数匹配与重载运算符">函数匹配与重载运算符</h2>

<p>表达式中运算符的候选函数集既包括成员函数，也应该包括非成员函数。</p>

<h2 id="模板实参推断">模板实参推断</h2>

<h3 id="类型转换与模板类型参数">类型转换与模板类型参数</h3>

<p>能应用于函数模板的类型转换：</p>

<ul>
  <li>顶层 const 会被忽略。</li>
  <li>const 转换：非 const 对象的引用（或指针）传递给一个 const 的引用（或指针）的形参。</li>
  <li>数组或函数指针的转换。</li>
</ul>

<p>一个模板类型参数可以用作多个函数形参的类型。由于只允许有限的几种类型转换，因此传递给这些形参的实参必须具有相同的类型。</p>

<p>正常类型转换应用于显示指定的实参。</p>

<h3 id="函数指针和实参推断">函数指针和实参推断</h3>

<p>当参数是一个函数模板实例的地址时，程序上下文必须満足：对每个模板参数，能唯一确定其类型或值。</p>

<h3 id="重载与模板">重载与模板</h3>

<ul>
  <li>对于一个调用，其候选函数包括所有模板实参推断成功的函数模板实例。</li>
  <li>候选的函数模板总是可行的，因为模板实参推断会排除任何不可行的模板。</li>
  <li>可行函数按类型转换来排序，如果有一个函数比其他函数匹配更好，则选择此函数。</li>
  <li>如果有多个函数提供同样好的匹配则：
    <ul>
      <li>如果同样好的函数中 且个是非模板函数，则选择此函数。</li>
      <li>如果没有非模板函数，如果其中一个模板函数更特例化，则选择此模板。</li>
      <li>否则，此调用有歧义。</li>
    </ul>
  </li>
</ul>

<h2 id="异常类型与-catch-类型的匹配">异常类型与 catch 类型的匹配</h2>

<p>异常类型与<code class="language-plaintext highlighter-rouge">catch</code>类型必须精确匹配：</p>

<ul>
  <li>允许从非常量向常量转换。</li>
  <li>允许从派生类向基类转换。</li>
  <li>数组与函数转换成指针。</li>
</ul>]]></content><author><name>Simpleyyt</name></author><category term="Program" /><category term="cpp" /><summary type="html"><![CDATA[记录一下函数重载、函数匹配、名字查找与实参类型推断的规则。]]></summary></entry><entry><title type="html">C++ 字面值常量</title><link href="https://simpleyyt.com/2016/11/14/cpp-literal/" rel="alternate" type="text/html" title="C++ 字面值常量" /><published>2016-11-14T00:00:00+00:00</published><updated>2016-11-14T00:00:00+00:00</updated><id>https://simpleyyt.com/2016/11/14/cpp-literal</id><content type="html" xml:base="https://simpleyyt.com/2016/11/14/cpp-literal/"><![CDATA[<p>在类型转换和函数匹配中，字面值类型也是非常值得关注的。</p>

<!--more-->

<h2 id="整型和浮点型字面值">整型和浮点型字面值</h2>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">20</span> <span class="cm">/* 十进制 */</span>	<span class="mo">024</span> <span class="cm">/* 八进制 */</span>	<span class="mh">0x14</span> <span class="cm">/* 十六进制 */</span>
</code></pre></div></div>

<p>默认情况下，十进制字面值是带符号数，八进制和十六进制既可能是带符号的也可能是无符号的。</p>

<p>十进制字面值类型是<code class="language-plaintext highlighter-rouge">int</code>、<code class="language-plaintext highlighter-rouge">long</code>和<code class="language-plaintext highlighter-rouge">long long</code>中尺寸最小的。</p>

<p>八进制和十六进制字面值类型是<code class="language-plaintext highlighter-rouge">int</code>、<code class="language-plaintext highlighter-rouge">unsigned int</code>、<code class="language-plaintext highlighter-rouge">long</code>、<code class="language-plaintext highlighter-rouge">unsigned long</code>、<code class="language-plaintext highlighter-rouge">long long</code>和<code class="language-plaintext highlighter-rouge">unsigned long long</code>中的尺寸最小者。</p>

<p>严格来说，十进制字面值不会是负数，负数只是对字面值取负值而已。</p>

<p>默认的，浮点型字面值是一个<code class="language-plaintext highlighter-rouge">double</code>。</p>

<h2 id="转义序列">转义序列</h2>

<p><code class="language-plaintext highlighter-rouge">\x</code>后紧跟1个或多个十六进制数字，或<code class="language-plaintext highlighter-rouge">\</code>后紧跟1个、2个或3个八进制数字。</p>

<h2 id="指定字面值的类型">指定字面值的类型</h2>

<table>
  <thead>
    <tr>
      <th>前缀</th>
      <th>含义</th>
      <th>类型</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>u</td>
      <td>Unicode 16 字符</td>
      <td>char16_t</td>
    </tr>
    <tr>
      <td>U</td>
      <td>Unicode 32 字符</td>
      <td>char32_t</td>
    </tr>
    <tr>
      <td>L</td>
      <td>宽字符</td>
      <td>wchar_t</td>
    </tr>
    <tr>
      <td>u8</td>
      <td>UTF-8（仅用于字符串字面常量）</td>
      <td>char</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>后缀</th>
      <th>最小匹配类型</th>
      <th>后缀</th>
      <th>类型</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>u or U</td>
      <td>unsigned</td>
      <td>f 或 F</td>
      <td>float</td>
    </tr>
    <tr>
      <td>l or L</td>
      <td>long</td>
      <td>l 或 L</td>
      <td>long double</td>
    </tr>
    <tr>
      <td>ll or LL</td>
      <td>long long</td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>]]></content><author><name>Simpleyyt</name></author><category term="Program" /><category term="cpp" /><summary type="html"><![CDATA[在类型转换和函数匹配中，字面值类型也是非常值得关注的。]]></summary></entry></feed>