靳哲 發表於 2021-8-24 09:35:00

深入理解 Node.js 的 Inspector

<blockquote data-tool="mdnice编辑器">
<p><strong><code>Node.js</code>&nbsp;提供的&nbsp;<code>Inspector</code>&nbsp;非常强大,不仅可以用来调试&nbsp;<code>Node.js</code>&nbsp;代码,还可以实时收集&nbsp;<code>Node.js</code>&nbsp;进程的&nbsp;<code>Heap Snapshot</code>、<code>Cpu Profile</code>&nbsp;等数据,同时支持静态、动态开启,是一个非常强大的工具,也是我们调试和诊断&nbsp;<code>Node.js</code>&nbsp;进程非常好的方式。本文从使用和原理详细讲解&nbsp;<code>Node.js</code>&nbsp;的&nbsp;<code>Inspector</code>。</strong></p>
</blockquote>
<p data-tool="mdnice编辑器"><code>Node.js</code>&nbsp;的文档中对&nbsp;<code>Inspector</code>&nbsp;的描述很少,但是如果深入探索,其实里面的内容还是挺多的。我们先看一下&nbsp;<code>Inspector</code>&nbsp;的使用。</p>
<h1 data-tool="mdnice编辑器">1 Inspector 的使用</h1>
<h2 data-tool="mdnice编辑器">1.1 &nbsp;本地调试</h2>
<p data-tool="mdnice编辑器">我们先从一个例子开始,下面是一个简单的 HTTP 服务器。</p>
<div class="cnblogs_code">
<pre data-tool="mdnice编辑器"><code>const&nbsp;http&nbsp;=&nbsp;require('http');<br>http.createServer((req,&nbsp;res)&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;res.end('ok');<br>}).listen(80);</code></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">然后我们以&nbsp;<code>node --inspect httpServer.js</code>&nbsp;的方式启动。我们可以看到以下输出。</p>
<pre data-tool="mdnice编辑器"><code>Debugger&nbsp;listening&nbsp;on&nbsp;ws://127.0.0.1:9229/fbbd9d8f-e088-48cc-b1e0-e16bfe58db44<br>For&nbsp;help,&nbsp;see:&nbsp;https://nodejs.org/en/docs/inspector<br></code></pre>
<p data-tool="mdnice编辑器"><code>9229</code>&nbsp;端口是&nbsp;<code>Node.js</code>&nbsp;默认选择的端口,当然我们也可以自定义,具体可参考&nbsp;<code>Node.js</code>&nbsp;官方文档。这时候我们去浏览器打开开发者工具,菜单栏多了一个调试 Node.js 的按钮。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092323617-1700955024.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">点击这个按钮。我们可以看到以下界面(点击切换到 Sources Tab)。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092334646-1973873324.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">我们可以选择某一行代码打断点,比如我在第三行,这时候我们访问&nbsp;<code>80</code>&nbsp;端口,开发者工具就会停留在断点处。这时候我们可以看到一些执行上下文。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092342354-325221626.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2 data-tool="mdnice编辑器">1.2 远程调试</h2>
<p data-tool="mdnice编辑器">但很多时候我们可能需要远程调试。比如我在一台云服务器上部署以上服务器代码。然后执行</p>
<div class="cnblogs_code">
<pre>node --inspect=0.0.0.0:8888 httpServer.js</pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">我们打开开发者工具发现按钮置灰或者找不到我们远程服务器的信息。这时候我们需要用另一种方式,通过在浏览器url输入框输入:</p>
<div class="cnblogs_code">
<pre>devtools:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">devtools/bundled/js_app.html?experiments=true&amp;v8only=true&amp;ws={host}:{port}/{path} </span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">的方式(替换&nbsp;<code>{}</code>&nbsp;里面的内容为你执行&nbsp;<code>Node.js</code>&nbsp;时输出的信息),浏览器就会去连接指定的地址,比如执行上面的命令输出的是&nbsp;<code>ws://0.0.0.0:8888/f6e42278-d915-48dc-af4d-453a23d330ab</code>,假设公网IP是&nbsp;<em>1.1.1.1</em>。那么最后浏览器url输入框里就填入&nbsp;<code>devtools://devtools/bundled/js_app.html?experiments=true&amp;v8only=true&amp;ws=1.1.1.1:8888/f6e42278-d915-48dc-af4d-453a23d330ab</code>&nbsp;就可以开始调试了,这种方式比较适合于常用的场景。</p>
<h2 data-tool="mdnice编辑器">1.3 自动探测</h2>
<p data-tool="mdnice编辑器">如果是我们自己调试的话,1.2 这种方式看起来就有点麻烦,我们可以使用浏览器提供的自动探测功能。</p>
<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
<li>URL 输入框输入&nbsp;<code>chrome://inspect/#devices</code>&nbsp;我们会看到以下界面</li>
</ol>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092428034-1003117035.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ol class="list-paddingleft-2" start="2" data-tool="mdnice编辑器">
<li>点击&nbsp;<code>configure</code>&nbsp;按钮,在弹出的弹框里输入你远程服务器的地址</li>
</ol>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092436441-736431988.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ol class="list-paddingleft-2" start="3" data-tool="mdnice编辑器">
<li>配置完毕后,我们会看到界面变成这样了(或者打开新的 Tab,我们看到开发者工具的调试按钮也变亮了)。</li>
</ol>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092444596-2008531301.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ol class="list-paddingleft-2" start="4" data-tool="mdnice编辑器">
<li>这时候我们点击&nbsp;<code>inspect</code>&nbsp;按钮、<code>Open dedicated DevTools for Node</code>&nbsp;按钮或者打开新 Tab 的开发者工具,就可以开始调试,而且还可以调试&nbsp;<code>Node.js</code>&nbsp;的原生 JS 模块。</li>
</ol>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092453657-1565200573.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2 data-tool="mdnice编辑器">1.4 收集数据</h2>
<p data-tool="mdnice编辑器"><code>V8 Inspector</code>&nbsp;是一个非常强大的工具,调试只是它其中一个能力,他还可以获取&nbsp;<code>Heap Snapshot</code>、<code>CPU&nbsp;Profile</code>&nbsp;等数据,具体能力请参考文章后面列出的指令文档和&nbsp;<code>Chrome Dev Tools</code>。</p>
<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
<li>收集 Cpu Profile 信息</li>
</ol>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092504097-1406547093.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ol class="list-paddingleft-2" start="2" data-tool="mdnice编辑器">
<li>获取 Heap Snapshop</li>
</ol>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092513867-52068476.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2 data-tool="mdnice编辑器">1.5 动态开启 Inspector</h2>
<p data-tool="mdnice编辑器">默认打开&nbsp;<code>Inspector</code>&nbsp;能力是不安全的,这意味着能连上服务器的客户端都能通过协议控制 Node.js 进程(虽然 URL 并不容易猜对),通常我们是在 Node.js 进程出现问题的时候,动态开启 Inspector,我们看一下下面的例子。</p>
<div class="cnblogs_code">
<pre>const inspector = require('inspector'<span style="color: rgba(0, 0, 0, 1)">);
const http </span>= require('http'<span style="color: rgba(0, 0, 0, 1)">);

let isOpend </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getHTML() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> `&lt;html&gt;
      &lt;meta charset="utf-8" /&gt;
      &lt;body&gt;<span style="color: rgba(0, 0, 0, 1)">
      复制到新 Tab 打开该 URL 开始调试 devtools:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">devtools/bundled/js_app.html?experiments=true&amp;v8only=true&amp;ws=${inspector.url().replace("ws://", '')}</span>
      &lt;/body&gt;
    &lt;/html&gt;`;
<span style="color: rgba(0, 0, 0, 1)">}

http.createServer((req, res) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (req.url == '/debug/open'<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 还没开启则开启</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">isOpend) {
          isOpend </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打开调试器</span>
<span style="color: rgba(0, 0, 0, 1)">          inspector.open();
      }
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回给前端的内容</span>
      const html =<span style="color: rgba(0, 0, 0, 1)"> getHTML() ;
      res.end(html);
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (req.url == '/debug/close'<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果开启了则关闭</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isOpend) {
          inspector.close();
          isOpend </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      res.end(</span>'ok'<span style="color: rgba(0, 0, 0, 1)">);
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    res.end(</span>'ok'<span style="color: rgba(0, 0, 0, 1)">);
}
}).listen(</span>80);</pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">当我们需要调试的时候,通过访问 /debug/open 打开调试器。前端界面可以看到以下输出。</p>
<div class="cnblogs_code">
<pre>复制到新 Tab 打开该 URL 开始调试 devtools:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">devtools/bundled/js_app.html?experiments=true&amp;v8only=true&amp;ws=127.0.0.1:9229/9efd4c80-956a-4422-b23c-4348e6613304</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">接着新开一个 Tab,然后复制上面的 URL,粘贴到浏览器 URL 地址栏访问,我们就可以看到调试页面。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092545421-2060080178.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">然后打个断点,接着新开一个 Tab 访问&nbsp;<code>http://localhost</code>&nbsp;就可以进入调试,调试完成后访问 /debug/close 关闭调试器。浏览器界面就会显示断开连接了。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092554399-87486153.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">以上方式支持调试和收集数据,如果我们只是需要收集数据,还有另一种动态开启&nbsp;<code>Inspector</code>&nbsp;的方式</p>
<div class="cnblogs_code">
<pre>const http = require('http'<span style="color: rgba(0, 0, 0, 1)">);
const inspector </span>= require('inspector'<span style="color: rgba(0, 0, 0, 1)">);
const fs </span>= require('fs'<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getCpuprofile(req, res) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打开一个和 V8 Inspector 的会话</span>
    const session = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> inspector.Session();
    session.connect();
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向V8 Inspector 提交命令,开启 Cpu Profile 并收集数据</span>
    session.post('Profiler.enable', () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    session.post(</span>'Profiler.start', () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 收集一段时间后提交停止收集命令</span>
      setTimeout(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      session.post(</span>'Profiler.stop', (err, { profile }) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 把数据写入文件</span>
          <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">err) {
            fs.writeFileSync(</span>'./profile.cpuprofile'<span style="color: rgba(0, 0, 0, 1)">, JSON.stringify(profile));
          }
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 断开会话</span>
<span style="color: rgba(0, 0, 0, 1)">          session.disconnect();
          </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 回复客户端</span>
          res.end('ok'<span style="color: rgba(0, 0, 0, 1)">);
      });
      }, </span>3000<span style="color: rgba(0, 0, 0, 1)">)
    });
});
}

http.createServer((req, res) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (req.url == '/debug/getCpuprofile'<span style="color: rgba(0, 0, 0, 1)">) {
      getCpuprofile(req, res);
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      res.end(</span>'ok'<span style="color: rgba(0, 0, 0, 1)">);
}
}).listen(</span>80);</pre>
</div>
<p>&nbsp;</p>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">我们可以通过&nbsp;<code>Inspector Session</code>&nbsp;的能力,实时和&nbsp;<code>V8 Inspector</code>&nbsp;交互而不需要启动一个&nbsp;<code>WebSocket</code>&nbsp;服务。本地调试时还可以在&nbsp;<code>VSCode</code>&nbsp;里点击&nbsp;<code>Profile</code>&nbsp;文件直接看到效果。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092613867-1256929084.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h1 data-tool="mdnice编辑器">2 Inspector 调试的原理</h1>
<p data-tool="mdnice编辑器">下面以通过 URL 的方式调试(可以看到 Network ),来看看调试的时候都发生了什么,浏览器和远程服务器建立连接后,是通过 WebSocket 协议通信的,下面是一次通信的信息。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092625647-636730067.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">我们看一下这命令是什么意思(具体可以参考 Inspector 协议文档)。</p>
<blockquote data-tool="mdnice编辑器">
<p>Debugger.scriptParsed # Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger.</p>
</blockquote>
<p data-tool="mdnice编辑器">从说明中我们看到,当 V8 解析脚本的时候就会触发这个事件,告诉浏览器相关的信息。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092636819-2041461403.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">我们发现返回的都是一些元数据,没有脚本的具体代码内容,这时候浏览器会再次发起请求(点击对应脚本对应的 JS 文件时),</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092647111-1170654950.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">我们看到这个脚本的 scriptId 是 103。所以请求里带了这个 scriptId。对应的请求 id 是 11。接着看一下响应。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092657781-1178811256.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">至此,我们了解了获取脚本内容的过程,然后我们看看调试的时候是怎样的过程。当我们在浏览器上点击某一行设置断点的时候,浏览器就会发送一个请求。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092706022-715425921.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">这个命令的意义顾名思义,我们看一下具体定义:</p>
<blockquote data-tool="mdnice编辑器">
<p>Debugger.setBreakpointByUrl # Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads.</p>
</blockquote>
<p data-tool="mdnice编辑器">接着服务返回响应。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092714909-1568469928.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">这时候我们从另外一个 Tab 访问 80 端口,服务器就会在我们设置的断点处停留,并且通知浏览器。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092721747-297609879.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">我们看一下这个命令的意思。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092729520-1271195414.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">这个命令就是当服务器执行到断点时通知浏览器,并且返回执行的一些上下文,比如执行到哪个断点停留了。这时候浏览器侧也会停留在对应的地方,当我们 hover 某个变量时,就会看到对应的上下文。这些都是通过具体的命令获取的数据。就不一一分析了。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092737359-243097081.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2 data-tool="mdnice编辑器">3 Node.js Inspector 的实现</h2>
<p data-tool="mdnice编辑器">大致了解了浏览器和服务器的交互过程和协议后,我们再来深入了解一下关于 Inspector 的一些实现。当然这里不是分析 V8 中 Inspector 的实现,而是分析如何使用 V8 的 Inspector 以及 Node.js 中关于 Inspector 的实现部分。</p>
<p data-tool="mdnice编辑器">当我们以以下方式执行应用时</p>
<div class="cnblogs_code">
<pre>node --inspect app.js</pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<h2 data-tool="mdnice编辑器">3.1 初始化</h2>
<p data-tool="mdnice编辑器">Node.js 在启动的过程中,就会初始化 Inspector 相关的逻辑。</p>
<div class="cnblogs_code">
<pre>inspector_agent_ = std::make_unique&lt;inspector::Agent&gt;(<span style="color: rgba(0, 0, 255, 1)">this</span>);</pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">Agent 是负责和&nbsp;<code>V8 Inspector</code>&nbsp;通信的对象,创建完后接着执行&nbsp;<code>env-&gt;InitializeInspector({})</code>&nbsp;启动&nbsp;<code>Agent</code>。</p>
<div class="cnblogs_code">
<pre>inspector_agent_-&gt;Start(...);</pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">Start 继续执行&nbsp;<code>Agent::StartIoThread</code>。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">bool Agent::StartIoThread() {
io_ </span>= InspectorIo::Start(client_-&gt;<span style="color: rgba(0, 0, 0, 1)">getThreadHandle(), ...);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>StartIoThread</code>&nbsp;中的&nbsp;<code>client_-&gt;getThreadHandle()</code>&nbsp;是重要的逻辑,我们先来分析该函数。</p>
<div class="cnblogs_code">
<pre>std::shared_ptr&lt;MainThreadHandle&gt;<span style="color: rgba(0, 0, 0, 1)"> getThreadHandle() {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">interface_) {
      interface_ </span>= std::make_shared&lt;MainThreadInterface&gt;(env_-&gt;<span style="color: rgba(0, 0, 0, 1)">inspector_agent(), ...);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> interface_-&gt;<span style="color: rgba(0, 0, 0, 1)">GetHandle();
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>getThreadHandle</code>&nbsp;首先创建来一个&nbsp;<code>MainThreadInterface</code>&nbsp;对象,接着又调用了他的 GetHandle 方法,我们看一下该方法的逻辑。</p>
<div class="cnblogs_code">
<pre>std::shared_ptr&lt;MainThreadHandle&gt;<span style="color: rgba(0, 0, 0, 1)"> MainThreadInterface::GetHandle() {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (handle_ ==<span style="color: rgba(0, 0, 0, 1)"> nullptr)
    handle_ </span>= std::make_shared&lt;MainThreadHandle&gt;(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> handle_;
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>GetHandle</code>&nbsp;了创建了一个&nbsp;<code>MainThreadHandle</code>&nbsp;对象,最终结构如下所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092826538-766056529.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">分析完后我们继续看&nbsp;<code>Agent::StartIoThread</code>&nbsp;中&nbsp;<code>InspectorIo::Start</code>&nbsp;的逻辑。</p>
<div class="cnblogs_code">
<pre>std::unique_ptr&lt;InspectorIo&gt; InspectorIo::Start(std::shared_ptr&lt;MainThreadHandle&gt;<span style="color: rgba(0, 0, 0, 1)"> main_thread, ...) {
auto io </span>= std::unique_ptr&lt;InspectorIo&gt;(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> InspectorIo(main_thread, ...));
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> io;
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>InspectorIo::Star</code>&nbsp;里新建了一个&nbsp;<code>InspectorIo</code>&nbsp;对象,我们看看&nbsp;<code>InspectorIo</code>&nbsp;构造函数的逻辑。</p>
<div class="cnblogs_code">
<pre>InspectorIo::InspectorIo(std::shared_ptr&lt;MainThreadHandle&gt;<span style="color: rgba(0, 0, 0, 1)"> main_thread, ...)
    :
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 初始化 main_thread_</span>
<span style="color: rgba(0, 0, 0, 1)">    main_thread_(main_thread)) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 新建一个子线程,子线程中执行 InspectorIo::ThreadMain</span>
uv_thread_create(&amp;thread_, InspectorIo::ThreadMain, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">这时候结构如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092851547-2039977919.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器"><code>InspectorIo</code>&nbsp;创建了一个子线程,&nbsp;<code>Inspector</code>&nbsp;在子线程里启动的原因主要有两个。</p>
<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
<li>如果在主线程里运行,那么当我们断点调试的时候,<code>Node.js</code>&nbsp;主线程就会被停住,也就无法处理客户端发过来的调试指令。</li>
<li>如果主线程陷入死循环,我们就无法实时抓取进程的&nbsp;<code>Profile</code>&nbsp;数据来分析原因。</li>
</ol>
<p data-tool="mdnice编辑器">接着继续看一下子线程里执行&nbsp;<code>InspectorIo::ThreadMain</code>&nbsp;的逻辑:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> InspectorIo::ThreadMain(<span style="color: rgba(0, 0, 255, 1)">void</span>*<span style="color: rgba(0, 0, 0, 1)"> io) {
static_cast</span>&lt;InspectorIo*&gt;(io)-&gt;<span style="color: rgba(0, 0, 0, 1)">ThreadMain();
}

</span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> InspectorIo::ThreadMain() {
uv_loop_t loop;
loop.data </span>=<span style="color: rgba(0, 0, 0, 1)"> nullptr;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在子线程开启一个新的事件循环</span>
<span style="color: rgba(0, 0, 255, 1)">int</span> err = uv_loop_init(&amp;<span style="color: rgba(0, 0, 0, 1)">loop);
std::shared_ptr</span>&lt;RequestQueueData&gt; queue(<span style="color: rgba(0, 0, 255, 1)">new</span> RequestQueueData(&amp;<span style="color: rgba(0, 0, 0, 1)">loop), ...);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 新建一个 delegate,用于处理请求</span>
std::unique_ptr&lt;InspectorIoDelegate&gt; <span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">(
      </span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> InspectorIoDelegate(queue, main_thread_, ...)
);
InspectorSocketServer server(std::move(</span><span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">), ...);
server.Start();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 进入事件循环</span>
uv_run(&amp;<span style="color: rgba(0, 0, 0, 1)">loop, UV_RUN_DEFAULT);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>ThreadMain</code>&nbsp;主要有三个逻辑:</p>
<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
<li>创建一个 delegate 对象,该对象是核心的对象,后面我们会看到有什么作用。</li>
<li>创建一个服务器并启动。</li>
<li>开启事件循环。</li>
</ol>
<p data-tool="mdnice编辑器">接下来看一下服务器的逻辑,首先看一下创建服务器的逻辑:</p>
<div class="cnblogs_code">
<pre>InspectorSocketServer::InspectorSocketServer(std::unique_ptr&lt;SocketServerDelegate&gt; <span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">, ...)
    : </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存 delegate</span>
      delegate_(std::move(<span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">)),
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 初始化 sessionId</span>
      next_session_id_(<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置 delegate 的 server 为当前服务器</span>
delegate_-&gt;AssignServer(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">执行完后形成以下结构:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092915422-1337803873.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">接着我们看启动服务器的逻辑:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> InspectorSocketServer::Start() {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> DNS 解析,比如输入的是localhost</span>
<span style="color: rgba(0, 0, 255, 1)">struct</span><span style="color: rgba(0, 0, 0, 1)"> addrinfo hints;
memset(</span>&amp;hints, <span style="color: rgba(128, 0, 128, 1)">0</span>, <span style="color: rgba(0, 0, 255, 1)">sizeof</span><span style="color: rgba(0, 0, 0, 1)">(hints));
hints.ai_flags </span>=<span style="color: rgba(0, 0, 0, 1)"> AI_NUMERICSERV;
hints.ai_socktype </span>=<span style="color: rgba(0, 0, 0, 1)"> SOCK_STREAM;
uv_getaddrinfo_t req;
</span><span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span> port_string =<span style="color: rgba(0, 0, 0, 1)"> std::to_string(port_);
uv_getaddrinfo(loop_, </span>&amp;<span style="color: rgba(0, 0, 0, 1)">req, nullptr, host_.c_str(),
                           port_string.c_str(), </span>&amp;<span style="color: rgba(0, 0, 0, 1)">hints);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监听解析到的 IP 列表               </span>
<span style="color: rgba(0, 0, 255, 1)">for</span> (addrinfo* address =<span style="color: rgba(0, 0, 0, 1)"> req.addrinfo;
         address </span>!=<span style="color: rgba(0, 0, 0, 1)"> nullptr;
       address </span>= address-&gt;<span style="color: rgba(0, 0, 0, 1)">ai_next) {

    auto server_socket </span>= ServerSocketPtr(<span style="color: rgba(0, 0, 255, 1)">new</span> ServerSocket(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">));
    err </span>= server_socket-&gt;Listen(address-&gt;<span style="color: rgba(0, 0, 0, 1)">ai_addr, loop_);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (err == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
      server_sockets_.push_back(std::move(server_socket));

}

</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">首先根据参数做&nbsp;<code>DNS</code>&nbsp;解析,然后根据拿到的&nbsp;<code>IP</code>&nbsp;列表(通常是一个),创建对应个数的&nbsp;<code>ServerSocket</code>&nbsp;对象,并执行它的&nbsp;<code>Listen</code>&nbsp;方法。<code>ServerSocket</code>&nbsp;表示一个监听&nbsp;<code>socket</code>,看一下&nbsp;<code>ServerSocket</code>&nbsp;的构造函数:</p>
<div class="cnblogs_code">
<pre>ServerSocket(InspectorSocketServer*<span style="color: rgba(0, 0, 0, 1)"> server) :
    tcp_socket_(uv_tcp_t()), server_(server) {}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">执行完后结构如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824092938990-917742388.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">接着看一下&nbsp;<code>ServerSocket</code>&nbsp;的&nbsp;<code>Listen</code>&nbsp;方法:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">int</span> ServerSocket::Listen(sockaddr* addr, uv_loop_t*<span style="color: rgba(0, 0, 0, 1)"> loop) {
uv_tcp_t</span>* server = &amp;<span style="color: rgba(0, 0, 0, 1)">tcp_socket_;
uv_tcp_init(loop, server)
uv_tcp_bind(server, addr, </span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
uv_listen(reinterpret_cast</span>&lt;uv_stream_t*&gt;<span style="color: rgba(0, 0, 0, 1)">(server),
            </span><span style="color: rgba(128, 0, 128, 1)">511</span><span style="color: rgba(0, 0, 0, 1)">,
            ServerSocket::SocketConnectedCallback);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>Listen</code>&nbsp;调用&nbsp;<code>Libuv</code>&nbsp;的接口完成服务器的启动。至此,<code>Inspector</code>&nbsp;提供的&nbsp;<code>Weboscket</code>&nbsp;服务器启动了。</p>
<h2 data-tool="mdnice编辑器">3.2 处理连接</h2>
<p data-tool="mdnice编辑器">从刚才分析中可以看到,当有连接到来时执行回调&nbsp;<code>ServerSocket::SocketConnectedCallback</code>。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> ServerSocket::SocketConnectedCallback(uv_stream_t*<span style="color: rgba(0, 0, 0, 1)"> tcp_socket,
                                           </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> status) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (status == <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据 Libuv handle 找到对应的 ServerSocket 对象</span>
    ServerSocket* server_socket =<span style="color: rgba(0, 0, 0, 1)"> ServerSocket::FromTcpSocket(tcp_socket);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Socket 对象的 server_ 字段保存了所在的 InspectorSocketServer</span>
    server_socket-&gt;server_-&gt;Accept(server_socket-&gt;<span style="color: rgba(0, 0, 0, 1)">port_, tcp_socket);
}
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">接着看&nbsp;<code>InspectorSocketServer</code>&nbsp;的&nbsp;<code>Accept</code>&nbsp;是如何处理连接的:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> InspectorSocketServer::Accept(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> server_port,
                                 uv_stream_t</span>*<span style="color: rgba(0, 0, 0, 1)"> server_socket) {

std::unique_ptr</span>&lt;SocketSession&gt;<span style="color: rgba(0, 0, 0, 1)"> session(
      </span><span style="color: rgba(0, 0, 255, 1)">new</span> SocketSession(<span style="color: rgba(0, 0, 255, 1)">this</span>, next_session_id_++<span style="color: rgba(0, 0, 0, 1)">, server_port)
);

InspectorSocket::DelegatePointer </span><span style="color: rgba(0, 0, 255, 1)">delegate</span> =<span style="color: rgba(0, 0, 0, 1)">
      InspectorSocket::DelegatePointer(
          </span><span style="color: rgba(0, 0, 255, 1)">new</span> SocketSession::Delegate(<span style="color: rgba(0, 0, 255, 1)">this</span>, session-&gt;<span style="color: rgba(0, 0, 0, 1)">id())
      );

InspectorSocket::Pointer inspector </span>=<span style="color: rgba(0, 0, 0, 1)">
      InspectorSocket::Accept(server_socket, std::move(</span><span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">));

</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (inspector) {
    session</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">Own(std::move(inspector));
    connected_sessions_.second =<span style="color: rgba(0, 0, 0, 1)"> std::move(session);
}
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>Accept</code>&nbsp;的首先创建里一个&nbsp;<code>SocketSession</code>&nbsp;和&nbsp;<code>SocketSession::Delegate</code>&nbsp;对象。然后调用&nbsp;<code>InspectorSocket::Accept</code>,从代码中可以看到&nbsp;<code>InspectorSocket::Accept</code>&nbsp;会返回一个&nbsp;<code>InspectorSocket</code>&nbsp;对象。<code>InspectorSocket</code>&nbsp;是对通信&nbsp;<code>socket</code>&nbsp;的封装(和客户端通信的&nbsp;<code>socket</code>,区别于服务器的监听&nbsp;<code>socket</code>)。然后记录&nbsp;<code>session</code>&nbsp;对象对应的&nbsp;<code>InspectorSocket</code>&nbsp;对象,同时记录&nbsp;<code>sessionId</code>&nbsp;和&nbsp;<code>session</code>&nbsp;的映射关系。结构如下图所示:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824093015718-1952866749.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">接着看一下&nbsp;<code>InspectorSocket::Accept</code>&nbsp;返回&nbsp;<code>InspectorSocket</code>&nbsp;的逻辑:</p>
<div class="cnblogs_code">
<pre>InspectorSocket::Pointer InspectorSocket::Accept(uv_stream_t*<span style="color: rgba(0, 0, 0, 1)"> server,
                                                 DelegatePointer </span><span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">) {
auto tcp </span>= TcpHolder::Accept(server, std::move(<span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">));
InspectorSocket</span>* inspector = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> InspectorSocket();
inspector</span>-&gt;SwitchProtocol(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpHandler(inspector, std::move(tcp)));
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> InspectorSocket::Pointer(inspector);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>InspectorSocket::Accept</code>&nbsp;的代码不多,但是逻辑还是挺多的:</p>
<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
<li><code>InspectorSocket::Accept</code>&nbsp;再次调用&nbsp;<code>TcpHolder::Accept</code>&nbsp;获取一个&nbsp;<code>TcpHolder</code>&nbsp;对象。</li>
</ol>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">TcpHolder::Pointer TcpHolder::Accept(
    uv_stream_t</span>*<span style="color: rgba(0, 0, 0, 1)"> server,
    InspectorSocket::DelegatePointer </span><span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">) {
   
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 新建一个 TcpHolder 对象,TcpHolder 是对 uv_tcp_t 和 delegate 的封装</span>
TcpHolder* result = <span style="color: rgba(0, 0, 255, 1)">new</span> TcpHolder(std::move(<span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 拿到 TcpHolder 对象的 uv_tcp_t 结构体</span>
uv_stream_t* tcp = reinterpret_cast&lt;uv_stream_t*&gt;(&amp;result-&gt;<span style="color: rgba(0, 0, 0, 1)">tcp_);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 初始化</span>
<span style="color: rgba(0, 0, 255, 1)">int</span> err = uv_tcp_init(server-&gt;loop, &amp;result-&gt;<span style="color: rgba(0, 0, 0, 1)">tcp_);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 摘取一个 TCP 连接对应的 fd 保存到 TcpHolder 的 uv_tcp_t 结构体中(即第二个参数的 tcp 字段)</span>
<span style="color: rgba(0, 0, 0, 1)">uv_accept(server, tcp);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册等待可读事件,有数据时执行 OnDataReceivedCb 回调</span>
<span style="color: rgba(0, 0, 0, 1)">uv_read_start(tcp, allocate_buffer, OnDataReceivedCb);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> TcpHolder::Pointer(result);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<ol class="list-paddingleft-2" start="2" data-tool="mdnice编辑器">
<li>新建一个&nbsp;<code>HttpHandler</code>&nbsp;对象:</li>
</ol>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">explicit</span> HttpHandler(InspectorSocket*<span style="color: rgba(0, 0, 0, 1)"> inspector, TcpHolder::Pointer tcp)
                     : ProtocolHandler(inspector, std::move(tcp)){

llhttp_init(</span>&amp;parser_, HTTP_REQUEST, &amp;<span style="color: rgba(0, 0, 0, 1)">parser_settings);
llhttp_settings_init(</span>&amp;<span style="color: rgba(0, 0, 0, 1)">parser_settings);
parser_settings.on_header_field </span>=<span style="color: rgba(0, 0, 0, 1)"> OnHeaderField;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)">}

ProtocolHandler::ProtocolHandler(InspectorSocket</span>*<span style="color: rgba(0, 0, 0, 1)"> inspector,
                                 TcpHolder::Pointer tcp)
                                 : inspector_(inspector), tcp_(std::move(tcp)) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置 TCP 数据的 handler,TCP 是只负责传输,数据的解析交给 handler 处理                               </span>
tcp_-&gt;SetHandler(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>HttpHandler</code>&nbsp;是对&nbsp;<code>TcpHolder</code>&nbsp;的封装,主要通过 HTTP 解析器&nbsp;<code>llhttp</code>&nbsp;对 HTTP 协议进行解析。</p>
<ol class="list-paddingleft-2" start="3" data-tool="mdnice编辑器">
<li>调用 inspector-&gt;SwitchProtocol() 切换当前协议处理器为 HTTP,建立 TCP 连接后,首先要经过一个 HTTP 请求从 HTTP 协议升级到 WebSocket 协议,升级成功后就使用 Websocket 协议进行通信.</li>
</ol>
<p data-tool="mdnice编辑器">我们看一下这时候的结构图:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824093047905-1530657318.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">至此,就完成了连接处理的分析!(撒花,你学废了么)</p>
<h2 data-tool="mdnice编辑器">3.3 协议升级</h2>
<p data-tool="mdnice编辑器">完成了 TCP 连接的处理后,接下来要完成协议升级,因为&nbsp;<code>Inspector</code>&nbsp;是通过&nbsp;<code>WebSocket</code>&nbsp;协议和客户端通信的,所以需要通过一个 HTTP 请求来完成 HTTP 到&nbsp;<code>WebSocekt</code>&nbsp;协议的升级。从刚才的分析中看当有数据到来时会执行&nbsp;<code>OnDataReceivedCb</code>&nbsp;回调:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> TcpHolder::OnDataReceivedCb(uv_stream_t*<span style="color: rgba(0, 0, 0, 1)"> tcp, ssize_t nread,
                                 </span><span style="color: rgba(0, 0, 255, 1)">const</span> uv_buf_t*<span style="color: rgba(0, 0, 0, 1)"> buf) {
TcpHolder</span>* holder =<span style="color: rgba(0, 0, 0, 1)"> From(tcp);
holder</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">ReclaimUvBuf(buf, nread);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 调用 handler 的 onData,目前 handler 是 HTTP 协议</span>
holder-&gt;handler_-&gt;OnData(&amp;holder-&gt;<span style="color: rgba(0, 0, 0, 1)">buffer);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">TCP 层收到数据后交给应用层解析,直接调用上层的 OnData 回调。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> OnData(std::vector&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt;* data) <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解析 HTTP 协议</span>
    llhttp_execute(&amp;parser_, data-&gt;data(), data-&gt;<span style="color: rgba(0, 0, 0, 1)">size());
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解析完并且是升级协议的请求则调用 delegate 的回调 OnSocketUpgrade</span>
    <span style="color: rgba(0, 0, 255, 1)">delegate</span>()-&gt;OnSocketUpgrade(<span style="color: rgba(0, 0, 255, 1)">event</span>.host, <span style="color: rgba(0, 0, 255, 1)">event</span>.path, <span style="color: rgba(0, 0, 255, 1)">event</span><span style="color: rgba(0, 0, 0, 1)">.ws_key);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>OnData</code>&nbsp;可能会被多次回调,并通过&nbsp;<code>llhttp_execute</code>&nbsp;解析收到的 HTTP 报文,当发现是一个协议升级的请求后,就调用&nbsp;<code>OnSocketUpgrade</code>&nbsp;回调。<code>delegate</code>&nbsp;是一个&nbsp;<code>SocketSession::Delegate</code>&nbsp;对象。来看一下该对象的 OnSocketUpgrade 方法:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> SocketSession::Delegate::OnSocketUpgrade(<span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> host,
                                              </span><span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> path,
                                              </span><span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> ws_key) {
std::</span><span style="color: rgba(0, 0, 255, 1)">string</span> id = path.empty() ? path : path.substr(<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
server_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">SessionStarted(session_id_, id, ws_key);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>OnSocketUpgrade</code>&nbsp;又调用了&nbsp;<code>server_</code>&nbsp;(<code>InspectorSocketServer</code>&nbsp;对象)的&nbsp;<code>SessionStarted</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> InspectorSocketServer::SessionStarted(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> session_id,
                                           </span><span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> id,
                                           </span><span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> ws_key) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 找到对应的 session 对象                                           </span>
SocketSession* session =<span style="color: rgba(0, 0, 0, 1)"> Session(session_id);
connected_sessions_.first </span>=<span style="color: rgba(0, 0, 0, 1)"> id;
session</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">Accept(ws_key);
delegate_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">StartSession(session_id, id);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">首先通过&nbsp;<code>session_id</code>&nbsp;找到建立 TCP 连接时分配的&nbsp;<code>SocketSession</code>&nbsp;对象:</p>
<ol class="list-paddingleft-2" data-tool="mdnice编辑器">
<li>执行 session-&gt;Accept(ws_key) 回复客户端同意协议升级:</li>
</ol>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> Accept(<span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> ws_key) {
ws_socket_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">AcceptUpgrade(ws_key);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">从结构图我们可以看到&nbsp;<code>ws_socket_</code>&nbsp;是一个&nbsp;<code>InspectorSocket</code>&nbsp;对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> AcceptUpgrade(<span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp; accept_key) <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">char</span><span style="color: rgba(0, 0, 0, 1)"> accept_string;
    generate_accept_string(accept_key, </span>&amp;<span style="color: rgba(0, 0, 0, 1)">accept_string);
    </span><span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">char</span> accept_ws_prefix[] = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">HTTP/1.1 101 Switching Protocols\r\n</span><span style="color: rgba(128, 0, 0, 1)">"</span>
                                    <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Upgrade: websocket\r\n</span><span style="color: rgba(128, 0, 0, 1)">"</span>
                                    <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Connection: Upgrade\r\n</span><span style="color: rgba(128, 0, 0, 1)">"</span>
                                    <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Sec-WebSocket-Accept: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 回复 101 给客户端             </span>
<span style="color: rgba(0, 0, 0, 1)">    WriteRaw(reply, WriteRequest::Cleanup);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 切换 handler 为 WebSocket handler</span>
    inspector_-&gt;SwitchProtocol(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> WsHandler(inspector_, std::move(tcp_)));
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>AcceptUpgradeh</code>&nbsp;首先回复客户端 101 表示同意升级到&nbsp;<code>WebSocket</code>&nbsp;协议,然后切换数据处理器为&nbsp;<code>WsHandler</code>,即后续的数据按照&nbsp;<code>WebSocket</code>&nbsp;协议处理。</p>
<ol class="list-paddingleft-2" start="2" data-tool="mdnice编辑器">
<li>执行&nbsp;<code>delegate_-&gt;StartSession(session_id, id)</code>&nbsp;建立和&nbsp;<code>V8 Inspector</code>&nbsp;的会话。<code>delegate_</code>&nbsp;&nbsp;是&nbsp;<code>InspectorIoDelegate</code>&nbsp;对象:</li>
</ol>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> InspectorIoDelegate::StartSession(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> session_id,
                                       </span><span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> target_id) {
auto session </span>= main_thread_-&gt;<span style="color: rgba(0, 0, 0, 1)">Connect(
      std::unique_ptr</span>&lt;InspectorSessionDelegate&gt;<span style="color: rgba(0, 0, 0, 1)">(
          </span><span style="color: rgba(0, 0, 255, 1)">new</span> IoSessionDelegate(request_queue_-&gt;<span style="color: rgba(0, 0, 0, 1)">handle(), session_id)
      ),
      </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (session) {
    sessions_ </span>=<span style="color: rgba(0, 0, 0, 1)"> std::move(session);
    fprintf(stderr, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Debugger attached.\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">首先通过&nbsp;<code>main_thread_-&gt;Connect</code>&nbsp;拿到一个&nbsp;<code>session</code>,并在&nbsp;<code>InspectorIoDelegate</code>&nbsp;中记录映射关系。结构图如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824093148555-2063319530.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">接下来看一下&nbsp;<code>main_thread_-&gt;Connect</code>&nbsp;的逻辑(<code>main_thread_</code>&nbsp;是&nbsp;<code>MainThreadHandle</code>&nbsp;对象):</p>
<div class="cnblogs_code">
<pre>std::unique_ptr&lt;InspectorSession&gt;<span style="color: rgba(0, 0, 0, 1)"> MainThreadHandle::Connect(
    std::unique_ptr</span>&lt;InspectorSessionDelegate&gt; <span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> prevent_shutdown) {

</span><span style="color: rgba(0, 0, 255, 1)">return</span> std::unique_ptr&lt;InspectorSession&gt;<span style="color: rgba(0, 0, 0, 1)">(
      </span><span style="color: rgba(0, 0, 255, 1)">new</span> CrossThreadInspectorSession(++<span style="color: rgba(0, 0, 0, 1)">next_session_id_,
                                    shared_from_this(),
                                    std::move(</span><span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">),
                                    prevent_shutdown));
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>Connect</code>&nbsp;函数新建了一个&nbsp;<code>CrossThreadInspectorSession</code>&nbsp;对象。<code>CrossThreadInspectorSession</code>&nbsp;构造函数如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> CrossThreadInspectorSession(...) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行 MainThreadSessionState::Connect                           </span>
    state_.Call(&amp;MainThreadSessionState::Connect, std::move(<span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">));
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">继续看&nbsp;<code>MainThreadSessionState::Connect</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> Connect(std::unique_ptr&lt;InspectorSessionDelegate&gt; <span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">) {
    Agent</span>* agent = thread_-&gt;<span style="color: rgba(0, 0, 0, 1)">inspector_agent();
    session_ </span>= agent-&gt;Connect(std::move(<span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">), prevent_shutdown_);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">继续调&nbsp;<code>agent-&gt;Connect</code>:</p>
<div class="cnblogs_code">
<pre>std::unique_ptr&lt;InspectorSession&gt;<span style="color: rgba(0, 0, 0, 1)"> Agent::Connect(
    std::unique_ptr</span>&lt;InspectorSessionDelegate&gt; <span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">,
    </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> prevent_shutdown) {

</span><span style="color: rgba(0, 0, 255, 1)">int</span> session_id = client_-&gt;connectFrontend(std::move(<span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">),
                                          prevent_shutdown);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> std::unique_ptr&lt;InspectorSession&gt;<span style="color: rgba(0, 0, 0, 1)">(
      </span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SameThreadInspectorSession(session_id, client_));
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">继续调&nbsp;<code>connectFrontend</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">int</span> connectFrontend(std::unique_ptr&lt;InspectorSessionDelegate&gt; <span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">,
                      </span><span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> prevent_shutdown) {
    </span><span style="color: rgba(0, 0, 255, 1)">int</span> session_id = next_session_id_++<span style="color: rgba(0, 0, 0, 1)">;
    channels_ </span>= std::make_unique&lt;ChannelImpl&gt;<span style="color: rgba(0, 0, 0, 1)">(env_,
                                                          client_,
                                                          getWorkerManager(),
                                                          std::move(</span><span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">),
                                                          getThreadHandle(),
                                                          prevent_shutdown);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> session_id;
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>connectFrontend</code>&nbsp;创建了一个&nbsp;<code>ChannelImpl</code>&nbsp;并且在&nbsp;<code>channels_</code>&nbsp;中保存了映射关系。看看&nbsp;<code>ChannelImpl</code>&nbsp;的构造函数:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">explicit</span> ChannelImpl(Environment*<span style="color: rgba(0, 0, 0, 1)"> env,
                     </span><span style="color: rgba(0, 0, 255, 1)">const</span> std::unique_ptr&lt;V8Inspector&gt;&amp;<span style="color: rgba(0, 0, 0, 1)"> inspector,
                     std::unique_ptr</span>&lt;InspectorSessionDelegate&gt; <span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">, ...)
      : delegate_(std::move(</span><span style="color: rgba(0, 0, 255, 1)">delegate</span><span style="color: rgba(0, 0, 0, 1)">)) {

    session_ </span>= inspector-&gt;connect(CONTEXT_GROUP_ID, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">, StringView());
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>ChannelImpl</code>&nbsp;调用&nbsp;<code>inspector-&gt;connect</code>&nbsp;建立了一个和&nbsp;<code>V8 Inspector</code>&nbsp;的会话。结构图大致如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824093238420-1122413890.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">客户端到&nbsp;<code>Node.js</code>&nbsp;到&nbsp;<code>V8 Inspector</code>&nbsp;的整体架构如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1313648/202108/1313648-20210824093249482-877617746.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2 data-tool="mdnice编辑器">3.4 客户端到 V8 Inspector 的数据处理</h2>
<p data-tool="mdnice编辑器">TCP 连接建立了,协议升级也完成了,接下来就可以开始处理业务数据。从前面的分析中我们已经知道数据到来时会执行&nbsp;<code>TcpHoldler</code>&nbsp;的&nbsp;<code>handler_-&gt;OnData</code>&nbsp;回调。因为已经完成了协议升级,所以这时候的&nbsp;<code>handler</code>&nbsp;变成了&nbsp;<code>WeSocket handler</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> OnData(std::vector&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt;* data) <span style="color: rgba(0, 0, 255, 1)">override</span>
    <span style="color: rgba(0, 0, 255, 1)">int</span> processed = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">do</span><span style="color: rgba(0, 0, 0, 1)"> {
      processed </span>= ParseWsFrames(*<span style="color: rgba(0, 0, 0, 1)">data);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
    } <span style="color: rgba(0, 0, 255, 1)">while</span> (processed &gt; <span style="color: rgba(128, 0, 128, 1)">0</span> &amp;&amp; !data-&gt;<span style="color: rgba(0, 0, 0, 1)">empty());
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>OnData</code>&nbsp;通过&nbsp;<code>ParseWsFrames</code>&nbsp;解析&nbsp;<code>WebSocket</code>&nbsp;协议:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">int</span> ParseWsFrames(<span style="color: rgba(0, 0, 255, 1)">const</span> std::vector&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt;&amp;<span style="color: rgba(0, 0, 0, 1)"> buffer) {
    </span><span style="color: rgba(0, 0, 255, 1)">int</span> bytes_consumed = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
    std::vector</span>&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> output;
    </span><span style="color: rgba(0, 0, 255, 1)">bool</span> compressed = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解析WebSocket协议</span>
    ws_decode_result r =<span style="color: rgba(0, 0, 0, 1)">decode_frame_hybi17(buffer,
                                              </span><span style="color: rgba(0, 0, 255, 1)">true</span> <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> client_frame </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">,
                                              </span>&amp;bytes_consumed, &amp;<span style="color: rgba(0, 0, 0, 1)">output,
                                              </span>&amp;<span style="color: rgba(0, 0, 0, 1)">compressed);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行delegate的回调                                        </span>
    <span style="color: rgba(0, 0, 255, 1)">delegate</span>()-&gt;<span style="color: rgba(0, 0, 0, 1)">OnWsFrame(output);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> bytes_consumed;
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">前面已经分析过&nbsp;<code>delegate</code>&nbsp;是&nbsp;<code>TcpHoldler</code>&nbsp;的&nbsp;<code>delegate</code>,即&nbsp;<code>SocketSession::Delegate</code>&nbsp;对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> SocketSession::Delegate::OnWsFrame(<span style="color: rgba(0, 0, 255, 1)">const</span> std::vector&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt;&amp;<span style="color: rgba(0, 0, 0, 1)"> data) {
server_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">MessageReceived(session_id_,
                           std::</span><span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">(data.data(),
                           data.size()));
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">继续回调&nbsp;<code>server_-&gt;MessageReceived</code>。从结构图可以看到&nbsp;<code>server_</code>&nbsp;是&nbsp;<code>InspectorSocketServer</code>&nbsp;对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> MessageReceived(<span style="color: rgba(0, 0, 255, 1)">int</span> session_id, <span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
delegate_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">MessageReceived(session_id, message);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">继续回调&nbsp;<code>delegate_-&gt;MessageReceived</code>,<code>InspectorSocketServer</code>&nbsp;的&nbsp;<code>delegate_</code>&nbsp;是&nbsp;<code>InspectorIoDelegate</code>&nbsp;对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> InspectorIoDelegate::MessageReceived(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> session_id,
                                          </span><span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
auto session </span>=<span style="color: rgba(0, 0, 0, 1)"> sessions_.find(session_id);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (session !=<span style="color: rgba(0, 0, 0, 1)"> sessions_.end())
    session</span>-&gt;second-&gt;Dispatch(Utf8ToStringView(message)-&gt;<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">());
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">首先通过&nbsp;<code>session_id</code>&nbsp;找到对应的&nbsp;<code>session</code>。<code>session</code>&nbsp;是一个&nbsp;<code>CrossThreadInspectorSession</code>&nbsp;对象。看看他的&nbsp;<code>Dispatch</code>&nbsp;方法:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">void</span> Dispatch(<span style="color: rgba(0, 0, 255, 1)">const</span> StringView&amp; message) <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> {
    state_.Call(</span>&amp;<span style="color: rgba(0, 0, 0, 1)">MainThreadSessionState::Dispatch,
                StringBuffer::create(message));
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">执行&nbsp;<code>MainThreadSessionState::Dispatch</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> Dispatch(std::unique_ptr&lt;StringBuffer&gt;<span style="color: rgba(0, 0, 0, 1)"> message) {
session_</span>-&gt;Dispatch(message-&gt;<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">());
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>session_</code>&nbsp;是&nbsp;<code>SameThreadInspectorSession</code>&nbsp;对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> SameThreadInspectorSession::Dispatch(
    </span><span style="color: rgba(0, 0, 255, 1)">const</span> v8_inspector::StringView&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
auto client </span>= client_.<span style="color: rgba(0, 0, 255, 1)">lock</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (client)
    client</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">dispatchMessageFromFrontend(session_id_, message);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">继续调&nbsp;<code>client-&gt;dispatchMessageFromFrontend</code>:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">void</span> dispatchMessageFromFrontend(<span style="color: rgba(0, 0, 255, 1)">int</span> session_id, <span style="color: rgba(0, 0, 255, 1)">const</span> StringView&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
   channels_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">dispatchProtocolMessage(message);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">通过&nbsp;<code>session_id</code>&nbsp;找到对应的&nbsp;<code>ChannelImpl</code>,继续调&nbsp;<code>ChannelImpl</code>&nbsp;的&nbsp;<code>dispatchProtocolMessage</code>:</p>
<div class="cnblogs_code">
<pre> voiddispatchProtocolMessage(<span style="color: rgba(0, 0, 255, 1)">const</span> StringView&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
   session_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">dispatchProtocolMessage(message);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">最终调用和&nbsp;<code>V8 Inspector</code>&nbsp;的会话对象把数据发送给 V8。至此客户端到&nbsp;<code>V8 Inspector</code>&nbsp;的通信过程就完成了。</p>
<h2 data-tool="mdnice编辑器">3.5 V8 Inspector 到客户端的数据处理</h2>
<p data-tool="mdnice编辑器">接着看从&nbsp;<code>V8 inspector</code>&nbsp;到客户端的数据传递逻辑。<code>V8 inspector</code>&nbsp;是通过&nbsp;<code>channel</code>&nbsp;的&nbsp;<code>sendResponse</code>&nbsp;函数把数据传递给客户端的:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> sendResponse(
      </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> callId,
      std::unique_ptr</span>&lt;v8_inspector::StringBuffer&gt; message) <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> {

    sendMessageToFrontend(message</span>-&gt;<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">());
}

</span><span style="color: rgba(0, 0, 255, 1)">void</span> sendMessageToFrontend(<span style="color: rgba(0, 0, 255, 1)">const</span> StringView&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
    delegate_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">SendMessageToFrontend(message);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>delegate_</code>&nbsp;是&nbsp;<code>IoSessionDelegate</code>&nbsp;对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> SendMessageToFrontend(<span style="color: rgba(0, 0, 255, 1)">const</span> v8_inspector::StringView&amp; message) <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> {
    request_queue_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">Post(id_, TransportAction::kSendMessage,
                         StringBuffer::create(message));
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code><br><br>request_queue_&nbsp;是 RequestQueueData 对象。<br></code></pre>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">void</span> Post(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> session_id,
            TransportAction action,
            std::unique_ptr</span>&lt;StringBuffer&gt;<span style="color: rgba(0, 0, 0, 1)"> message) {

    Mutex::ScopedLock scoped_lock(state_lock_);
    </span><span style="color: rgba(0, 0, 255, 1)">bool</span> notify =<span style="color: rgba(0, 0, 0, 1)"> messages_.empty();
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 消息入队</span>
<span style="color: rgba(0, 0, 0, 1)">    messages_.emplace_back(action, session_id, std::move(message));
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (notify) {
      CHECK_EQ(</span><span style="color: rgba(128, 0, 128, 1)">0</span>, uv_async_send(&amp;<span style="color: rgba(0, 0, 0, 1)">async_));
      incoming_message_cond_.Broadcast(scoped_lock);
    }
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>Post</code>&nbsp;首先把消息入队,然后通过异步的方式通知&nbsp;<code>async_</code>,接着看&nbsp;<code>async_</code>&nbsp;的处理函数(在子线程的事件循环里执行):</p>
<div class="cnblogs_code">
<pre>uv_async_init(loop, &amp;async_, [](uv_async_t*<span style="color: rgba(0, 0, 0, 1)"> async) {
   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 拿到async对应的上下文</span>
   RequestQueueData* wrapper = node::ContainerOf(&amp;<span style="color: rgba(0, 0, 0, 1)">RequestQueueData::async_, async);
   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行RequestQueueData的DoDispatch</span>
   wrapper-&gt;<span style="color: rgba(0, 0, 0, 1)">DoDispatch();
});</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">回调函数里调用了&nbsp;<code>wrapper-&gt;DoDispatch()</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> DoDispatch() {
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">const</span> auto&amp;<span style="color: rgba(0, 0, 0, 1)"> request : GetMessages()) {
      request.Dispatch(server_);
    }
}

request 是 RequestToServer 对象。
</span><span style="color: rgba(0, 0, 255, 1)">void</span> Dispatch(InspectorSocketServer* server) <span style="color: rgba(0, 0, 255, 1)">const</span><span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (action_) {
      </span><span style="color: rgba(0, 0, 255, 1)">case</span><span style="color: rgba(0, 0, 0, 1)"> TransportAction::kSendMessage:
      server</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">Send(
            session_id_,
            protocol::StringUtil::StringViewToUtf8(message_</span>-&gt;<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">()));
      </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">接着看&nbsp;<code>InspectorSocketServer</code>&nbsp;的&nbsp;<code>Send</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> InspectorSocketServer::Send(<span style="color: rgba(0, 0, 255, 1)">int</span> session_id, <span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
SocketSession</span>* session =<span style="color: rgba(0, 0, 0, 1)"> Session(session_id);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (session !=<span style="color: rgba(0, 0, 0, 1)"> nullptr) {
    session</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">Send(message);
}
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>session</code>&nbsp;代表可客户端的一个连接:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> SocketSession::Send(<span style="color: rgba(0, 0, 255, 1)">const</span> std::<span style="color: rgba(0, 0, 255, 1)">string</span>&amp;<span style="color: rgba(0, 0, 0, 1)"> message) {
ws_socket_</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">Write(message.data(), message.length());
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">接着调用&nbsp;<code>WebSocket handler</code>&nbsp;的&nbsp;<code>Write</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">void</span> Write(<span style="color: rgba(0, 0, 255, 1)">const</span> std::vector&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt; data) <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> {
    std::vector</span>&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt; output =<span style="color: rgba(0, 0, 0, 1)"> encode_frame_hybi17(data);
    WriteRaw(output, WriteRequest::Cleanup);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器"><code>WriteRaw</code>&nbsp;是基类&nbsp;<code>ProtocolHandler</code>&nbsp;实现的:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">int</span> ProtocolHandler::WriteRaw(<span style="color: rgba(0, 0, 255, 1)">const</span> std::vector&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt;&amp;<span style="color: rgba(0, 0, 0, 1)"> buffer,
                              uv_write_cb write_cb) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> tcp_-&gt;<span style="color: rgba(0, 0, 0, 1)">WriteRaw(buffer, write_cb);
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">最终是通过 TCP 连接返回给客户端:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">int</span> TcpHolder::WriteRaw(<span style="color: rgba(0, 0, 255, 1)">const</span> std::vector&lt;<span style="color: rgba(0, 0, 255, 1)">char</span>&gt;&amp;<span style="color: rgba(0, 0, 0, 1)"> buffer, uv_write_cb write_cb) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Freed in write_request_cleanup</span>
WriteRequest* wr = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> WriteRequest(handler_, buffer);
uv_stream_t</span>* stream = reinterpret_cast&lt;uv_stream_t*&gt;(&amp;<span style="color: rgba(0, 0, 0, 1)">tcp_);
</span><span style="color: rgba(0, 0, 255, 1)">int</span> err = uv_write(&amp;wr-&gt;req, stream, &amp;wr-&gt;buf, <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">, write_cb);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (err &lt; <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">delete</span><span style="color: rgba(0, 0, 0, 1)"> wr;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> err &lt; <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<pre data-tool="mdnice编辑器"><code>&nbsp;</code></pre>
<p data-tool="mdnice编辑器">新建一个写请求,<code>socket</code>&nbsp;可写的时候发送数据给客户端。</p>
<h1 data-tool="mdnice编辑器">4 总结</h1>
<p data-tool="mdnice编辑器">从以上介绍和分析中,我们了解了&nbsp;<code>Node.js Inspector</code>&nbsp;的工作原理和使用。它方便了我们对&nbsp;<code>Node.js</code>&nbsp;的调试和问题排查,提高开发效率。通过它可以收集&nbsp;<code>Node.js</code>&nbsp;进程的堆快照分析是否有内存泄漏,可以收集&nbsp;<code>CPU Profile</code>&nbsp;分析代码的性能瓶颈,从而帮助提高服务的可用性和性能。另外,它支持动态开启,降低了安全风险,同时支持对子线程进行调试,是一个非常强大的工具。</p>
<p data-tool="mdnice编辑器">参考内容:1 Debugging Guide 2 inspector 3 开源的 inspector agent 实现 4 inspector 协议文档 5 Debugging Node.js with Chrome DevTools</p>
<p data-tool="mdnice编辑器">转自https://mp.weixin.qq.com/s/-fFwUGJrV5Rxia7KtoPkhg</p><br><br>
来源:https://www.cnblogs.com/cangqinglang/p/15178838.html
頁: [1]
查看完整版本: 深入理解 Node.js 的 Inspector