首頁 >web前端 >js教程 >Node.Js實作連接埠重複使用步驟詳解

Node.Js實作連接埠重複使用步驟詳解

php中世界最好的语言
php中世界最好的语言原創
2018-05-14 11:31:091821瀏覽

這次帶給大家Node.Js實作埠重用步驟詳解,Node.Js實作埠重用的注意事項有哪些,下面就是實戰案例,一起來看一下。

起源,從官方實例中看多進程共用連接埠

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
 console.log(`Master ${process.pid} is running`);
 for (let i = 0; i < numCPUs; i++) {
  cluster.fork();
 }
 cluster.on(&#39;exit&#39;, (worker, code, signal) => {
  console.log(`worker ${worker.process.pid} died`);
 });
} else {
 http.createServer((req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
 }).listen(8000);
 console.log(`Worker ${process.pid} started`);
}

執行結果:

$ node server.js
Master 3596 is running
Worker 4324 started
Worker 4520 started
Worker 6056 started
Worker 5644 started#http

#了解http.js模組:

#我們都只有要建立一個#http

#了解http.js模組:

#我們都只有要建立一個一個#http

#了解http.js模組:
  1. #我們都只有要建立一個一個#http

    #了解http.js模組:
  2. #我們都只有要建立一個一個#http

    #了解http.js模組:
#我們都只有要建立一個#http

#了解http.js模組:
  1. #我們都只有要建立一個一個#http

    #了解http.js模組:
  2. #我們都只有要建立一個#http

    #了解http.js模組:
服務,必須

引用

http模組,http模組最終會呼叫net.js實作網路服務
    // lib/net.js
    'use strict';
     ...
    Server.prototype.listen = function(...args) {
      ...
     if (options instanceof TCP) {
       this._handle = options;
       this[async_id_symbol] = this._handle.getAsyncId();
       listenInCluster(this, null, -1, -1, backlogFromArgs); // 注意这个方法调用了cluster模式下的处理办法
       return this;
      }
      ...
    };
    function listenInCluster(server, address, port, addressType,backlog, fd, exclusive) {
    // 如果是master 进程或者没有开启cluster模式直接启动listen
    if (cluster.isMaster || exclusive) {
      //_listen2,细心的人一定会发现为什么是listen2而不直接使用listen
     // _listen2 包裹了listen方法,如果是Worker进程,会调用被hack后的listen方法,从而避免出错端口被占用的错误
      server._listen2(address, port, addressType, backlog, fd);
      return;
     }
     const serverQuery = {
      address: address,
      port: port,
      addressType: addressType,
      fd: fd,
      flags: 0
     };
    // 是fork 出来的进程,获取master上的handel,并且监听,
    // 现在是不是很好奇_getServer方法做了什么
     cluster._getServer(server, serverQuery, listenOnMasterHandle);
    }
     ...
  1. 答案很快就可以透過cluster._getServer 這個函式找到

  2. #代理程式了server._listen2 這個方法在work程序的執行操作

  3. #向master傳送queryServer訊息,向master註冊一個內部TCP伺服器

#
// lib/internal/cluster/child.js
cluster._getServer = function(obj, options, cb) {
 // ...
 const message = util._extend({
  act: 'queryServer',  // 关键点:构建一个queryServer的消息
  index: indexes[indexesKey],
  data: null
 }, options);
 message.address = address;
// 发送queryServer消息给master进程,master 在收到这个消息后,会创建一个开始一个server,并且listen
 send(message, (reply, handle) => {
   rr(reply, indexesKey, cb);       // Round-robin.
 });
 obj.once('listening', () => {
  cluster.worker.state = 'listening';
  const address = obj.address();
  message.act = 'listening';
  message.port = address && address.port || options.port;
  send(message);
 });
};
 //...
 // Round-robin. Master distributes handles across workers.
function rr(message, indexesKey, cb) {
  if (message.errno) return cb(message.errno, null);
  var key = message.key;
  // 这里hack 了listen方法
  // 子进程调用的listen方法,就是这个,直接返回0,所以不会报端口被占用的错误
  function listen(backlog) {
    return 0;
  }
  // ...
  const handle = { close, listen, ref: noop, unref: noop };
  handles[key] = handle;
  // 这个cb 函数是net.js 中的listenOnMasterHandle 方法
  cb(0, handle);
}
// lib/net.js
/*
function listenOnMasterHandle(err, handle) {
  err = checkBindError(err, port, handle);
  server._handle = handle;
  // _listen2 函数中,调用的handle.listen方法,也就是上面被hack的listen
  server._listen2(address, port, addressType, backlog, fd);
 }
*/

master程序收到queryServer訊息後進行啟動服務#如果位址沒被監聽過,透過RoundRobinHandle監聽開啟服務

  1. #如果位址已經被監聽,直接綁定handel到已經監聽到服務上,去消費請求

  2. // lib/internal/cluster/master.js
    function queryServer(worker, message) {
      const args = [
        message.address,
        message.port,
        message.addressType,
        message.fd,
        message.index
      ];
      const key = args.join(':');
      var handle = handles[key];
      // 如果地址没被监听过,通过RoundRobinHandle监听开启服务
      if (handle === undefined) {
        var constructor = RoundRobinHandle;
        if (schedulingPolicy !== SCHED_RR ||
          message.addressType === 'udp4' ||
          message.addressType === 'udp6') {
          constructor = SharedHandle;
        }
        handles[key] = handle = new constructor(key,
          address,
          message.port,
          message.addressType,
          message.fd,
          message.flags);
      }
      // 如果地址已经被监听,直接绑定handel到已经监听到服务上,去消费请求
      // Set custom server data
      handle.add(worker, (errno, reply, handle) => {
        reply = util._extend({
          errno: errno,
          key: key,
          ack: message.seq,
          data: handles[key].data
        }, reply);
        if (errno)
          delete handles[key]; // Gives other workers a chance to retry.
        send(worker, reply, handle);
      });
    }

    看到這一步,已經很明顯,我們知道了多進行端口共享的實作原理

  3. 其實連接埠僅由master進程中的內部TCP伺服器監聽了一次

  4. 因為net.js 模組中會判斷目前的進程是master還是Worker進程

如果是Worker進程調用cluster._getServer 去hack原生的listen 方法

所以在child調用的listen方法,是一個return 0 的空方法,所以不會報到埠佔用錯誤

那現在問題來了,既然Worker進程是如何取得到master進程監聽服務接收到的connect呢?


監聽master進程啟動的TCP伺服器的connection

事件###############透過輪詢挑選一個worker### #########向其發送newconn內部訊息,訊息體中包含了客戶端句柄############有了句柄,誰都知道要怎麼處理了哈哈## #######
// lib/internal/cluster/round_robin_handle.js
function RoundRobinHandle(key, address, port, addressType, fd) {
  this.server = net.createServer(assert.fail);
  if (fd >= 0)
    this.server.listen({ fd });
  else if (port >= 0)
    this.server.listen(port, address);
  else
    this.server.listen(address); // UNIX socket path.
  this.server.once('listening', () => {
    this.handle = this.server._handle;
    // 监听onconnection方法
    this.handle.onconnection = (err, handle) => this.distribute(err, handle);
    this.server._handle = null;
    this.server = null;
  });
}
RoundRobinHandle.prototype.add = function (worker, send) {
  // ...
};
RoundRobinHandle.prototype.remove = function (worker) {
  // ...
};
RoundRobinHandle.prototype.distribute = function (err, handle) {
  // 负载均衡地挑选出一个worker
  this.handles.push(handle);
  const worker = this.free.shift();
  if (worker) this.handoff(worker);
};
RoundRobinHandle.prototype.handoff = function (worker) {
  const handle = this.handles.shift();
  const message = { act: 'newconn', key: this.key };
  // 向work进程其发送newconn内部消息和客户端的句柄handle
  sendHelper(worker.process, message, handle, (reply) => {
  // ...
    this.handoff(worker);
  });
};
###下面讓我們看看Worker進程接收到newconn訊息後進行了哪些操作###
// lib/child.js
function onmessage(message, handle) {
  if (message.act === 'newconn')
   onconnection(message, handle);
  else if (message.act === 'disconnect')
   _disconnect.call(worker, true);
 }
// Round-robin connection.
// 接收连接,并且处理
function onconnection(message, handle) {
 const key = message.key;
 const server = handles[key];
 const accepted = server !== undefined;
 send({ ack: message.seq, accepted });
 if (accepted) server.onconnection(0, handle);
}
#########總結######### #########net模組會對程式進行判斷,是worker 還是master, 是worker的話進行hack net.Server實例的listen方法############worker 呼叫的listen方法是hack掉的,直接return 0,不過會向master註冊一個connection接手的事件############master 收到客戶端connection事件後,會輪詢向worker發送connection上來的客戶端句柄############worker收到master發送過來客戶端的句柄,這時候就可以處理客戶端請求了###########我相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章! ######推薦閱讀:#########vue mint-ui tabbar元件使用步驟詳解###############Vue 修飾符觸發事件### ######

以上是Node.Js實作連接埠重複使用步驟詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn