成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

通過源碼解析 Node.js 中進(jìn)程間通信中的 socket 句柄傳遞

HackerShell / 2947人閱讀

摘要:子進(jìn)程使用反序列化消息字符串為消息對(duì)象。在調(diào)用這類方法時(shí),遍歷列表中的實(shí)例發(fā)送內(nèi)部消息,子進(jìn)程列表中的對(duì)應(yīng)項(xiàng)收到內(nèi)部消息并處理返回,父進(jìn)程中再結(jié)合返回結(jié)果和對(duì)應(yīng)著這個(gè)類實(shí)例維護(hù)的信息,保證功能的正確性。

在 Node.js 中,當(dāng)我們使用 child_process 模塊創(chuàng)建子進(jìn)程后,會(huì)返回一個(gè) ChildProcess 類的實(shí)例,通過調(diào)用 ChildProcess#send(message[, sendHandle[, options]][, callback]) 方法,我們可以實(shí)現(xiàn)與子進(jìn)程的通信,其中的 sendHandle 參數(shù)支持傳遞 net.Servernet.Socket 等多種句柄,使用它,我們可以很輕松的實(shí)現(xiàn)在進(jìn)程間轉(zhuǎn)發(fā) TCP socket:

// parent.js
"use stirct"
const { createServer } = require("net")
const { fork } = require("child_process")

const server = createServer()
const child = fork("./child.js")

server.on("connection", function (socket) {
  child.send("socket", socket)
})
.listen(1337)
// child.js
"use strict"

process.on("message", function (message, socket) {
  if (message === "socket") socket.end("Child handled it.")
})
$ curl 127.0.0.1:1337
Child handled it.

這時(shí)你可能就會(huì)疑問,此時(shí) socket 已經(jīng)處在了另一個(gè)進(jìn)程中,那么像 net.Server#getConnectionsnet.Server#close 等等這些方法,該怎么實(shí)現(xiàn)其功能呢?傳遞的句柄都是 JavaScript 對(duì)象,它們?cè)趥鬟f時(shí),序列化和反序列化的機(jī)制,又是怎么樣的呢?

讓我們跟著 Node.js 項(xiàng)目中的 lib/child_process.js,lib/internal/child_process.jslib/internal/process.js 等文件中的代碼,來一探究竟。

序列化與反序列化

當(dāng)使用 child_process 模塊中的 fork 函數(shù)創(chuàng)建 ChildProcess 類的實(shí)例時(shí),會(huì)在建立 IPC channel 時(shí),初始化 ChildProcess#send 方法:

// lib/internal/child_process.js
// ...

function setupChannel(target, channel) { 
  // 此處的 target,即為正在創(chuàng)建的 ChildProcess 類實(shí)例
  target._channel = channel;
  target._handleQueue = null;
  // ...
  
  target.send = function(message, handle, options, callback) {
    // ...
    if (this.connected) {
      return this._send(message, handle, options, callback);
    }
    // ...
  };
  
  target._send = function(message, handle, options, callback) {
    assert(this.connected || this._channel);
    // ...
    if (handle) {
      message = {
        cmd: "NODE_HANDLE",
        type: null,
        msg: message
      };

      if (handle instanceof net.Socket) {
        message.type = "net.Socket";
      } else if (handle instanceof net.Server) {
        message.type = "net.Server";
      } else if (handle instanceof TCP || handle instanceof Pipe) {
        message.type = "net.Native";
      } else if (handle instanceof dgram.Socket) {
        message.type = "dgram.Socket";
      } else if (handle instanceof UDP) {
        message.type = "dgram.Native";
      } else {
        throw new TypeError("This handle type can"t be sent");
      }

      var obj = handleConversion[message.type];
      handle = handleConversion[message.type].send.call(target,
                                                        message,
                                                        handle,
                                                        options);
    // ...
    var req = new WriteWrap();
    req.async = false;

    var string = JSON.stringify(message) + "
";
    var err = channel.writeUtf8String(req, string, handle);
    // ...
  };
}

從代碼我們可以看到,當(dāng)我們帶著句柄調(diào)用 ChildProcess#send 方法發(fā)送消息時(shí),Node.js 會(huì)替我們先將該消息封裝成它的內(nèi)部消息(將消息包在對(duì)象中,且對(duì)象擁有一個(gè) cmd 屬性)。句柄的序列化,使用到的是 handleConversion[message.type].send 方法,在傳遞的是 socket 時(shí),即為 handleConversion["net.Socket"].send。

所以關(guān)鍵一定就是在 handleConversion 這個(gè)對(duì)象上了,我們先不著急看它的如山真面如。讓我們先來看看子進(jìn)程反序列化時(shí)的關(guān)鍵步驟代碼。

在子進(jìn)程啟動(dòng)時(shí),若發(fā)現(xiàn)自己是通過 child_process 模塊創(chuàng)建的進(jìn)程(環(huán)境變量中帶有 NODE_CHANNEL_FD),則最后也會(huì)執(zhí)行上述的 lib/internal/child_process.js 文件中的 setupChannel 初始化函數(shù):

// lib/internal/process.js
// ...
function setupChannel() {
  if (process.env.NODE_CHANNEL_FD) {
    var fd = parseInt(process.env.NODE_CHANNEL_FD, 10);
    delete process.env.NODE_CHANNEL_FD;

    var cp = require("child_process");
    // ...
    cp._forkChild(fd);
    assert(process.send);
  }
}

// lib/child_process.js
// ...
const child_process = require("internal/child_process");
const setupChannel = child_process.setupChannel;

exports._forkChild = function(fd) {
  // ...
  const control = setupChannel(process, p);
};

以下函數(shù)與上上個(gè)例子的中函數(shù)為同一個(gè),只不過于子進(jìn)程中執(zhí)行:

// lib/internal/child_process.js
// ...
function setupChannel(target, channel) {
  target._channel = channel;
  target._handleQueue = null;
  // ...
  target.on("internalMessage", function(message, handle) {
    // ...
    if (message.cmd !== "NODE_HANDLE") return;
    var obj = handleConversion[message.type];
    
    obj.got.call(this, message, handle, function(handle) {
      handleMessage(target, message.msg, handle);
    });
  });
}

function handleMessage(target, message, handle) {
  if (!target._channel)
    return;

  var eventName = "message";
  if (message !== null &&
      typeof message === "object" &&
      typeof message.cmd === "string" &&
      message.cmd.length > INTERNAL_PREFIX.length &&
      message.cmd.slice(0, INTERNAL_PREFIX.length) === INTERNAL_PREFIX) {
    eventName = "internalMessage";
  }
  target.emit(eventName, message, handle);
}

顯而易見,使用了 handleConversion[message.type].got 來進(jìn)行句柄的反序列化,使之構(gòu)建成 JavaScript 對(duì)象。所以我們不難想到,句柄序列化 & 反序列化運(yùn)用的就是,各個(gè) handleConversion[message.type] 對(duì)象中提供的同一方法 sendgot 。打個(gè)比方就像 Java 中的這些 class 都實(shí)現(xiàn)了同一個(gè) interface

// lib/internal/child_process.js
// ...

const handleConversion = {
  // ...
  "net.Server": {
    // ...
    send: function(message, server, options) {
      return server._handle;
    },
    got: function(message, handle, emit) {
      var server = new net.Server();
      server.listen(handle, function() {
        emit(server);
      });
    }
  },

  "net.Socket": {
    send: function(message, socket, options) {
      // ...
    },

    got: function(message, handle, emit) {
      // ...
    }
  },
  "dgram.Socket": {
    send: function(message, socket, options) {
      // ...
    },
    got: function(message, handle, emit) {
      // ...
    }
  }
  // ...
};

所以傳遞的過程:

主進(jìn)程:

傳遞消息和句柄。

將消息包裝成內(nèi)部消息,使用 JSON.stringify 序列化為字符串。

通過對(duì)應(yīng)的 handleConversion[message.type].send 方法序列化句柄。

將序列化后的字符串和句柄發(fā)入 IPC channel 。

子進(jìn)程

使用 JSON.parse 反序列化消息字符串為消息對(duì)象。

觸發(fā)內(nèi)部消息事件(internalMessage)監(jiān)聽器。

將傳遞來的句柄使用 handleConversion[message.type].got 方法反序列化為 JavaScript 對(duì)象。

帶著消息對(duì)象中的具體消息內(nèi)容和反序列化后的句柄對(duì)象,觸發(fā)用戶級(jí)別事件。

net.Server#getConnections 等方法的功能實(shí)現(xiàn)

由于將 socket 傳遞給了子進(jìn)程之后,net.Server#getConnectionsnet.Server#close 等等方法,原來的實(shí)現(xiàn)已經(jīng)無效了,為了保證功能,Node.js 又是怎么辦的呢?答案可以大致概括為,父子進(jìn)程之間,在同一地址下的 socket 傳遞時(shí),各自都額外維護(hù)一個(gè)關(guān)聯(lián)列表存儲(chǔ)這些 socket 信息和 ChildProcess 實(shí)例,并且父進(jìn)程中的 net#Server 類實(shí)例自己保存下所有父進(jìn)程關(guān)聯(lián)列表。在調(diào)用 net.Server#getConnections 這類方法時(shí),遍歷列表中的 ChildPorcess 實(shí)例發(fā)送內(nèi)部消息,子進(jìn)程列表中的對(duì)應(yīng)項(xiàng)收到內(nèi)部消息并處理返回,父進(jìn)程中再結(jié)合返回結(jié)果和對(duì)應(yīng)著這個(gè) ChildProcess 類實(shí)例維護(hù)的 socket 信息,保證功能的正確性。

lib/internal/socket_list.js 這個(gè)文件中,分別定義了這兩個(gè)列表類,分別名為 SocketListSendSocketListReceive

// lib/internal/socket_list.js
// ...
function SocketListSend(slave, key) {
  EventEmitter.call(this);

  this.key = key;
  this.slave = slave;
}
util.inherits(SocketListSend, EventEmitter);

// ...
function SocketListReceive(slave, key) {
  EventEmitter.call(this);

  this.connections = 0;
  this.key = key;
  this.slave = slave;
  // ...
}
util.inherits(SocketListReceive, EventEmitter);

然后在 net.Socket 句柄的序列化和反序列化過程中,將句柄和進(jìn)程推入列表:

// lib/internal/child_process.js
// ...

const handleConversion = {
  // ...
  send: function(message, socket, options) {
    // ...
    if (socket.server) {
      // ...
      var firstTime = !this._channel.sockets.send[message.key];
      var socketList = getSocketList("send", this, message.key);

      if (firstTime) socket.server._setupSlave(socketList);
    }
    // ...
    return handle;
  },
  
  got: function(message, handle, emit) {
    var socket = new net.Socket({handle: handle});
    socket.readable = socket.writable = true;
    if (message.key) {
      var socketList = getSocketList("got", this, message.key);
      socketList.add({
        socket: socket
      });
    }

    emit(socket);
  }
}

function getSocketList(type, slave, key) {
  // slave 對(duì)象即為當(dāng)前正在創(chuàng)建的 ChildProcess 類實(shí)例
  var sockets = slave._channel.sockets[type];
  var socketList = sockets[key];
  if (!socketList) {
    var Construct = type === "send" ? SocketListSend : SocketListReceive;
    socketList = sockets[key] = new Construct(slave, key);
  }
  return socketList;
}

// lib/net.js
// ...
Server.prototype._setupSlave = function(socketList) {
  this._usingSlaves = true;
  this._slaves.push(socketList);
};

然后在調(diào)用具體方法時(shí),遍歷列表,結(jié)合通信來的結(jié)果,再返回:

// lib/net.js
// ...

Server.prototype.getConnections = function(cb) {
  // ...
  if (!this._usingSlaves) {
    return end(null, this._connections);
  }
  var left = this._slaves.length;
  var total = this._connections;

  function oncount(err, count) {
    if (err) {
      left = -1;
      return end(err);
    }

    total += count;
    if (--left === 0) return end(null, total);
  }

  this._slaves.forEach(function(slave) {
    slave.getConnections(oncount);
  }); 
}

即遍歷了 _salves 列表調(diào)用各項(xiàng)其上的 getConnections 方法(封裝了 IPC 通信和內(nèi)部事件邏輯)。

當(dāng)我們解析好了 net.Server#getConnections 方法后,其他類似需求方法的解決方案,其實(shí)也大同小異,思路是一致的。涉及的東西有點(diǎn)多,上一個(gè)簡單的圖示(順序?yàn)楹?,藍(lán),紅):

最后

參考:

https://github.com/nodejs/node/blob/master/lib/child_process.js

https://github.com/nodejs/node/blob/master/lib/net.js

https://github.com/nodejs/node/blob/master/lib/internal/process.js

https://github.com/nodejs/node/blob/master/lib/internal/child_process.js

https://github.com/nodejs/node/blob/master/lib/internal/socket_list.js

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/79380.html

相關(guān)文章

  • 通過源碼解析 Node.js cluster 模塊的主要功能實(shí)現(xiàn)

    摘要:通常的解決方案,便是使用中自帶的模塊,以模式啟動(dòng)多個(gè)應(yīng)用實(shí)例。最后中的模塊除了上述提到的功能外,其實(shí)還提供了非常豐富的供和進(jìn)程之前通信,對(duì)于不同的操作系統(tǒng)平臺(tái),也提供了不同的默認(rèn)行為。如果大家有閑,非常推薦完整領(lǐng)略一下模塊的代碼實(shí)現(xiàn)。 眾所周知,Node.js中的JavaScript代碼執(zhí)行在單線程中,非常脆弱,一旦出現(xiàn)了未捕獲的異常,那么整個(gè)應(yīng)用就會(huì)崩潰。這在許多場景下,尤其是web...

    leeon 評(píng)論0 收藏0
  • 深入淺出nodeJS - 4 - (玩轉(zhuǎn)進(jìn)程、測試、產(chǎn)品化)

    摘要:進(jìn)程間通信的目的是為了讓不同的進(jìn)程能夠互相訪問資源,并進(jìn)程協(xié)調(diào)工作。這個(gè)過程的示意圖如下端口共同監(jiān)聽集群穩(wěn)定之路進(jìn)程事件自動(dòng)重啟負(fù)載均衡狀態(tài)共享模塊工作原理事件二測試單元測試性能測試三產(chǎn)品化項(xiàng)目工程化部署流程性能日志監(jiān)控報(bào)警穩(wěn)定性異構(gòu)共存 內(nèi)容 9.玩轉(zhuǎn)進(jìn)程10.測試11.產(chǎn)品化 一、玩轉(zhuǎn)進(jìn)程 node的單線程只不過是js層面的單線程,是基于V8引擎的單線程,因?yàn)?,V8的緣故,前后...

    henry14 評(píng)論0 收藏0
  • Node.js - 阿里Egg的多進(jìn)程模型和進(jìn)程通訊

    摘要:第二種是主進(jìn)程創(chuàng)建監(jiān)聽后發(fā)送給感興趣的工作進(jìn)程,由工作進(jìn)程負(fù)責(zé)直接接收連接。繼續(xù)看,可以看到它捕獲了事件,并在回調(diào)函數(shù)里面關(guān)閉連接,關(guān)閉本身進(jìn)程,斷開與的通道。參考與引用多進(jìn)程模型和進(jìn)程間通訊源碼解析之 前言 最近用Egg作為底層框架開發(fā)項(xiàng)目,好奇其多進(jìn)程模型的管理實(shí)現(xiàn),于是學(xué)習(xí)了解了一些東西,順便記錄下來。文章如有錯(cuò)誤, 請(qǐng)輕噴 為什么需要多進(jìn)程 伴隨科技的發(fā)展, 現(xiàn)在的服務(wù)器基本上...

    Cheng_Gang 評(píng)論0 收藏0
  • nodejs cluster模塊分析

    摘要:而在進(jìn)程執(zhí)行把進(jìn)程添加到調(diào)度器中時(shí)添加了一個(gè)回調(diào)函數(shù),回調(diào)函數(shù)了一個(gè)帶的消息,并且為,就是這個(gè)消息觸發(fā)了發(fā)送的函數(shù)的執(zhí)行。 最近做了點(diǎn)nodejs項(xiàng)目,對(duì)nodejs的cluster怎么利用多進(jìn)程處理請(qǐng)求產(chǎn)生了疑問,于是著手進(jìn)行了研究,之后發(fā)現(xiàn)這其中竟大有文章!一切還是先從遙遠(yuǎn)的TCP說起吧。。。 TCP與Socket 說到TCP,相信很多人都相當(dāng)了解了,大學(xué)已經(jīng)教過,但是又相信有很多...

    KnewOne 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

HackerShell

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<