国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

深入理解 Node.js 的 Inspector

共 43140字,需瀏覽 87分鐘

 ·

2021-08-26 13:59

Node.js 提供的 Inspector 非常強(qiáng)大,不僅可以用來(lái)調(diào)試 Node.js 代碼,還可以實(shí)時(shí)收集 Node.js 進(jìn)程的 Heap Snapshot、Cpu Profile 等數(shù)據(jù),同時(shí)支持靜態(tài)、動(dòng)態(tài)開(kāi)啟,是一個(gè)非常強(qiáng)大的工具,也是我們調(diào)試和診斷 Node.js 進(jìn)程非常好的方式。本文從使用和原理詳細(xì)講解 Node.jsInspector。

Node.js 的文檔中對(duì) Inspector 的描述很少,但是如果深入探索,其實(shí)里面的內(nèi)容還是挺多的。我們先看一下 Inspector 的使用。

1 Inspector 的使用

1.1  本地調(diào)試

我們先從一個(gè)例子開(kāi)始,下面是一個(gè)簡(jiǎn)單的 HTTP 服務(wù)器。

const http = require('http');
http.createServer((req, res) => {
    res.end('ok');
}).listen(80);

然后我們以 node --inspect httpServer.js 的方式啟動(dòng)。我們可以看到以下輸出。

Debugger listening on ws://127.0.0.1:9229/fbbd9d8f-e088-48cc-b1e0-e16bfe58db44
For help, see: https://nodejs.org/en/docs/inspector

9229 端口是 Node.js 默認(rèn)選擇的端口,當(dāng)然我們也可以自定義,具體可參考 Node.js 官方文檔。這時(shí)候我們?nèi)g覽器打開(kāi)開(kāi)發(fā)者工具,菜單欄多了一個(gè)調(diào)試 Node.js 的按鈕。

點(diǎn)擊這個(gè)按鈕。我們可以看到以下界面(點(diǎn)擊切換到 Sources Tab)。

我們可以選擇某一行代碼打斷點(diǎn),比如我在第三行,這時(shí)候我們?cè)L問(wèn) 80 端口,開(kāi)發(fā)者工具就會(huì)停留在斷點(diǎn)處。這時(shí)候我們可以看到一些執(zhí)行上下文。

1.2 遠(yuǎn)程調(diào)試

但很多時(shí)候我們可能需要遠(yuǎn)程調(diào)試。比如我在一臺(tái)云服務(wù)器上部署以上服務(wù)器代碼。然后執(zhí)行

node --inspect=0.0.0.0:8888 httpServer.js

我們打開(kāi)開(kāi)發(fā)者工具發(fā)現(xiàn)按鈕置灰或者找不到我們遠(yuǎn)程服務(wù)器的信息。這時(shí)候我們需要用另一種方式,通過(guò)在瀏覽器url輸入框輸入:

devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={host}:{port}/{path} 

的方式(替換 {} 里面的內(nèi)容為你執(zhí)行 Node.js 時(shí)輸出的信息),瀏覽器就會(huì)去連接指定的地址,比如執(zhí)行上面的命令輸出的是 ws://0.0.0.0:8888/f6e42278-d915-48dc-af4d-453a23d330ab,假設(shè)公網(wǎng)IP是 1.1.1.1。那么最后瀏覽器url輸入框里就填入 devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=1.1.1.1:8888/f6e42278-d915-48dc-af4d-453a23d330ab 就可以開(kāi)始調(diào)試了,這種方式比較適合于常用的場(chǎng)景。

1.3 自動(dòng)探測(cè)

如果是我們自己調(diào)試的話(huà),1.2 這種方式看起來(lái)就有點(diǎn)麻煩,我們可以使用瀏覽器提供的自動(dòng)探測(cè)功能。

  1. URL 輸入框輸入 chrome://inspect/#devices 我們會(huì)看到以下界面
  1. 點(diǎn)擊 configure 按鈕,在彈出的彈框里輸入你遠(yuǎn)程服務(wù)器的地址
  1. 配置完畢后,我們會(huì)看到界面變成這樣了(或者打開(kāi)新的 Tab,我們看到開(kāi)發(fā)者工具的調(diào)試按鈕也變亮了)。
  1. 這時(shí)候我們點(diǎn)擊 inspect 按鈕、Open dedicated DevTools for Node 按鈕或者打開(kāi)新 Tab 的開(kāi)發(fā)者工具,就可以開(kāi)始調(diào)試,而且還可以調(diào)試 Node.js 的原生 JS 模塊。

1.4 收集數(shù)據(jù)

V8 Inspector 是一個(gè)非常強(qiáng)大的工具,調(diào)試只是它其中一個(gè)能力,他還可以獲取 Heap SnapshotCPU Profile 等數(shù)據(jù),具體能力請(qǐng)參考文章后面列出的指令文檔和 Chrome Dev Tools。

  1. 收集 Cpu Profile 信息
  1. 獲取 Heap Snapshop

1.5 動(dòng)態(tài)開(kāi)啟 Inspector

默認(rèn)打開(kāi) Inspector 能力是不安全的,這意味著能連上服務(wù)器的客戶(hù)端都能通過(guò)協(xié)議控制 Node.js 進(jìn)程(雖然 URL 并不容易猜對(duì)),通常我們是在 Node.js 進(jìn)程出現(xiàn)問(wèn)題的時(shí)候,動(dòng)態(tài)開(kāi)啟 Inspector,我們看一下下面的例子。

const inspector = require('inspector');
const http = require('http');

let isOpend = false;

function getHTML({
    return `<html>
      <meta charset="utf-8" />
      <body>
        復(fù)制到新 Tab 打開(kāi)該 URL 開(kāi)始調(diào)試 devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=${inspector.url().replace("ws://"'')}
      </body>
    </html>`
;
}

http.createServer((req, res) => {
  if (req.url == '/debug/open') {
        // 還沒(méi)開(kāi)啟則開(kāi)啟
        if (!isOpend) {
          isOpend = true;
          // 打開(kāi)調(diào)試器
          inspector.open();
        }
        // 返回給前端的內(nèi)容
        const html = getHTML() ;
        res.end(html);
  } else if (req.url == '/debug/close') {
        // 如果開(kāi)啟了則關(guān)閉
        if (isOpend) {
          inspector.close();
          isOpend = false;
        } 
        res.end('ok');
  } else {
    res.end('ok');
  }
}).listen(80);

當(dāng)我們需要調(diào)試的時(shí)候,通過(guò)訪(fǎng)問(wèn) /debug/open 打開(kāi)調(diào)試器。前端界面可以看到以下輸出。

復(fù)制到新 Tab 打開(kāi)該 URL 開(kāi)始調(diào)試 devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/9efd4c80-956a-4422-b23c-4348e6613304

接著新開(kāi)一個(gè) Tab,然后復(fù)制上面的 URL,粘貼到瀏覽器 URL 地址欄訪(fǎng)問(wèn),我們就可以看到調(diào)試頁(yè)面。

然后打個(gè)斷點(diǎn),接著新開(kāi)一個(gè) Tab 訪(fǎng)問(wèn) http://localhost 就可以進(jìn)入調(diào)試,調(diào)試完成后訪(fǎng)問(wèn) /debug/close 關(guān)閉調(diào)試器。瀏覽器界面就會(huì)顯示斷開(kāi)連接了。

以上方式支持調(diào)試和收集數(shù)據(jù),如果我們只是需要收集數(shù)據(jù),還有另一種動(dòng)態(tài)開(kāi)啟 Inspector 的方式

const http = require('http');
const inspector = require('inspector');
const fs = require('fs');

function getCpuprofile(req, res{
    // 打開(kāi)一個(gè)和 V8 Inspector 的會(huì)話(huà)
    const session = new inspector.Session();
    session.connect();
    // 向V8 Inspector 提交命令,開(kāi)啟 Cpu Profile 并收集數(shù)據(jù)
    session.post('Profiler.enable', () => {
    session.post('Profiler.start', () => {
      // 收集一段時(shí)間后提交停止收集命令
      setTimeout(() => {
        session.post('Profiler.stop', (err, { profile }) => {
          // 把數(shù)據(jù)寫(xiě)入文件
          if (!err) {
            fs.writeFileSync('./profile.cpuprofile'JSON.stringify(profile));
          }
          // 斷開(kāi)會(huì)話(huà)
          session.disconnect();
          // 回復(fù)客戶(hù)端
          res.end('ok');
        });
      }, 3000)
    });
  });
}

http.createServer((req, res) => {
  if (req.url == '/debug/getCpuprofile') {
        getCpuprofile(req, res);
  } else {
        res.end('ok');
  }
}).listen(80);

我們可以通過(guò) Inspector Session 的能力,實(shí)時(shí)和 V8 Inspector 交互而不需要啟動(dòng)一個(gè) WebSocket 服務(wù)。本地調(diào)試時(shí)還可以在 VSCode 里點(diǎn)擊 Profile 文件直接看到效果。

2 Inspector 調(diào)試的原理

下面以通過(guò) URL 的方式調(diào)試(可以看到 Network ),來(lái)看看調(diào)試的時(shí)候都發(fā)生了什么,瀏覽器和遠(yuǎn)程服務(wù)器建立連接后,是通過(guò) WebSocket 協(xié)議通信的,下面是一次通信的信息。

我們看一下這命令是什么意思(具體可以參考 Inspector 協(xié)議文檔)。

Debugger.scriptParsed # Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger.

從說(shuō)明中我們看到,當(dāng) V8 解析腳本的時(shí)候就會(huì)觸發(fā)這個(gè)事件,告訴瀏覽器相關(guān)的信息。

我們發(fā)現(xiàn)返回的都是一些元數(shù)據(jù),沒(méi)有腳本的具體代碼內(nèi)容,這時(shí)候?yàn)g覽器會(huì)再次發(fā)起請(qǐng)求(點(diǎn)擊對(duì)應(yīng)腳本對(duì)應(yīng)的 JS 文件時(shí)),

我們看到這個(gè)腳本的 scriptId 是 103。所以請(qǐng)求里帶了這個(gè) scriptId。對(duì)應(yīng)的請(qǐng)求 id 是 11。接著看一下響應(yīng)。

至此,我們了解了獲取腳本內(nèi)容的過(guò)程,然后我們看看調(diào)試的時(shí)候是怎樣的過(guò)程。當(dāng)我們?cè)跒g覽器上點(diǎn)擊某一行設(shè)置斷點(diǎn)的時(shí)候,瀏覽器就會(huì)發(fā)送一個(gè)請(qǐng)求。

這個(gè)命令的意義顧名思義,我們看一下具體定義:

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.

接著服務(wù)返回響應(yīng)。

這時(shí)候我們從另外一個(gè) Tab 訪(fǎng)問(wèn) 80 端口,服務(wù)器就會(huì)在我們?cè)O(shè)置的斷點(diǎn)處停留,并且通知瀏覽器。

我們看一下這個(gè)命令的意思。

這個(gè)命令就是當(dāng)服務(wù)器執(zhí)行到斷點(diǎn)時(shí)通知瀏覽器,并且返回執(zhí)行的一些上下文,比如執(zhí)行到哪個(gè)斷點(diǎn)停留了。這時(shí)候?yàn)g覽器側(cè)也會(huì)停留在對(duì)應(yīng)的地方,當(dāng)我們 hover 某個(gè)變量時(shí),就會(huì)看到對(duì)應(yīng)的上下文。這些都是通過(guò)具體的命令獲取的數(shù)據(jù)。就不一一分析了。

3 Node.js Inspector 的實(shí)現(xiàn)

大致了解了瀏覽器和服務(wù)器的交互過(guò)程和協(xié)議后,我們?cè)賮?lái)深入了解一下關(guān)于 Inspector 的一些實(shí)現(xiàn)。當(dāng)然這里不是分析 V8 中 Inspector 的實(shí)現(xiàn),而是分析如何使用 V8 的 Inspector 以及 Node.js 中關(guān)于 Inspector 的實(shí)現(xiàn)部分。

當(dāng)我們以以下方式執(zhí)行應(yīng)用時(shí)

node --inspect app.js

3.1 初始化

Node.js 在啟動(dòng)的過(guò)程中,就會(huì)初始化 Inspector 相關(guān)的邏輯。

inspector_agent_ = std::make_unique<inspector::Agent>(this);

Agent 是負(fù)責(zé)和 V8 Inspector 通信的對(duì)象,創(chuàng)建完后接著執(zhí)行 env->InitializeInspector({}) 啟動(dòng) Agent

inspector_agent_->Start(...);

Start 繼續(xù)執(zhí)行 Agent::StartIoThread

bool Agent::StartIoThread() {
  io_ = InspectorIo::Start(client_->getThreadHandle(), ...);
  return true;
}

StartIoThread 中的 client_->getThreadHandle() 是重要的邏輯,我們先來(lái)分析該函數(shù)。

std::shared_ptr<MainThreadHandle> getThreadHandle() {
    if (!interface_) {
      interface_ = std::make_shared<MainThreadInterface>(env_->inspector_agent(), ...);
    }
    return interface_->GetHandle();
}

getThreadHandle 首先創(chuàng)建來(lái)一個(gè) MainThreadInterface 對(duì)象,接著又調(diào)用了他的 GetHandle 方法,我們看一下該方法的邏輯。

std::shared_ptr<MainThreadHandle> MainThreadInterface::GetHandle() {
  if (handle_ == nullptr)
    handle_ = std::make_shared<MainThreadHandle>(this);
  return handle_;
}

GetHandle 了創(chuàng)建了一個(gè) MainThreadHandle 對(duì)象,最終結(jié)構(gòu)如下所示。

分析完后我們繼續(xù)看 Agent::StartIoThreadInspectorIo::Start 的邏輯。

std::unique_ptr<InspectorIo> InspectorIo::Start(std::shared_ptr<MainThreadHandle> main_thread, ...) {
  auto io = std::unique_ptr<InspectorIo>(new InspectorIo(main_thread, ...));
  return io;
}

InspectorIo::Star 里新建了一個(gè) InspectorIo 對(duì)象,我們看看 InspectorIo 構(gòu)造函數(shù)的邏輯。

InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread, ...)
    : 
    // 初始化 main_thread_
    main_thread_(main_thread)) {
  // 新建一個(gè)子線(xiàn)程,子線(xiàn)程中執(zhí)行 InspectorIo::ThreadMain
  uv_thread_create(&thread_, InspectorIo::ThreadMain, this);
}

這時(shí)候結(jié)構(gòu)如下:

InspectorIo 創(chuàng)建了一個(gè)子線(xiàn)程, Inspector 在子線(xiàn)程里啟動(dòng)的原因主要有兩個(gè)。

  1. 如果在主線(xiàn)程里運(yùn)行,那么當(dāng)我們斷點(diǎn)調(diào)試的時(shí)候,Node.js 主線(xiàn)程就會(huì)被停住,也就無(wú)法處理客戶(hù)端發(fā)過(guò)來(lái)的調(diào)試指令。
  2. 如果主線(xiàn)程陷入死循環(huán),我們就無(wú)法實(shí)時(shí)抓取進(jìn)程的 Profile 數(shù)據(jù)來(lái)分析原因。

接著繼續(xù)看一下子線(xiàn)程里執(zhí)行 InspectorIo::ThreadMain 的邏輯:

void InspectorIo::ThreadMain(void* io) {
  static_cast<InspectorIo*>(io)->ThreadMain();
}

void InspectorIo::ThreadMain() {
  uv_loop_t loop;
  loop.data = nullptr;
  // 在子線(xiàn)程開(kāi)啟一個(gè)新的事件循環(huán)
  int err = uv_loop_init(&loop);
  std::shared_ptr<RequestQueueData> queue(new RequestQueueData(&loop), ...);
  // 新建一個(gè) delegate,用于處理請(qǐng)求
  std::unique_ptr<InspectorIoDelegate> delegate(
      new InspectorIoDelegate(queue, main_thread_, ...)
  )
;
  InspectorSocketServer server(std::move(delegate), ...);
  server.Start();
  // 進(jìn)入事件循環(huán)
  uv_run(&loop, UV_RUN_DEFAULT);
}

ThreadMain 主要有三個(gè)邏輯:

  1. 創(chuàng)建一個(gè) delegate 對(duì)象,該對(duì)象是核心的對(duì)象,后面我們會(huì)看到有什么作用。
  2. 創(chuàng)建一個(gè)服務(wù)器并啟動(dòng)。
  3. 開(kāi)啟事件循環(huán)。

接下來(lái)看一下服務(wù)器的邏輯,首先看一下創(chuàng)建服務(wù)器的邏輯:

InspectorSocketServer::InspectorSocketServer(std::unique_ptr<SocketServerDelegate> delegate, ...)
    : // 保存 delegate
      delegate_(std::move(delegate)),
      // 初始化 sessionId
      next_session_id_(0) {
  // 設(shè)置 delegate 的 server 為當(dāng)前服務(wù)器
  delegate_->AssignServer(this);
}

執(zhí)行完后形成以下結(jié)構(gòu):

接著我們看啟動(dòng)服務(wù)器的邏輯:

bool InspectorSocketServer::Start() {
  // DNS 解析,比如輸入的是localhost
  struct addrinfo hints;
  memset(&hints, 0sizeof(hints));
  hints.ai_flags = AI_NUMERICSERV;
  hints.ai_socktype = SOCK_STREAM;
  uv_getaddrinfo_t req;
  const std::string port_string = std::to_string(port_);
  uv_getaddrinfo(loop_, &req, nullptr, host_.c_str(),
                           port_string.c_str(), &hints);
  // 監(jiān)聽(tīng)解析到的 IP 列表                 
  for (addrinfo* address = req.addrinfo; 
         address != nullptr;
       address = address->ai_next) {

    auto server_socket = ServerSocketPtr(new ServerSocket(this));
    err = server_socket->Listen(address->ai_addr, loop_);
    if (err == 0)
      server_sockets_.push_back(std::move(server_socket));

  }

  return true;
}

首先根據(jù)參數(shù)做 DNS 解析,然后根據(jù)拿到的 IP 列表(通常是一個(gè)),創(chuàng)建對(duì)應(yīng)個(gè)數(shù)的 ServerSocket 對(duì)象,并執(zhí)行它的 Listen 方法。ServerSocket 表示一個(gè)監(jiān)聽(tīng) socket,看一下 ServerSocket 的構(gòu)造函數(shù):

ServerSocket(InspectorSocketServer* server) : 
    tcp_socket_(uv_tcp_t()), server_(server) {}

執(zhí)行完后結(jié)構(gòu)如下:

接著看一下 ServerSocketListen 方法:

int ServerSocket::Listen(sockaddr* addr, uv_loop_t* loop) {
  uv_tcp_t* server = &tcp_socket_;
  uv_tcp_init(loop, server)
  uv_tcp_bind(server, addr, 0);
  uv_listen(reinterpret_cast<uv_stream_t*>(server), 
            511,
            ServerSocket::SocketConnectedCallback);
}

Listen 調(diào)用 Libuv 的接口完成服務(wù)器的啟動(dòng)。至此,Inspector 提供的 Weboscket 服務(wù)器啟動(dòng)了。

3.2 處理連接

從剛才分析中可以看到,當(dāng)有連接到來(lái)時(shí)執(zhí)行回調(diào) ServerSocket::SocketConnectedCallback

void ServerSocket::SocketConnectedCallback(uv_stream_t* tcp_socket,
                                           int status)
 
{
  if (status == 0) {
    // 根據(jù) Libuv handle 找到對(duì)應(yīng)的 ServerSocket 對(duì)象
    ServerSocket* server_socket = ServerSocket::FromTcpSocket(tcp_socket);
    // Socket 對(duì)象的 server_ 字段保存了所在的 InspectorSocketServer
    server_socket->server_->Accept(server_socket->port_, tcp_socket);
  }
}

接著看 InspectorSocketServerAccept 是如何處理連接的:

void InspectorSocketServer::Accept(int server_port,
                                   uv_stream_t* server_socket)
 
{

  std::unique_ptr<SocketSession> session(
      new SocketSession(this, next_session_id_++, server_port)
  )
;

  InspectorSocket::DelegatePointer delegate =
      InspectorSocket::DelegatePointer(
          new SocketSession::Delegate(this, session->id())
      );

  InspectorSocket::Pointer inspector =
      InspectorSocket::Accept(server_socket, std::move(delegate));

  if (inspector) {
    session->Own(std::move(inspector));
    connected_sessions_[session->id()].second = std::move(session);
  }
}

Accept 的首先創(chuàng)建里一個(gè) SocketSessionSocketSession::Delegate 對(duì)象。然后調(diào)用 InspectorSocket::Accept,從代碼中可以看到 InspectorSocket::Accept 會(huì)返回一個(gè) InspectorSocket 對(duì)象。InspectorSocket 是對(duì)通信 socket 的封裝(和客戶(hù)端通信的 socket,區(qū)別于服務(wù)器的監(jiān)聽(tīng) socket)。然后記錄 session 對(duì)象對(duì)應(yīng)的 InspectorSocket 對(duì)象,同時(shí)記錄 sessionIdsession 的映射關(guān)系。結(jié)構(gòu)如下圖所示:

接著看一下 InspectorSocket::Accept 返回 InspectorSocket 的邏輯:

InspectorSocket::Pointer InspectorSocket::Accept(uv_stream_t* server,
                                                 DelegatePointer delegate)
 
{
  auto tcp = TcpHolder::Accept(server, std::move(delegate));
  InspectorSocket* inspector = new InspectorSocket();
  inspector->SwitchProtocol(new HttpHandler(inspector, std::move(tcp)));
  return InspectorSocket::Pointer(inspector);
}

InspectorSocket::Accept 的代碼不多,但是邏輯還是挺多的:

  1. InspectorSocket::Accept 再次調(diào)用 TcpHolder::Accept 獲取一個(gè) TcpHolder 對(duì)象。
TcpHolder::Pointer TcpHolder::Accept(
    uv_stream_t* server,
    InspectorSocket::DelegatePointer delegate)
 
{
    
  // 新建一個(gè) TcpHolder 對(duì)象,TcpHolder 是對(duì) uv_tcp_t 和 delegate 的封裝
  TcpHolder* result = new TcpHolder(std::move(delegate));
  // 拿到 TcpHolder 對(duì)象的 uv_tcp_t 結(jié)構(gòu)體
  uv_stream_t* tcp = reinterpret_cast<uv_stream_t*>(&result->tcp_);
  // 初始化
  int err = uv_tcp_init(server->loop, &result->tcp_);
  // 摘取一個(gè) TCP 連接對(duì)應(yīng)的 fd 保存到 TcpHolder 的 uv_tcp_t 結(jié)構(gòu)體中(即第二個(gè)參數(shù)的 tcp 字段)
  uv_accept(server, tcp);
  // 注冊(cè)等待可讀事件,有數(shù)據(jù)時(shí)執(zhí)行 OnDataReceivedCb 回調(diào)
  uv_read_start(tcp, allocate_buffer, OnDataReceivedCb);
  return TcpHolder::Pointer(result);
}
  1. 新建一個(gè) HttpHandler 對(duì)象:
explicit HttpHandler(InspectorSocket* inspector, TcpHolder::Pointer tcp)
                     : ProtocolHandler(inspector, std::move(tcp))
{

  llhttp_init(&parser_, HTTP_REQUEST, &parser_settings);
  llhttp_settings_init(&parser_settings);
  parser_settings.on_header_field = OnHeaderField;
  // ...
}

ProtocolHandler::ProtocolHandler(InspectorSocket* inspector,
                                 TcpHolder::Pointer tcp)
                                 : inspector_(inspector), tcp_(std::move(tcp)) {
  // 設(shè)置 TCP 數(shù)據(jù)的 handler,TCP 是只負(fù)責(zé)傳輸,數(shù)據(jù)的解析交給 handler 處理                               
  tcp_->SetHandler(this);
}

HttpHandler 是對(duì) TcpHolder 的封裝,主要通過(guò) HTTP 解析器 llhttp 對(duì) HTTP 協(xié)議進(jìn)行解析。

  1. 調(diào)用 inspector->SwitchProtocol() 切換當(dāng)前協(xié)議處理器為 HTTP,建立 TCP 連接后,首先要經(jīng)過(guò)一個(gè) HTTP 請(qǐng)求從 HTTP 協(xié)議升級(jí)到 WebSocket 協(xié)議,升級(jí)成功后就使用 Websocket 協(xié)議進(jìn)行通信.

我們看一下這時(shí)候的結(jié)構(gòu)圖:

至此,就完成了連接處理的分析!(撒花,你學(xué)廢了么)

3.3 協(xié)議升級(jí)

完成了 TCP 連接的處理后,接下來(lái)要完成協(xié)議升級(jí),因?yàn)?Inspector 是通過(guò) WebSocket 協(xié)議和客戶(hù)端通信的,所以需要通過(guò)一個(gè) HTTP 請(qǐng)求來(lái)完成 HTTP 到 WebSocekt 協(xié)議的升級(jí)。從剛才的分析中看當(dāng)有數(shù)據(jù)到來(lái)時(shí)會(huì)執(zhí)行 OnDataReceivedCb 回調(diào):

void TcpHolder::OnDataReceivedCb(uv_stream_t* tcp, ssize_t nread,
                                 const uv_buf_t* buf)
 
{
  TcpHolder* holder = From(tcp);
  holder->ReclaimUvBuf(buf, nread);
  // 調(diào)用 handler 的 onData,目前 handler 是 HTTP 協(xié)議
  holder->handler_->OnData(&holder->buffer);
}

TCP 層收到數(shù)據(jù)后交給應(yīng)用層解析,直接調(diào)用上層的 OnData 回調(diào)。

void OnData(std::vector<char>* data) override {
    // 解析 HTTP 協(xié)議
    llhttp_execute(&parser_, data->data(), data->size());
    // 解析完并且是升級(jí)協(xié)議的請(qǐng)求則調(diào)用 delegate 的回調(diào) OnSocketUpgrade
    delegate()->OnSocketUpgrade(event.host, event.path, event.ws_key);
}

OnData 可能會(huì)被多次回調(diào),并通過(guò) llhttp_execute 解析收到的 HTTP 報(bào)文,當(dāng)發(fā)現(xiàn)是一個(gè)協(xié)議升級(jí)的請(qǐng)求后,就調(diào)用 OnSocketUpgrade 回調(diào)。delegate 是一個(gè) SocketSession::Delegate 對(duì)象。來(lái)看一下該對(duì)象的 OnSocketUpgrade 方法:

void SocketSession::Delegate::OnSocketUpgrade(const std::string& host,
                                              const std::string& path,
                                              const std::string& ws_key) {
  std::string id = path.empty() ? path : path.substr(1);
  server_->SessionStarted(session_id_, id, ws_key);
}

OnSocketUpgrade 又調(diào)用了 server_InspectorSocketServer 對(duì)象)的 SessionStarted

void InspectorSocketServer::SessionStarted(int session_id,
                                           const std::string& id,
                                           const std::string& ws_key)
 
{
  // 找到對(duì)應(yīng)的 session 對(duì)象                                           
  SocketSession* session = Session(session_id);
  connected_sessions_[session_id].first = id;
  session->Accept(ws_key);
  delegate_->StartSession(session_id, id);
}

首先通過(guò) session_id 找到建立 TCP 連接時(shí)分配的 SocketSession 對(duì)象:

  1. 執(zhí)行 session->Accept(ws_key) 回復(fù)客戶(hù)端同意協(xié)議升級(jí):
void Accept(const std::string& ws_key) {
  ws_socket_->AcceptUpgrade(ws_key);
}

從結(jié)構(gòu)圖我們可以看到 ws_socket_ 是一個(gè) InspectorSocket 對(duì)象:

void AcceptUpgrade(const std::string& accept_key) override {
    char accept_string[ACCEPT_KEY_LENGTH];
    generate_accept_string(accept_key, &accept_string);
    const char accept_ws_prefix[] = "HTTP/1.1 101 Switching Protocols\r\n"
                                    "Upgrade: websocket\r\n"
                                    "Connection: Upgrade\r\n"
                                    "Sec-WebSocket-Accept: ";
    // ...
    // 回復(fù) 101 給客戶(hù)端             
    WriteRaw(reply, WriteRequest::Cleanup);
    // 切換 handler 為 WebSocket handler
    inspector_->SwitchProtocol(new WsHandler(inspector_, std::move(tcp_)));
}

AcceptUpgradeh 首先回復(fù)客戶(hù)端 101 表示同意升級(jí)到 WebSocket 協(xié)議,然后切換數(shù)據(jù)處理器為 WsHandler,即后續(xù)的數(shù)據(jù)按照 WebSocket 協(xié)議處理。

  1. 執(zhí)行 delegate_->StartSession(session_id, id) 建立和 V8 Inspector 的會(huì)話(huà)。delegate_  是 InspectorIoDelegate 對(duì)象:
void InspectorIoDelegate::StartSession(int session_id,
                                       const std::string& target_id)
 
{
  auto session = main_thread_->Connect(
      std::unique_ptr<InspectorSessionDelegate>(
          new IoSessionDelegate(request_queue_->handle(), session_id)
      ), 
      true);
  if (session) {
    sessions_[session_id] = std::move(session);
    fprintf(stderr"Debugger attached.\n");
  }
}

首先通過(guò) main_thread_->Connect 拿到一個(gè) session,并在 InspectorIoDelegate 中記錄映射關(guān)系。結(jié)構(gòu)圖如下:

接下來(lái)看一下 main_thread_->Connect 的邏輯(main_thread_MainThreadHandle 對(duì)象):

std::unique_ptr<InspectorSession> MainThreadHandle::Connect(
    std::unique_ptr<InspectorSessionDelegate> delegate,
    bool prevent_shutdown)
 
{

  return std::unique_ptr<InspectorSession>(
      new CrossThreadInspectorSession(++next_session_id_,
                                      shared_from_this(),
                                      std::move(delegate),
                                      prevent_shutdown));
}

Connect 函數(shù)新建了一個(gè) CrossThreadInspectorSession 對(duì)象。CrossThreadInspectorSession 構(gòu)造函數(shù)如下:

 CrossThreadInspectorSession(...) {
    // 執(zhí)行 MainThreadSessionState::Connect                             
    state_.Call(&MainThreadSessionState::Connect, std::move(delegate));
 }

繼續(xù)看 MainThreadSessionState::Connect

void Connect(std::unique_ptr<InspectorSessionDelegate> delegate) {
    Agent* agent = thread_->inspector_agent();
    session_ = agent->Connect(std::move(delegate), prevent_shutdown_);
}

繼續(xù)調(diào) agent->Connect

std::unique_ptr<InspectorSession> Agent::Connect(
    std::unique_ptr<InspectorSessionDelegate> delegate,
    bool prevent_shutdown)
 
{

  int session_id = client_->connectFrontend(std::move(delegate),
                                            prevent_shutdown);
  return std::unique_ptr<InspectorSession>(
      new SameThreadInspectorSession(session_id, client_));
}

繼續(xù)調(diào) connectFrontend

  int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate,
                      bool prevent_shutdown)
 
{
    int session_id = next_session_id_++;
    channels_[session_id] = std::make_unique<ChannelImpl>(env_,
                                                          client_,
                                                          getWorkerManager(),
                                                          std::move(delegate),
                                                          getThreadHandle(),
                                                          prevent_shutdown);
    return session_id;
  }

connectFrontend 創(chuàng)建了一個(gè) ChannelImpl 并且在 channels_ 中保存了映射關(guān)系??纯?ChannelImpl 的構(gòu)造函數(shù):

explicit ChannelImpl(Environment* env,
                     const std::unique_ptr<V8Inspector>& inspector,
                     std::unique_ptr<InspectorSessionDelegate> delegate, ...)

      : delegate_(std::move(delegate)) 
{

    session_ = inspector->connect(CONTEXT_GROUP_ID, this, StringView());
}

ChannelImpl 調(diào)用 inspector->connect 建立了一個(gè)和 V8 Inspector 的會(huì)話(huà)。結(jié)構(gòu)圖大致如下:

客戶(hù)端到 Node.jsV8 Inspector 的整體架構(gòu)如下:

3.4 客戶(hù)端到 V8 Inspector 的數(shù)據(jù)處理

TCP 連接建立了,協(xié)議升級(jí)也完成了,接下來(lái)就可以開(kāi)始處理業(yè)務(wù)數(shù)據(jù)。從前面的分析中我們已經(jīng)知道數(shù)據(jù)到來(lái)時(shí)會(huì)執(zhí)行 TcpHoldlerhandler_->OnData 回調(diào)。因?yàn)橐呀?jīng)完成了協(xié)議升級(jí),所以這時(shí)候的 handler 變成了 WeSocket handler

  void OnData(std::vector<char>* data) override 
    int processed 
0;
    do {
      processed = ParseWsFrames(*data);
      // ...
    } while (processed > 0 && !data->empty());
  }

OnData 通過(guò) ParseWsFrames 解析 WebSocket 協(xié)議:

int ParseWsFrames(const std::vector<char>& buffer) {
    int bytes_consumed = 0;
    std::vector<char> output;
    bool compressed = false;
    // 解析WebSocket協(xié)議
    ws_decode_result r =  decode_frame_hybi17(buffer,
                                              true /* client_frame */,
                                              &bytes_consumed, &output,
                                              &compressed);
    // 執(zhí)行delegate的回調(diào)                                        
    delegate()->OnWsFrame(output);
    return bytes_consumed;
  }

前面已經(jīng)分析過(guò) delegateTcpHoldlerdelegate,即 SocketSession::Delegate 對(duì)象:

void SocketSession::Delegate::OnWsFrame(const std::vector<char>& data) {
  server_->MessageReceived(session_id_,
                           std::string(data.data(), 
                           data.size()));
}

繼續(xù)回調(diào) server_->MessageReceived。從結(jié)構(gòu)圖可以看到 server_InspectorSocketServer 對(duì)象:

void MessageReceived(int session_id, const std::string& message) {
  delegate_->MessageReceived(session_id, message);
}

繼續(xù)回調(diào) delegate_->MessageReceived,InspectorSocketServerdelegate_InspectorIoDelegate 對(duì)象:

void InspectorIoDelegate::MessageReceived(int session_id,
                                          const std::string& message)
 
{
  auto session = sessions_.find(session_id);
  if (session != sessions_.end())
    session->second->Dispatch(Utf8ToStringView(message)->string());
}

首先通過(guò) session_id 找到對(duì)應(yīng)的 sessionsession 是一個(gè) CrossThreadInspectorSession 對(duì)象。看看他的 Dispatch 方法:

 void Dispatch(const StringView& message) override {
    state_.Call(&MainThreadSessionState::Dispatch,
                StringBuffer::create(message));
  }

執(zhí)行 MainThreadSessionState::Dispatch

void Dispatch(std::unique_ptr<StringBuffer> message) {
  session_->Dispatch(message->string());
}

session_SameThreadInspectorSession 對(duì)象:

void SameThreadInspectorSession::Dispatch(
    const v8_inspector::StringView& message)
 
{
  auto client = client_.lock();
  if (client)
    client->dispatchMessageFromFrontend(session_id_, message);
}

繼續(xù)調(diào) client->dispatchMessageFromFrontend

 void dispatchMessageFromFrontend(int session_id, const StringView& message) {
   channels_[session_id]->dispatchProtocolMessage(message);
 }

通過(guò) session_id 找到對(duì)應(yīng)的 ChannelImpl,繼續(xù)調(diào) ChannelImpldispatchProtocolMessage

 voiddispatchProtocolMessage(const StringView& message) {
   session_->dispatchProtocolMessage(message);
 }

最終調(diào)用和 V8 Inspector 的會(huì)話(huà)對(duì)象把數(shù)據(jù)發(fā)送給 V8。至此客戶(hù)端到 V8 Inspector 的通信過(guò)程就完成了。

3.5 V8 Inspector 到客戶(hù)端的數(shù)據(jù)處理

接著看從 V8 inspector 到客戶(hù)端的數(shù)據(jù)傳遞邏輯。V8 inspector 是通過(guò) channelsendResponse 函數(shù)把數(shù)據(jù)傳遞給客戶(hù)端的:

 void sendResponse(
      int callId,
      std::unique_ptr<v8_inspector::StringBuffer> message)
 override 
{

    sendMessageToFrontend(message->string());
  }

 void sendMessageToFrontend(const StringView& message) {
    delegate_->SendMessageToFrontend(message);
 }

delegate_IoSessionDelegate 對(duì)象:

void SendMessageToFrontend(const v8_inspector::StringView& message) override {
    request_queue_->Post(id_, TransportAction::kSendMessage,
                         StringBuffer::create(message));
  }

request_queue_ 是 RequestQueueData 對(duì)象。
 void Post(int session_id,
            TransportAction action,
            std::unique_ptr<StringBuffer> message)
 
{

    Mutex::ScopedLock scoped_lock(state_lock_);
    bool notify = messages_.empty();
    // 消息入隊(duì)
    messages_.emplace_back(action, session_id, std::move(message));
    if (notify) {
      CHECK_EQ(0, uv_async_send(&async_));
      incoming_message_cond_.Broadcast(scoped_lock);
    }
  }

Post 首先把消息入隊(duì),然后通過(guò)異步的方式通知 async_,接著看 async_ 的處理函數(shù)(在子線(xiàn)程的事件循環(huán)里執(zhí)行):

uv_async_init(loop, &async_, [](uv_async_t* async) {
   // 拿到async對(duì)應(yīng)的上下文
   RequestQueueData* wrapper = node::ContainerOf(&RequestQueueData::async_, async);
   // 執(zhí)行RequestQueueData的DoDispatch
   wrapper->DoDispatch();
});

回調(diào)函數(shù)里調(diào)用了 wrapper->DoDispatch()

void DoDispatch() {
    for (const auto& request : GetMessages()) {
      request.Dispatch(server_);
    }
}

request 是 RequestToServer 對(duì)象。
  void Dispatch(InspectorSocketServer* server) const {
    switch (action_) {
      case TransportAction::kSendMessage:
        server->Send(
            session_id_,
            protocol::StringUtil::StringViewToUtf8(message_->string()));
        break;
    }
  }

接著看 InspectorSocketServerSend

void InspectorSocketServer::Send(int session_id, const std::string& message) {
  SocketSession* session = Session(session_id);
  if (session != nullptr) {
    session->Send(message);
  }
}

session 代表可客戶(hù)端的一個(gè)連接:

void SocketSession::Send(const std::string& message) {
  ws_socket_->Write(message.data(), message.length());
}

接著調(diào)用 WebSocket handlerWrite

  void Write(const std::vector<char> data) override {
    std::vector<char> output = encode_frame_hybi17(data);
    WriteRaw(output, WriteRequest::Cleanup);
  }

WriteRaw 是基類(lèi) ProtocolHandler 實(shí)現(xiàn)的:

int ProtocolHandler::WriteRaw(const std::vector<char>& buffer,
                              uv_write_cb write_cb)
 
{
  return tcp_->WriteRaw(buffer, write_cb);
}

最終是通過(guò) TCP 連接返回給客戶(hù)端:

int TcpHolder::WriteRaw(const std::vector<char>& buffer, uv_write_cb write_cb) {
  // Freed in write_request_cleanup
  WriteRequest* wr = new WriteRequest(handler_, buffer);
  uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(&tcp_);
  int err = uv_write(&wr->req, stream, &wr->buf, 1, write_cb);
  if (err < 0)
    delete wr;
  return err < 0;
}

新建一個(gè)寫(xiě)請(qǐng)求,socket 可寫(xiě)的時(shí)候發(fā)送數(shù)據(jù)給客戶(hù)端。

4 總結(jié)

從以上介紹和分析中,我們了解了 Node.js Inspector 的工作原理和使用。它方便了我們對(duì) Node.js 的調(diào)試和問(wèn)題排查,提高開(kāi)發(fā)效率。通過(guò)它可以收集 Node.js 進(jìn)程的堆快照分析是否有內(nèi)存泄漏,可以收集 CPU Profile 分析代碼的性能瓶頸,從而幫助提高服務(wù)的可用性和性能。另外,它支持動(dòng)態(tài)開(kāi)啟,降低了安全風(fēng)險(xiǎn),同時(shí)支持對(duì)子線(xiàn)程進(jìn)行調(diào)試,是一個(gè)非常強(qiáng)大的工具。

參考內(nèi)容:1 Debugging Guide 2 inspector 3 開(kāi)源的 inspector agent 實(shí)現(xiàn) 4 inspector 協(xié)議文檔 5 Debugging Node.js with Chrome DevTools


內(nèi)推社群


我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對(duì)加入騰訊感興趣的話(huà)(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


瀏覽 48
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)
評(píng)論
圖片
表情
推薦
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 午夜一本道| 国产com| 美女中文字幕| 翔田千里无码| 亚洲艹逼| 久久韩国| 热久久伊人| www日韩欧美| 男人av网站| 精品国产三级| 深爱五月网| 青青草乱伦视频| 亚洲网站在线免费观看| 97国产在线| 青青草亚洲| 欧美一卡| av网站在线播放| 免费黄色小视频在线观看| 三级无码视频| 日本天堂网| 中文字幕日韩高清| 久久久久久免费毛片精品| 中文字幕一二三四| 国产淫乱视频| 久操网站| 国产一区不卡| 日韩人妻在线视频| www.99国产| 国产资源AV| 国产精品久久77777| 久久青青婷婷| 超碰99在线| 免费精品99| 国产AV久久| 精品无码一区二区三区蜜桃李宗瑞| 99视频自拍| 麻豆蜜桃wwww精品无码| 大香蕉伊人视频| 色片在线观看| 久久久久无码精品国产91福利| 国产亚洲欧美视频| 亚洲无码中文字幕视频| 老司机福利在线视频| 欧美日韩在线免费观看| 伊人亚洲综合| 天堂操逼| 无套内射学生妹去看片| 五月天婷婷激情视频| 无码熟妇人妻无码AV在线天堂| 人妻无码精品久久人妻成人| 黄色电影免费网站| 婷婷五月综合久久中文字幕| 51妺妺嘿嘿午夜成人A片| 91色在线观看| 欧美日韩一级A片| 东北操逼视频| 97人妻精品一区二区三区软件| 91人妻一区二区三区无不码超满| 国产精品18进进出出17c| 91国产爽黄在线相亲| 高清无码电影| 国产黄色小电影| 国产亚洲久一区二区三区| 免费91| 无码人妻AV一区| 夜夜撸天天日| 91人妻无码视频| 最新福利视频| 91人妻视频| 日本欧美国产| 亚洲一级黄| 蜜芽av在线| 成人在线超碰| 自拍乱伦| 久久久久久综合| 无码秘蜜桃吴梦梦| 91人人妻人人操| 男女黄色免费网站| 成人性爱视频免费在线观看| 天天摸天天摸| 亚洲国产成人精品综合99| 成人午夜黄色| 中国熟妇| 国产大鸡吧| 怡红院av| 国内自拍视频在线观看| 无码人妻丰满熟妇| 五月丁香天堂网| 欧美成人网站在线| 美女黄色免费网站| 国产精品成人免费久久黄AV片| 国产成人精品av在线观看| 99精品视频国产| 国产小视频在线免费观看| 少妇成人网| 国产wwwww| 青青操原| 伊人婷婷久久| 日韩黄色A片| 久久久久久麻豆| 少妇高潮av久久久久久| 天天躁狠狠躁av| 黄色操逼网站?| 国产精品资源| 精品网站999www| 国产AV影视| 日韩人妻精品中文字幕| 成人一级A片| 日日av| 国精产品一区二区三区黑人和中国 | 亚洲AV无码一区二区三竹菊| 韩国精品久久久| 超碰91在线| 国产黄色电影在线观看| 欧美日逼| 日本高清版色视频| 色色色色色欧美网| 汇聚全球淫荡熟女| 成人a一级片| 九九久久影院| 亚洲去干网| 婷婷五月大香蕉| 欧美婷婷五月| r四虎18| 日屄免费视频| 天堂成人AV| 亚洲人人爱| 欧美老女人的逼| 在线日韩国产| 亚洲黄色视频在线观看网站| 嫩草导航| 亚洲AV黄色| 夜夜爱视频| 伊人黄色电影| 日本精品黄色| 初尝人妻滑进去了莹莹视频| 久久久WWW成人免费精品| 激情亚洲婷婷| 97人妻精品一区二区三区视频| 免费操| 日韩欧美午夜成人无码| 少妇搡BBBB搡BBB搡视频一级 | 欧美成人性爱图片| 中文字幕第315页| 嫩BBB槡BBBB槡BBBB撒尿| 国产91在线亚洲| 九九九中文字幕| 三级三级久久三级久久18| 特级西西444www高清| 91久久久无码国产一区二区三区| 丁香久久| 亚洲无码视频在线播放| 少妇被躁到高潮无码| 国产亚洲欧美一区二区| 青娱乐国产精品| 中文字幕无吗| 成人A片免费在线观看| 亚洲中文中出| 欧美色逼逼| 久久理论| 丁香五月婷婷在线| 国产精品人人| 三上悠亚一区二区| 人人摸人人射| 色婷婷亚洲婷婷| 国产精品码一本A片| 无码人妻一区二区三区三| 欧洲三级片网站| 日韩aaa| 伊人天天色| 日本无码片| 久久免费毛片| 五月天婷婷网站| 黄色无码av| 国产精品911| 免费的操逼视频| 午夜看黄| 无码av无码AV| 天天撸天天色| 欧美成人精品激情在线观看| 亚洲社区在线观看| 嫩草视频网站| 国产欧美日本| 无码-ThePorn| 中文字幕人妻精品一区| 亚洲激情综合视频| 国产资源AV| 日日操夜夜| 苍井空一区二区三区| 17c.白丝喷水自慰| 中文字幕视频| 自拍偷拍免费| 黄色不卡视频| 国内精品久久久久久久久久| 国产男女无套免费视频| 色xxxx| www.bbbb| 最新国产第一页| www.大香蕉伊人| 四色影视| 成人国产在线无码AV免费| 久久99精品久久久水蜜桃| 大香蕉操逼视频| 青草社区在线观看| 国产精品精品| 国产一区二三区| 口爆AV| 黑人操白人| 草逼免费视频| av天天日| 视频一二三区| 99久热在线精品视频| 国产V在线观看| 一级A片一毛片大全| 天天久久| 成年人免费看视频| 日韩欧美精品在线| 91久久久久国产一区二区| 无码精品一区二区三区同学聚会| 无码国产精品一区二区| 夜夜躁狠狠躁| 久久国产乱子伦精品免费午夜...| 在线小黄片| 亚洲综合91| 精品无码在线观看| 黄色免费毛片| 精品国产va久久久久久久| 黄片无码在线观看| 成人性爱在线观看| 青青三级片| 国产老女人操逼视频| 国产成人自拍视频在线| 黄网站在线免费| 强开小嫩苞一区二区电影| 一级A片亲子乱| 日本免费高清视频在线观看一区| 亚洲无码免费在线视频| 99reav| 亚洲国产成人综合| 无码高清在线观看| 蜜桃传媒在线| 日本在线一区| 亚洲精品97久久中文字幕| 在线人妻| 99在线观看视频在线高清| 午夜福利站| 亚洲在线视频免费观看| 亚洲不卡视频| 日韩av无码中文字幕| 日日摸夜夜| 人人妻人人操人人| 成人小说视频在线社区| 国产免费啪啪视频| 特级西西444www精品视频| 成人视频免费网站| 一级黄色在线观看| 色婷婷一二三精品A片| 三级网站免费观看| 亚洲日韩成人| 久色入口| 日韩爆乳一区二区三区| 久久久久久久免费视频| 久久久久久97电影院电影院无码| 在线无码视频观看| 日本视频一区二区| 婷婷五月天社区| A片欧美| 偷拍亚洲色图| 免费观看黄色片| 招土一级黄色片| 婷婷成人在线| 伊人大综合| 亚洲视频一区二区三区| 一卡二卡久久| 四虎黄色影院| 一级A片免费观看| 夏目あきら被续侵犯7天| 五月丁香婷婷啪啪| 亚洲综合视频在线观看| 亚洲s在线| 97人妻精品一区二区三区免| AV在线资源网| 久草视频在线播放| 亚洲天堂无码av| 亚洲无码成人网| 先锋影音资源站| 三级片AV在线| 东京热小视频| 超碰91在线| 精品中文字幕视频| 亚洲天码中字| 久草在线| 精品免费一区二区三区四区| 免费成人大片| 天天影视综合网免费观看电视剧国产 | 日本操逼网| 人人摸人人看人人| 欧美成人免费电影| 色婷婷在线播放| 免费的黄色视频在线观看| 视频二区中文字幕| 德美日三级片在线观看| 中文字幕无码观看| 色婷婷AV国产精品| 天天搞天天色| 天干夜天干天天天爽视频| 久久er热| 99插插插| 欧美老熟女18| 国产高清AV| 欧美少妇做爱| 色色五月天婷婷| 欧美久久一区二区三区四区视频| 亚洲色五月天| 色婷婷色| 大香蕉久在线| 青青草在线播放| 男女啪网| 久久婷婷国产麻豆91天堂| 日本成人中文字幕| 国产又粗又猛又黄又爽无遮挡 | 日韩a√| 影音先锋男人资源站| 黄色成人视频网站在线观看| 精品人妻无码一区二区三区四川人| 天天爱夜夜操| 四川性BBB搡BBB爽爽爽小说| 日韩美女操逼| 中文字幕不卡AV在线观看| 免费国产在线视频| 91精品一区二区| 在线看污| 国产一级AV国产免费| 久热最新| 日韩视频区| 人妻少妇精品| 狼友视频免费在线观看| 骚逼av| 久久久久久免费一级A片| 日本毛片视频| 无码在线视频免费观看| 精品国产重口乱子伦| 亚洲无码免费看| 超碰九一| 夜夜狠狠擅视频| 亚洲青娱乐在线| 色综合综合色| 色福利网| 九九香蕉视频| 日韩欧美不卡色不卡| 91看片看婬黄大片女跟女| 日韩一级一级| 黑人毛片91久久久久久| 成人在线国产| 伊人久久大香蕉视频| 欧美三级黄色| 搡BBBB搡BBB搡五十粉嫩| 成人免费视频国产在线观看 | 中日韩一级片| 亚洲中文字幕成人| 超碰少妇| 久久精品99国产国产精| 看看AV| 欧美色图狠狠操| 午夜天堂网| 午夜啪啪网站| 久久久久久久成人| 插插插插网| 国产黄色视频网站在线观看| 91视频免费观看| 爱爱爱爱视频| 国产熟女乱伦| 自拍视频一区| 亚洲操逼网| 人妻无码久久精品| 91久久婷婷国产麻豆精品电影.co| 一区二区三区不卡在线| 91人妻成人精品一区二区| 四虎在线免费视频| 日韩国产AV| 日韩专区中文字幕| 99国产在线视频| 成人免费视频在线| 三级片视频网站| 黄色动漫在线免费观看| 九九九国产| 天天干天天射天天爽| 一区二区三区成人| 操操片| 操B网站| 男女操逼视频网站| 胖老板办公室沙发无套爆秘书| 特级欧美AAAAAA| 狼友视频在在观看| 久草福利网| 999成人电影| 午夜久久久久久久久久久久91| 国产精品AV网站| 少妇熟女一区| 亚洲精品系列| 99自拍| 中国12一13毛片| 天天色天天干天天日| 成人自拍电影| av高清无码| 老女人网站| 国产乱伦不卡| 天天操夜夜操人人操| 丰满人妻一区二区三区Av猛交| 久久精品一区二区三区蜜芽的特点| 无套内射免费视频| 嫩BBB搡BBB槡BBB小号| 天天夜夜久久| 超碰AV在线| 久草视频福利在线| 杨晨晨不雅视频| 久久久精品免费视频| av大片在线观看| a视频免费在线观看| 成年无码| 成人网站视频在线观看| 永久m3u8在线观看| 人妻在线你懂的| 开心五月激情婷婷| 国产成人视频在线播放| 成人黄片视频| 草草影院第一页YYCCC| 日日干日日操| 一本一本久久a久久精品牛牛影视| 色色色色色欧美网| 99久久99久久| 日产精品久久久| 米奇色色| 91香蕉在线视频| 肥臀AV在线| 国产在线高潮| 国产无遮挡又黄又爽又色视频软件 | 夜夜操免费视频| 国产成人精品免费看视频| 精品无码一区二区| 久久99久久99久久99| 国产精品无码成人AV电影| 久久久国产91桃色一区二区三区| 天天天日天天天天天天天日歌词 | 黄色成人视频免费看| 免费精品黄色网页| 久久亚洲av| 亚洲不卡一区二区三区| 91中文字幕在线观看| 四川BBBBBB搡BBBBB| 中文日韩| 九九色影院| 午夜视频在线看| 一级做a爰片毛片A片| AV免费在线播放| 国产福利电影在线| 久久久久久91香蕉国产| 欧美成人精品在线观看| 免看一级a一片| 久久精品熟妇丰满人妻99| 91丨九色丨蝌蚪丨丝袜| 色色视频免费看| 人人爽人人澡| 亚洲激情自拍| 香蕉av在线播放| 大香伊人中文字幕精品| 亚洲激色| 黄色电影免费网站| 亚洲xxxxx| 91一起草高清资源| 强伦轩人妻一区二区三区最新版本更新内容 | 亚洲欧洲日韩| 精品九九九九九| 天天澡天天爽日日AV| 久久久久久穴| 免费黄视频在线观看| 成人网站在线| 操操操影院| 狠狠ri| 高清无码学生妹| 狠狠干狠狠色| 亚洲三级无码在线| 特级西西444www大胆高清图片 | 亚洲美眉综合网| 爆乳尤物一区二区三区| 日韩在线一区二区三区四区| 精品日韩在线视频| 国产高清在线视频| 一级a毛片| 麻豆乱伦| av手机版| 欧美麻豆| 91视频成人版一区二区| 婷婷六月激情| 91香蕉在线视频| 国产成人毛片18女人18精品 | 美女黄色片| 国产乱叫456在线| 加勒比DVD手机在线播放观看视频| www.777av| 久久99精品国产.久久久久久| 91大长腿美女花外围在线观看| 日本免费视频| 一区二区在线免费观看| 日韩成人AV在线播放| 91中文字幕在线| 久久久久久久麻豆| 精品国精品自拍自在线| 欧美一级片内射| 老熟女-ThePorn| 婷婷看片| 综合视频一区| 黄片视频在线观看| 肉乳无码A片av| 欧美日韩无码视频| 天堂在线| 极品美鮑20p| 中文字幕东京热| 色天使青青草| 91探花国产综合在线精品| 欧美人妻无码| 国产AV日韩AⅤ亚洲AV中文 | 92午夜福利天堂视频2019| 2014天堂网| 久久久久无码精品国产91福利 | 三级片在线看片AV| 日本特黄AA片免费视频| 亚洲国产另类无码| 丁香花在线小说免费阅读| 免费观看无码视频| 欧美中文日韩| 婷婷五月天大香蕉| 超碰观看| 神马Aⅴ| 99热自拍| 亚洲美女在线观看| 中字AV| 伊人久久大香色综合久久| 婷婷射| 亚洲成av人无码| 日本无码网站| 欧美性受XXXX黑人XYX性爽一 | 国产精品美女久久久久AV爽| 爽爽午国产浪潮AV性色www| 91白浆| 老司机AV| 日皮视频在线免费观看| 99热激情在线| 91吊逼| 性色网站| 亚洲在线视频播放| 欧美A片在线| 天天色色天天| 精品成人Av一区二区三区| 91中文字幕| 中文字幕AV在线观看| 国产精品无码专区AV免费播放| 欧美做受高潮白| 亚洲韩国国产| 日韩精品在线视频| 欧美日韩高清在线| 欧洲成人在线观看| 99久re热视频精品98| 亚洲视频A| 欧美在线国产| 成人三区| 波多野结衣亚洲无码| 日韩天天| 青青草原成人在线视频| 18禁无码网站| 国产欧美毛片| 日韩不卡在线| 欧美成人无码A片免费| 国产精品婷婷午夜在线观看| 亚洲午夜久久久久久久久| 欧美亚洲日韩一区| 黄色A网站| 豆花视频在线观看| 亚洲第一国产黄AV动漫软件| 色情电影网站| 色综合加勒比| 精品伊人久久| A片操逼| 97操逼网| 97视频| 国产美女操逼| 日韩国产中文字幕| 中文无码播放| 伊人免费在线| 日日操人人操| 91丨国产丨白丝| 欧美熟女性爱视频| 国产激情无码| 怡红院av| 香蕉操逼视频| 久久黄视频| 人妻制服丝袜| 亚洲精品秘一区二区三区影| 国产精品免费久久| 国产精品特级毛片| 性爱免费专区| www.狠狠操| 91蜜桃在线| 翔田AV无码秘三区| 亚洲中文字幕在线看| 五月天婷婷激情视频| 深夜福利一区二区| 国产一区在线观看视频| 日韩五月婷婷| 熟女探花精选| 91在线亚洲| 青青网站| 超碰人人草| 99精品视频免费| 九九久热| 中文字幕第9页| 九九亚洲精品| 国产白浆一区二区三区| 亚洲无码一区二区三区妃光| 中文无码Av| 青娱乐黄片| 亚洲五月婷婷| 一区视频| 国产女人18毛片18精品| 国产一二三四区| 日韩中文在线视频| 男人在线天堂| 日韩人妻av| 91AV电影网| 午夜日逼网站| 国产精品久久久久久久牛牛| 日本黄色片在线播放| 大香蕉啪啪啪啪| 在线亚洲免费观看| 狼人香蕉在线视频| 熟女探花精选| 激情婷婷五月天| AV免费网站| 色五月激情五月| 亚洲无码影音先锋| 在线观看国产黄色| 中文字幕亚洲综合| 做爱无码| 国产AV在| 欧美丰满人妻| 淫色淫香综合网| 日韩大屌| 99久久婷婷国产综合精品漫 | 真实白嫖91探花无码| 欧美日皮| 亚洲精品麻豆| 欧美AAAAAAAAAA特级| 中文字幕第27页| 伊人三级片| 午夜成人精品一区二区三区 | 青青草免费在线| 狠狠操狠狠撸| 日本不卡视频在线| 亚洲AV成人无码精品直播在线| 亚洲Av无码成人专区擼| 巨爆乳肉感一区二区三区| 欧美口爆视频| 国产精品啪啪视频| 永久免费无码中文字幕| 熟女综合网| 男人的天堂手机在线| 日韩性爱在线观看| 大香蕉综合久久| 亚欧洲精品在线视频| 中文字幕人妻在线中文乱码怎么解决| 日本精品在线播放| 亚洲无码高清在线视频| 最近日本中文字幕中文翻译歌词| 日韩欧美国产综合| 国产精品日韩高清北条麻衣| 精品码A片18| www.199麻豆在线观看网站 | 日本五十路熟女视频| 色欲一区二区| 不卡视频一区二区| 在线你懂得| 日韩欧美二区| 青青草视频| 中文在线a√在线8| 黄网站免费在线观看| 亚洲中文字幕免费| 亚洲在线视频播放| 亚洲无码成人网站| 久久精品国产视频| 97久久精品国产熟妇高清网 | 国产乱国产乱300精品| 成人日韩欧美| 久久久久大香蕉| 国产电影一区二区三区| 99人妻视频| 国产激情在线视频| 安徽妇搡BBBB搡BBBB小说| 91探花足浴店按摩店| 日韩三级AV在线观看| 久久嫩草精品久久久久精| 午夜无码鲁丝片午夜精品一区二区| 日逼一级| 久久爱91| 人妖和人妖互交性XXXX视频| 天天草天天爽| 中文字幕无码播放| 夜色88V精品国产亚洲| 亚洲三级毛片| 中文在线免费看视频| 亚洲无套内射| 精品人妻午夜一区二区三区四区 | 9l农村站街老熟女| 在线观看免费无码| 口爆吞精在线观看| 久久国产精品网站| 国产A片免费看| 亚洲性爱在线| 亚洲高清国产欧美综合s8| 中文字幕无码毛片| 激情婷婷色五月| 围内精品久久久久久久久白丝制服 | 黄色电影av| 久久精品偷拍视频| 不卡的一区二区| 97黄片| 91丨九色丨蝌蚪丨成人| 天堂网av在线| 牛牛在线视频| 国产熟女乱伦| 天天爽爽爽爽爽成人片| 一道本无码免费视频| 日韩欧美中文字幕视频| 黄色免费视频| 久久成人网豆花视频| 四虎成人精品在永久免费| 一道本一区二区三区| 日韩精品三级片| 日韩在线视频网站| 欧美精产国品一二三区| 亚色网址| 亚洲影音先锋资源| 欧一美一婬一伦一区二区三区| 91精品丝袜久久久久久| 国产精品丝袜| 大香蕉啪啪视频| 免费看18禁| eeuss一区| 国产AA| 国产黄色不卡| 丁香花在线高清完整版视频| 黄色小说在线播放| 天天天天日天天干| 日韩在线视频一区二区三区 | 青青草国产| 老司机永久免费91| 激情婷婷在线| 日韩欧美天堂| 久久久久久黄色| 中文字幕在线网| 人妻无码在线视频| 欧美激情综合| 操女人逼AV| 天天操天天射天天爽| 青娱乐国产av| 黄色三级电影| 丁香五月欧美激情| 白浆av| 开心激情站| 中文字幕无码毛片| 久久五月天综合| 天天爽天天爽夜夜爽毛片| 国产中文在线观看| 91成人免费| 大香蕉欧美| 免费日本A片| 天天干天天肏| 亚洲高清成人| 女人操逼| 成人在线观看网站| 国产欧美一区二区三区视频| 丁香五月婷婷视频| 黄色中文字幕| 激情五月婷婷丁香| 97A片在线观看播放| 黄网站免费观看| 熟女一区二区三区| 少妇一级婬片内射视频| 欧美婬乱片A片AAA毛片地址| 亚洲精品无码中文字幕| 免费av大全| 中文字幕观看av| 夜夜撸夜夜操| 黄色电影大香蕉| 97黄片| 蜜臀久久99精品久久久电影| 久久香蕉综合在线| 中文字幕成人网站中文字幕| 免费在线观看黄片视频| 天堂在线www| 97久久精品国产熟妇高清网 | 日本无码一区二区三三| 精品孕妇一级A片免费看| 色片免费| 亚洲天堂日本| 亚洲日韩在线观看视频| 大香蕉啪啪啪| 嫩草A片www在线观看| 成人伊人综合网| 欧美精品日韩在线观看| 一级免费视频| 久久久久久久毛片| 日韩中文字幕国产| 家庭乱伦AV| 午夜精品18| 88AV视频| 日少妇视频| 北京熟妇搡BBBB搡BBBB| 天天插夜夜操| 中文字幕++中文字幕明步| 亚洲成人视频在线播放| 天干天干天夜夜| 成人无码人妻| 色网在线观看| 自拍偷拍福利视频网站| 在线播放www| 性满足BBWBBWBBW| 亚洲无码在线免费观看| 日韩无码人妻视频| 欧美日韩中文字幕视频| 国产一卡二卡在线观看| 99综合| 午夜免费AV| 吹潮喷水高潮HD| 蜜桃传媒一区二区亚洲| 国产一级婬女AAAA片季秀英| 开心五月色婷婷综合开心网| 毛片导航| 日韩av一区二区三区| 99久久99久国产黄毛片| 国产婬片lA片www777| 伊人性爱网| 黄色小电影网站| 激情久久五月天| 亚洲无码av中文字幕| 免费av在线播放| 国产三级黄色视频| 东北骚妇大战黑人视频| 日韩精品在线免费| 国产成人精品一区二区三区在线 | 人人爱人人操人人干| 91久久亚洲| 91久色| 男女啪网站| 五月婷婷色色色| 91爱爱视频| 国产成人a亚洲精品www| 九九九视频在线观看| 成人免费看片| 亚洲第一黄色| 91人人妻人人妻人人澡| 密臀av在线| 日韩无码电影网| 夜夜嗨AV一区二区三区啊| 国产久久在线观看| 一本色道久久综合亚洲精品久久| 欧美性猛交一区二区三区精品 | 探花在线| 九色PORN视频成人蝌蚪自拍| 国产77777| 国产—级a毛—a毛免费视频| 日韩黄色片网站| 足浴小少妇-88AX| 国产ts视频| 色综合加勒比| 性爱视频免费网站| 三级日韩| 三级成人在线| 中文字幕免费看| 汇聚全球淫荡熟女| japanese在线观看| 色婷婷色99国产综合精品| 伊人成人视频在线观看| 国产成人777777精品综合| 91九色91蝌蚪91窝成人| 91麻豆大奶巨乳一区白虎| 亚洲黄色在线观看视频| 日本a片在线观看| 99热精品免费在线观看| 人人看人人艹| 国产剧情在线| 国产欧美日韩综合| 欧美熟妇搡BBBB搡BBBBB| 一本色道久久综合熟妇| 人人爽亚洲AV人人爽AV人人片| 日韩美女免费视频| 老欧性老太色HD大全| 91九色精品女同系列| 色欲网| 亚洲av免费在线观看| 不卡无码中文字幕|