Home  >  Article  >  Web Front-end  >  How to implement port reuse function in Node.Js

How to implement port reuse function in Node.Js

php中世界最好的语言
php中世界最好的语言Original
2018-06-01 11:32:101405browse

This time I will bring you how to implement the port reuse function in Node.Js, what are the precautions for implementing the port reuse function in Node.Js, the following is a practical case, Let’s take a look.

Origin, from the official example, multi-process shared port

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`);
}

Execution result:

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

Understand the http.js module:

We all only need to create an http Service mustreferencehttp module. The http module will eventually call net.js to implement network services

// 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);
}
 ...

The answer can be found soon through the cluster._getServer function

  1. Proxy server._listen2 This method performs operations in the work process

  2. Sends a queryServer message to the master and registers an internal TCP server with the master

// 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);
 }
*/

The master process starts the service after receiving the queryServer message

  1. If the address has not been monitored, start the service through RoundRobinHandle monitoring

  2. If the address has been monitored, directly bind the handle to the service that has been monitored to consume the request

// 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);
  });
}

Seeing this step, it is already obvious that we know how to carry out more ports Sharing implementation principle

  1. In fact, the port is only monitored once by the internal TCP server in the master process

  2. Because the net.js module will determine Is the current process a master or a Worker process?

  3. If it is a Worker process, call cluster._getServer to hack the native listen method.

  4. So call it in the child The listen method is an empty method that returns 0, so it will not report a port occupation error

Now the question comes, since how does the Worker process get the master process listening service received What about the connect?

  1. Listen to the connection of the TCP server started by the master processEvent

  2. Select a worker through polling

  3. Send newconn internal message to it, the message body contains the client handle

  4. ##With the handle, everyone knows how to deal with it haha

  5. // 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);
      });
    };
Let us see what operations the Worker process performed after receiving the newconn message

// 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);
}

Summary

  1. The net module will judge the process, whether it is a worker or a master. If it is a worker, hack it. The listen method of the net.Server instance

  2. listen called by the worker The method is hacked and returns 0 directly, but a connection event will be registered with the master.

  3. After the master receives the client connection event, it will poll and send the connection to the worker. Client handle

  4. The worker receives the client handle sent by the master, and then it can process the client request

I believe I read this article You have mastered the case method. For more exciting information, please pay attention to other related articles on the PHP Chinese website!

Recommended reading:

Vue.js two-way binding project practical analysis

How jquery determines that the element content is empty

The above is the detailed content of How to implement port reuse function in Node.Js. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn