> 웹 프론트엔드 > JS 튜토리얼 > Node.Js의 포트 재사용 원리에 대한 자세한 설명

Node.Js의 포트 재사용 원리에 대한 자세한 설명

不言
풀어 주다: 2018-05-05 11:28:43
원래의
1531명이 탐색했습니다.

이 글은 주로 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(&#39;hello world\n&#39;);
 }).listen(8000);

 console.log(`Worker ${process.pid} started`);
}
로그인 후 복사

실행 결과:

$ 노드 서버. js
Master 3596이 실행 중입니다
Worker 4324가 시작되었습니다
Worker 4520이 시작되었습니다
Worker 6056이 시작되었습니다
Worker 5644가 시작되었습니다

http.js 모듈을 이해하세요.

우리는 모두 http 서비스만 생성하면 되며 http 모듈을 참조해야 합니다. .http 모듈은 결국 net.js를 호출하여 네트워크 서비스를 구현합니다

// lib/net.js
&#39;use strict&#39;;

 ...
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: &#39;queryServer&#39;,  // 关键点:构建一个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(&#39;listening&#39;, () => {
  cluster.worker.state = &#39;listening&#39;;
  const address = obj.address();
  message.act = &#39;listening&#39;;
  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);
 }
*/
로그인 후 복사

마스터 프로세스는 queryServer 메시지를 받은 후 서비스를 시작합니다

  1. 주소가 모니터링, RoundRobinHandle 모니터링을 통해 서비스 시작

  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(&#39;:&#39;);
  var handle = handles[key];

  // 如果地址没被监听过,通过RoundRobinHandle监听开启服务
  if (handle === undefined) {
    var constructor = RoundRobinHandle;
    if (schedulingPolicy !== SCHED_RR ||
      message.addressType === &#39;udp4&#39; ||
      message.addressType === &#39;udp6&#39;) {
      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);
  });
}
로그인 후 복사

이 단계를 보면, 다중 포트 공유의 구현 원리는 이미 알고 있습니다

  1. 사실 포트는 마스터 프로세스에 의해서만 제어됩니다. 내부 TCP 서버는 한 번만 수신합니다

  2. net.js 모듈이 현재 포트 공유 여부를 결정하기 때문입니다. 프로세스가 마스터이거나 작업자 프로세스입니다

  3. 작업자 프로세스인 경우 Cluster._getServer를 호출하여 기본 Listen 메서드를 해킹하세요

  4. 그래서 자식이 호출한 Listen 메서드는 0을 반환하는 빈 메서드이고, 그러면 포트 점유 오류가 보고되지 않습니다

이제 질문은 작업자 프로세스가 마스터 프로세스 수신 서비스에서 수신한 연결을 어떻게 얻느냐 하는 것입니다.

  1. 마스터 프로세스에 의해 시작된 TCP 서버의 연결 이벤트를 수신합니다

  2. 폴링을 통해 작업자 선택

  3. newconn 내부 메시지를 보냅니다. 메시지 본문에는 클라이언트 핸들이 포함됩니다

  4. With 처리는 다들 아시죠 ㅎㅎ ​​

// 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(&#39;listening&#39;, () => {
    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: &#39;newconn&#39;, key: this.key };
  // 向work进程其发送newconn内部消息和客户端的句柄handle
  sendHelper(worker.process, message, handle, (reply) => {
  // ...
    this.handoff(worker);
  });
};
로그인 후 복사

newconn 메시지를 받은 후 Worker 프로세스가 어떤 작업을 수행했는지 볼까요

// lib/child.js
function onmessage(message, handle) {
  if (message.act === &#39;newconn&#39;)
   onconnection(message, handle);
  else if (message.act === &#39;disconnect&#39;)
   _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. 넷 모듈은 프로세스가 워커인지 마스터인지 판단합니다. 워커라면 넷의 Listen 메소드를 해킹하세요.서버 인스턴스

  2. 워커가 호출한 Listen 메소드가 해킹되어 0을 직접 반환합니다. , 그러나 연결에 의해 인계된 이벤트를 마스터에 등록합니다.

  3. 마스터가 클라이언트 연결 이벤트를 수신한 후 연결에서 클라이언트 핸들을 보내기 위해 작업자를 폴링합니다.

  4. 작업자는 전송된 클라이언트 핸들을 받습니다. 그런 다음 클라이언트 요청을 처리할 수 있습니다.

관련 권장 사항:

노드는 정적 리소스 서버를 구현합니다

위 내용은 Node.Js의 포트 재사용 원리에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿