>  기사  >  웹 프론트엔드  >  Node.Js에서 포트 재사용 기능을 구현하는 방법

Node.Js에서 포트 재사용 기능을 구현하는 방법

php中世界最好的语言
php中世界最好的语言원래의
2018-06-01 11:32:101405검색

이번에는 Node.Js에서 포트 재사용 기능을 구현하는 방법과 Node.Js에서 포트 재사용 기능을 구현하기 위한 주의사항에 대해 알아보겠습니다.

Origin, 공식 인스턴스에서 다중 프로세스 공유 포트를 확인하세요

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이 실행 중입니다
Worker 4324가 시작되었습니다
Worker 4520이 시작되었습니다
Worker 6056이 시작되었습니다
Worker 5644가 시작되었습니다

http.js 모듈 이해하기:

우리 모두는 http 서비스만 생성해야 하며 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);
}
 ...
답은 다음과 같습니다. 곧 Cluster._getServer 함수를 통해

  1. 및 Proxy server._listen2를 찾을 수 있습니다. 이 메소드는 작업 프로세스에서 작업을 수행하며

  2. 마스터에 queryServer 메시지를 보내고 마스터에 내부 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);
     }
    */
  3. 마스터 프로세스는 queryServer 메시지를 받은 후 서비스를 시작합니다.

    주소가 모니터링되지 않은 경우 RoundRobinHandle 모니터링을 통해 서비스를 시작합니다.
  1. 주소가 모니터링된 경우 핸들을 직접 바인딩합니다. 모니터링된 서비스를 실행하고 요청을 소비하러 갑니다
  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. 이 단계를 보면 다중 포트 공유의 구현 원리를 이미 알고 있음이 분명합니다

    실제로 포트는 내부 TCP에 의해 한 번만 수신됩니다. 서버는 마스터 프로세스에 있습니다
  1. net.js 모듈은 현재 프로세스가 마스터인지 아니면 작업자 프로세스인지를 결정하기 때문입니다
  2. 작업자 프로세스가 Cluster._getServer를 호출하면 기본 수신 방법을 해킹하세요
  3. 하위 항목에서 호출된 Listen 메서드는 0을 반환하는 빈 메서드이므로 포트 점유 오류가 보고되지 않습니다.
  4. 이제 질문이 뜹니다. 작업자 프로세스는 마스터 프로세스에서 받은 연결을 어떻게 얻나요? 청취 서비스?

    마스터 프로세스에 의해 시작된 TCP 서버의 연결
  1. 이벤트를 듣고

  2. 폴링을 통해 작업자를 선택하고
  3. 메시지 본문에 클라이언트 핸들
  4. 이 포함되어 있습니다.

  5. 핸들을 사용하면 무엇을 해야 할지 모두가 알 수 있습니다. 하하
  6. // 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);
      });
    };
  7. 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);
}

Summary

    net 모듈이 프로세스에 수행할 작업을 Worker 프로세스가 수행한 작업으로 살펴보겠습니다. 워커인지 마스터인지 판단합니다. 워커라면 net.Server 인스턴스의 Listen 메서드를 해킹합니다.
  1. 워커가 호출한 Listen 메서드가 해킹되어 직접 0을 반환하지만 연결 이벤트가 발생합니다. master에 등록됨
  2. 클라이언트 연결 이벤트를 받은 후 마스터는 연결에서 클라이언트 핸들을 폴링하여 작업자에게 보냅니다.
  3. 워커는 마스터가 보낸 클라이언트 핸들을 받은 후 처리할 수 있습니다. the client request
  4. 이 글의 사례를 읽으신 후 방법을 마스터하셨다고 생각합니다. 더 흥미로운 정보를 보려면 PHP 중국어 웹사이트의 다른 관련 글을 주목해 보세요!

추천 자료:

Vue.js 양방향 바인딩 프로젝트 실제 분석


jquery가 요소 콘텐츠가 비어 있는지 확인하는 방법

위 내용은 Node.Js에서 포트 재사용 기능을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.