이번에는 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('exit', (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.jshttp.js 모듈 이해하기: 우리 모두는 http 서비스만 생성해야 하며Master 3596이 실행 중입니다
Worker 4324가 시작되었습니다
Worker 4520이 시작되었습니다
Worker 6056이 시작되었습니다
Worker 5644가 시작되었습니다
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 함수를 통해
// 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); } */마스터 프로세스는 queryServer 메시지를 받은 후 서비스를 시작합니다.
// 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); }); }이 단계를 보면 다중 포트 공유의 구현 원리를 이미 알고 있음이 분명합니다
이제 질문이 뜹니다. 작업자 프로세스는 마스터 프로세스에서 받은 연결을 어떻게 얻나요? 청취 서비스?
// 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); }); };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 프로세스가 수행한 작업으로 살펴보겠습니다. 워커인지 마스터인지 판단합니다. 워커라면 net.Server 인스턴스의 Listen 메서드를 해킹합니다.
vue mint-ui 탭바 구성 요소 사용 단계에 대한 자세한 설명
위 내용은 Node.Js에서 포트 재사용을 구현하는 단계에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!