Node.js的進(jìn)程管理的深入理解
眾所周知Node基于V8,而在V8中JavaScript是單線程運(yùn)行的,這里的單線程不是指Node啟動(dòng)的時(shí)候就只有一個(gè)線程,而是說運(yùn)行JavaScript代碼是在單線程上,Node還有其他線程,比如進(jìn)行異步IO操作的IO線程。這種單線程模型帶來的好處就是系統(tǒng)調(diào)度過程中不會(huì)頻繁進(jìn)行上下文切換,提升了單核CPU的利用率。
但是這種做法有個(gè)缺陷,就是我們無法利用服務(wù)器CPU多核的性能,一個(gè)Node進(jìn)程只能利用一個(gè)CPU。而且單線程模式下一旦代碼崩潰就是整個(gè)程序崩潰。通常解決方案就是使用Node的cluster模塊,通過master-worker模式啟用多個(gè)進(jìn)程實(shí)例。下面我們?cè)敿?xì)講述下,Node如何使用多進(jìn)程模型利用多核CPU,以及自帶的cluster模塊具體的工作原理。
如何創(chuàng)建子進(jìn)程
node提供了child_process模塊用來進(jìn)行子進(jìn)程的創(chuàng)建,該模塊一共有四個(gè)方法用來創(chuàng)建子進(jìn)程。
const { spawn, exec, execFile, fork } = require('child_process')
spawn(command[, args][, options])
exec(command[, options][, callback])
execFile(file[, args][, options][, callback])
fork(modulePath[, args][, options])
spawn
首先認(rèn)識(shí)一下spawn方法,下面是Node文檔的官方實(shí)例。
const { spawn } = require('child_process');
const child = spawn('ls', ['-lh', '/home']);
child.on('close', (code) => {
console.log(`子進(jìn)程退出碼:$[code]`);
});
const { stdin, stdout, stderr } = child
stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
通過spawn創(chuàng)建的子進(jìn)程,繼承自EventEmitter,所以可以在上面進(jìn)行事件(discount,error,close,message)的監(jiān)聽。同時(shí)子進(jìn)程具有三個(gè)輸入輸出流:stdin、stdout、stderr,通過這三個(gè)流,可以實(shí)時(shí)獲取子進(jìn)程的輸入輸出和錯(cuò)誤信息。
這個(gè)方法的最終實(shí)現(xiàn)基于libuv,這里不再展開討論,感興趣可以查看源碼。
// 調(diào)用libuv的api,初始化一個(gè)進(jìn)程 int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
exec/execFile
之所以把這兩個(gè)放到一起,是因?yàn)閑xec最后調(diào)用的就是execFile方法。唯一的區(qū)別是,exec中調(diào)用的normalizeExecArgs方法會(huì)將opts的shell屬性默認(rèn)設(shè)置為true。
exports.exec = function exec(/* command , options, callback */) {
const opts = normalizeExecArgs.apply(null, arguments);
return exports.execFile(opts.file, opts.options, opts.callback);
};
function normalizeExecArgs(command, options, callback) {
options = { ...options };
options.shell = typeof options.shell === 'string' ? options.shell : true;
return { options };
}
在execFile中,最終調(diào)用的是spawn方法。
exports.execFile = function execFile(file /* , args, options, callback */) {
let args = [];
let callback;
let options;
var child = spawn(file, args, {
// ... some options
});
return child;
}
exec會(huì)將spawn的輸入輸出流轉(zhuǎn)換成String,默認(rèn)使用UTF-8的編碼,然后傳遞給回調(diào)函數(shù),使用回調(diào)方式在node中較為熟悉,比流更容易操作,所以我們能使用exec方法執(zhí)行一些shell命令,然后在回調(diào)中獲取返回值。有點(diǎn)需要注意,這里的buffer是有最大緩存區(qū)的,如果超出會(huì)直接被kill掉,可用通過maxBuffer屬性進(jìn)行配置(默認(rèn): 200*1024)。
const { exec } = require('child_process');
exec('ls -lh /home', (error, stdout, stderr) => {
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
});
fork
fork最后也是調(diào)用spawn來創(chuàng)建子進(jìn)程,但是fork是spawn的一種特殊情況,用于衍生新的 Node.js 進(jìn)程,會(huì)產(chǎn)生一個(gè)新的V8實(shí)例,所以執(zhí)行fork方法時(shí)需要指定一個(gè)js文件。
exports.fork = function fork(modulePath /* , args, options */) {
// ...
options.shell = false;
return spawn(options.execPath, args, options);
};
通過fork創(chuàng)建子進(jìn)程之后,父子進(jìn)程直接會(huì)創(chuàng)建一個(gè)IPC(進(jìn)程間通信)通道,方便父子進(jìn)程直接通信,在js層使用 process.send(message) 和 process.on('message', msg => {}) 進(jìn)行通信。而在底層,實(shí)現(xiàn)進(jìn)程間通信的方式有很多,Node的進(jìn)程間通信基于libuv實(shí)現(xiàn),不同操作系統(tǒng)實(shí)現(xiàn)方式不一致。在*unix系統(tǒng)中采用Unix Domain Socket方式實(shí)現(xiàn),Windows中使用命名管道的方式實(shí)現(xiàn)。
常見進(jìn)程間通信方式:消息隊(duì)列、共享內(nèi)存、pipe、信號(hào)量、套接字
下面是一個(gè)父子進(jìn)程通信的實(shí)例。
parent.js
const path = require('path')
const { fork } = require('child_process')
const child = fork(path.join(__dirname, 'child.js'))
child.on('message', msg => {
console.log('message from child', msg)
});
child.send('hello child, I\'m master')
child.js
process.on('message', msg => {
console.log('message from master:', msg)
});
let counter = 0
setInterval(() => {
process.send({
child: true,
counter: counter++
})
}, 1000);

小結(jié)
其實(shí)可以看到,這些方法都是對(duì)spawn方法的復(fù)用,然后spawn方法底層調(diào)用了libuv進(jìn)行進(jìn)程的管理,具體可以看下圖。

利用fork實(shí)現(xiàn)master-worker模型
首先來看看,如果我們?cè)?code>child.js中啟動(dòng)一個(gè)http服務(wù)會(huì)發(fā)生什么情況。
// master.js
const { fork } = require('child_process')
for (let i = 0; i < 2; i++) {
const child = fork('./child.js')
}
// child.js
const http = require('http')
http.createServer((req, res) => {
res.end('Hello World\n');
}).listen(8000)

+--------------+
| |
| master |
| |
+--------+--------------+- -- -- -
| |
| Error: listen EADDRINUSE
| |
|
+----v----+ +-----v---+
| | | |
| worker1 | | worker2 |
| | | |
+---------+ +---------+
:8000 :8000
我們fork了兩個(gè)子進(jìn)程,因?yàn)閮蓚€(gè)子進(jìn)程同時(shí)對(duì)一個(gè)端口進(jìn)行監(jiān)聽,Node會(huì)直接拋出一個(gè)異常(Error: listen EADDRINUSE),如上圖所示。那么我們能不能使用代理模式,同時(shí)監(jiān)聽多個(gè)端口,讓master進(jìn)程監(jiān)聽80端口收到請(qǐng)求時(shí),再將請(qǐng)求分發(fā)給不同服務(wù),而且master進(jìn)程還能做適當(dāng)?shù)呢?fù)載均衡。
+--------------+
| |
| master |
| :80 |
+--------+--------------+---------+
| |
| |
| |
| |
+----v----+ +-----v---+
| | | |
| worker1 | | worker2 |
| | | |
+---------+ +---------+
:8000 :8001
但是這么做又會(huì)帶來另一個(gè)問題,代理模式中十分消耗文件描述符(linux系統(tǒng)默認(rèn)的最大文件描述符限制是1024),文件描述符在windows系統(tǒng)中稱為句柄(handle),習(xí)慣性的我們也可以稱linux中的文件描述符為句柄。當(dāng)用戶進(jìn)行訪問,首先連接到master進(jìn)程,會(huì)消耗一個(gè)句柄,然后master進(jìn)程再代理到worker進(jìn)程又會(huì)消耗掉一個(gè)句柄,所以這種做法十分浪費(fèi)系統(tǒng)資源。為了解決這個(gè)問題,Node的進(jìn)程間通信可以發(fā)送句柄,節(jié)省系統(tǒng)資源。
句柄是一種特殊的智能指針 。當(dāng)一個(gè)應(yīng)用程序要引用其他系統(tǒng)(如數(shù)據(jù)庫(kù)、操作系統(tǒng))所管理的內(nèi)存塊或?qū)ο髸r(shí),就要使用句柄。
我們可以在master進(jìn)程啟動(dòng)一個(gè)tcp服務(wù),然后通過IPC將服務(wù)的句柄發(fā)送給子進(jìn)程,子進(jìn)程再對(duì)服務(wù)的連接事件進(jìn)行監(jiān)聽,具體代碼如下:
// master.js
var { fork } = require('child_process')
var server = require('net').createServer()
server.on('connection', function(socket) {
socket.end('handled by master') // 響應(yīng)來自master
})
server.listen(3000, function() {
console.log('master listening on: ', 3000)
})
for (var i = 0; i < 2; i++) {
var child = fork('./child.js')
child.send('server', server) // 發(fā)送句柄給worker
console.log('worker create, pid is ', child.pid)
}
// child.js
process.on('message', function (msg, handler) {
if (msg !== 'server') {
return
}
// 獲取到句柄后,進(jìn)行請(qǐng)求的監(jiān)聽
handler.on('connection', function(socket) {
socket.end('handled by worker, pid is ' + process.pid)
})
})

下面我們通過curl連續(xù)請(qǐng)求 5 次服務(wù)。
for varible1 in {1..5}
do
curl "localhost:3000"
done

可以看到,響應(yīng)請(qǐng)求的可以是父進(jìn)程,也可以是不同子進(jìn)程,多個(gè)進(jìn)程對(duì)同一個(gè)服務(wù)響應(yīng)的連接事件監(jiān)聽,誰先搶占,就由誰進(jìn)行響應(yīng)。這里就會(huì)出現(xiàn)一個(gè)Linux網(wǎng)絡(luò)編程中很常見的事件,當(dāng)多個(gè)進(jìn)程同時(shí)監(jiān)聽網(wǎng)絡(luò)的連接事件,當(dāng)這個(gè)有新的連接到達(dá)時(shí),這些進(jìn)程被同時(shí)喚醒,這被稱為“驚群”。這樣導(dǎo)致的情況就是,一旦事件到達(dá),每個(gè)進(jìn)程同時(shí)去響應(yīng)這一個(gè)事件,而最終只有一個(gè)進(jìn)程能處理事件成功,其他的進(jìn)程在處理該事件失敗后重新休眠,造成了系統(tǒng)資源的浪費(fèi)。

ps:在windows系統(tǒng)上,永遠(yuǎn)都是最后定義的子進(jìn)程搶占到句柄,這可能和libuv的實(shí)現(xiàn)機(jī)制有關(guān),具體原因往有大佬能夠指點(diǎn)。

出現(xiàn)這樣的問題肯定是大家都不愿意的嘛,這個(gè)時(shí)候我們就想起了nginx的好了,這里有篇文章講解了nginx是如何解決“驚群”的,利用nginx的反向代理可以有效地解決這個(gè)問題,畢竟nginx本來就很擅長(zhǎng)這種問題。
http {
upstream node {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
keepalive 64;
}
server {
listen 80;
server_name shenfq.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Nginx-Proxy true;
proxy_set_header Connection "";
proxy_pass http://node; # 這里要和最上面upstream后的應(yīng)用名一致,可以自定義
}
}
}
小結(jié)
如果我們自己用Node原生來實(shí)現(xiàn)一個(gè)多進(jìn)程模型,存在這樣或者那樣的問題,雖然最終我們借助了nginx達(dá)到了這個(gè)目的,但是使用nginx的話,我們需要另外維護(hù)一套nginx的配置,而且如果有一個(gè)Node服務(wù)掛了,nginx并不知道,還是會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到那個(gè)端口。
cluster模塊
除了用nginx做反向代理,node本身也提供了一個(gè)cluster模塊,用于多核CPU環(huán)境下多進(jìn)程的負(fù)載均衡。cluster模塊創(chuàng)建子進(jìn)程本質(zhì)上是通過child_procee.fork,利用該模塊可以很容易的創(chuàng)建共享同一端口的子進(jìn)程服務(wù)器。
上手指南
有了這個(gè)模塊,你會(huì)感覺實(shí)現(xiàn)Node的單機(jī)集群是多么容易的一件事情。下面看看官方實(shí)例,短短的十幾行代碼就實(shí)現(xiàn)了一個(gè)多進(jìn)程的Node服務(wù),且自帶負(fù)載均衡。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) { // 判斷是否為主進(jìn)程
console.log(`主進(jìn)程 ${process.pid} 正在運(yùn)行`);
// 衍生工作進(jìn)程。
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作進(jìn)程 ${worker.process.pid} 已退出`);
});
} else { // 子進(jìn)程進(jìn)行服務(wù)器創(chuàng)建
// 工作進(jìn)程可以共享任何 TCP 連接。
// 在本例子中,共享的是一個(gè) HTTP 服務(wù)器。
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`工作進(jìn)程 ${process.pid} 已啟動(dòng)`);
}

cluster模塊源碼分析
首先看代碼,通過isMaster來判斷是否為主進(jìn)程,如果是主進(jìn)程進(jìn)行fork操作,子進(jìn)程創(chuàng)建服務(wù)器。這里cluster進(jìn)行fork操作時(shí),執(zhí)行的是當(dāng)前文件。cluster.fork最終調(diào)用的child_process.fork,且第一個(gè)參數(shù)為process.argv.slice(2),在fork子進(jìn)程之后,會(huì)對(duì)其internalMessage事件進(jìn)行監(jiān)聽,這個(gè)后面會(huì)提到,具體代碼如下:
const { fork } = require('child_process');
cluster.fork = function(env) {
cluster.setupMaster();
const id = ++ids;
const workerProcess = createWorkerProcess(id, env);
const worker = new Worker({
id: id,
process: workerProcess
});
// 監(jiān)聽子進(jìn)程的消息
worker.process.on('internalMessage', internal(worker, onmessage));
// ...
};
// 配置master進(jìn)程
cluster.setupMaster = function(options) {
cluster.settings = {
args: process.argv.slice(2),
exec: process.argv[1],
execArgv: process.execArgv,
silent: false,
...cluster.settings,
...options
};
};
// 創(chuàng)建子進(jìn)程
function createWorkerProcess(id, env) {
return fork(cluster.settings.exec, cluster.settings.args, {
// some options
});
}
子進(jìn)程端口監(jiān)聽問題
這里會(huì)有一個(gè)問題,子進(jìn)程全部都在監(jiān)聽同一個(gè)端口,我們之前已經(jīng)試驗(yàn)過,服務(wù)監(jiān)聽同一個(gè)端口會(huì)出現(xiàn)端口占用的問題,那么cluster模塊如何保證端口不沖突的呢? 查閱源碼發(fā)現(xiàn),http模塊的createServer繼承自net模塊。
util.inherits(Server, net.Server);
而在net模塊中,listen方法會(huì)調(diào)用listenInCluster方法,listenInCluster判斷當(dāng)前是否為master進(jìn)程。
Server.prototype.listen = function(...args) {
// ...
if (typeof options.port === 'number' || typeof options.port === 'string') {
// 如果listen方法只傳入了端口號(hào),最后會(huì)走到這里
listenInCluster(this, null, options.port | 0, 4, backlog, undefined, options.exclusive);
return this;
}
// ...
};
function listenInCluster(server, address, port, addressType, backlog, fd, exclusive, flags) {
if (cluster === undefined) cluster = require('cluster');
if (cluster.isMaster) {
// 如果是主進(jìn)程則啟動(dòng)一個(gè)服務(wù)
// 但是主進(jìn)程沒有調(diào)用過listen方法,所以沒有走這里一步
server._listen2(address, port, addressType, backlog, fd, flags);
return;
}
const serverQuery = {
address: address,
port: port,
addressType: addressType,
fd: fd,
flags,
};
// 子進(jìn)程獲取主進(jìn)程服務(wù)的句柄
cluster._getServer(server, serverQuery, listenOnMasterHandle);
function listenOnMasterHandle(err, handle) {
server._handle = handle; // 重寫handle,對(duì)listen方法進(jìn)行了hack
server._listen2(address, port, addressType, backlog, fd, flags);
}
}
看上面代碼可以知道,真正啟動(dòng)服務(wù)的方法為server._listen2。在_listen2方法中,最終調(diào)用的是_handle下的listen方法。
function setupListenHandle(address, port, addressType, backlog, fd, flags) {
// ...
this._handle.onconnection = onconnection;
var err = this._handle.listen(backlog || 511);
// ...
}
Server.prototype._listen2 = setupListenHandle; // legacy alias
那么cluster._getServer方法到底做了什么呢?
搜尋它的源碼,首先向master進(jìn)程發(fā)送了一個(gè)消息,消息類型為queryServer。
// child.js
cluster._getServer = function(obj, options, cb) {
// ...
const message = {
act: 'queryServer',
index,
data: null,
...options
};
// 發(fā)送消息到master進(jìn)程,消息類型為 queryServer
send(message, (reply, handle) => {
rr(reply, indexesKey, cb); // Round-robin.
});
// ...
};
這里的rr方法,對(duì)前面提到的_handle.listen進(jìn)行了hack,所有子進(jìn)程的listen其實(shí)是不起作用的。
function rr(message, indexesKey, cb) {
if (message.errno)
return cb(message.errno, null);
var key = message.key;
function listen(backlog) { // listen方法直接返回0,不再進(jìn)行端口監(jiān)聽
return 0;
}
function close() {
send({ act: 'close', key });
}
function getsockname(out) {
return 0;
}
const handle = { close, listen, ref: noop, unref: noop };
handles.set(key, handle); // 根據(jù)key將工作進(jìn)程的 handle 進(jìn)行緩存
cb(0, handle);
}
// 這里的cb回調(diào)就是前面_getServer方法傳入的。 參考之前net模塊的listen方法
function listenOnMasterHandle(err, handle) {
server._handle = handle; // 重寫handle,對(duì)listen方法進(jìn)行了hack
// 該方法調(diào)用后,會(huì)對(duì)handle綁定一個(gè) onconnection 方法,最后會(huì)進(jìn)行調(diào)用
server._listen2(address, port, addressType, backlog, fd, flags);
}
主進(jìn)程與子進(jìn)程通信
那么到底在哪里對(duì)端口進(jìn)行了監(jiān)聽呢?
前面提到過,fork子進(jìn)程的時(shí)候,對(duì)子進(jìn)程進(jìn)行了internalMessage事件的監(jiān)聽。
worker.process.on('internalMessage', internal(worker, onmessage));
子進(jìn)程向master進(jìn)程發(fā)送消息,一般使用process.send方法,會(huì)被監(jiān)聽的message事件所接收。這里是因?yàn)榘l(fā)送的message指定了cmd: 'NODE_CLUSTER',只要cmd字段以NODE_開頭,這樣消息就會(huì)認(rèn)為是內(nèi)部通信,被internalMessage事件所接收。
// child.js
function send(message, cb) {
return sendHelper(process, message, null, cb);
}
// utils.js
function sendHelper(proc, message, handle, cb) {
if (!proc.connected)
return false;
// Mark message as internal. See INTERNAL_PREFIX in lib/child_process.js
message = { cmd: 'NODE_CLUSTER', ...message, seq };
if (typeof cb === 'function')
callbacks.set(seq, cb);
seq += 1;
return proc.send(message, handle);
}
master進(jìn)程接收到消息后,根據(jù)act的類型開始執(zhí)行不同的方法,這里act為queryServer。queryServer方法會(huì)構(gòu)造一個(gè)key,如果這個(gè)key(規(guī)則主要為地址+端口+文件描述符)之前不存在,則對(duì)RoundRobinHandle構(gòu)造函數(shù)進(jìn)行了實(shí)例化,RoundRobinHandle構(gòu)造函數(shù)中啟動(dòng)了一個(gè)TCP服務(wù),并對(duì)之前指定的端口進(jìn)行了監(jiān)聽。
// master.js
const handles = new Map();
function onmessage(message, handle) {
const worker = this;
if (message.act === 'online')
online(worker);
else if (message.act === 'queryServer')
queryServer(worker, message);
// other act logic
}
function queryServer(worker, message) {
// ...
const key = `${message.address}:${message.port}:${message.addressType}:` +
`${message.fd}:${message.index}`;
var handle = handles.get(key);
// 如果之前沒有對(duì)該key進(jìn)行實(shí)例化,則進(jìn)行實(shí)例化
if (handle === undefined) {
let address = message.address;
// const RoundRobinHandle = require('internal/cluster/round_robin_handle');
var constructor = RoundRobinHandle;
handle = new constructor(key,
address,
message.port,
message.addressType,
message.fd,
message.flags);
handles.set(key, handle);
}
// ...
}
// internal/cluster/round_robin_handle
function RoundRobinHandle(key, address, port, addressType, fd, flags) {
this.server = net.createServer(assert.fail);
// 這里啟動(dòng)一個(gè)TCP服務(wù)器
this.server.listen({ port, host });
// TCP服務(wù)器啟動(dòng)時(shí)的事件
this.server.once('listening', () => {
this.handle = this.server._handle;
this.handle.onconnection = (err, handle) => this.distribute(err, handle);
});
// ...
}
可以看到TCP服務(wù)啟動(dòng)后,立馬對(duì)connection事件進(jìn)行了監(jiān)聽,會(huì)調(diào)用RoundRobinHandle的distribute方法。
// RoundRobinHandle
this.handle.onconnection = (err, handle) => this.distribute(err, handle);
// distribute 對(duì)工作進(jìn)程進(jìn)行分發(fā)
RoundRobinHandle.prototype.distribute = function(err, handle) {
this.handles.push(handle); // 存入TCP服務(wù)的句柄
const worker = this.free.shift(); // 取出第一個(gè)工作進(jìn)程
if (worker)
this.handoff(worker); // 切換到工作進(jìn)程
};
RoundRobinHandle.prototype.handoff = function(worker) {
const handle = this.handles.shift(); // 獲取TCP服務(wù)句柄
if (handle === undefined) {
this.free.push(worker); // 將該工作進(jìn)程重新放入隊(duì)列中
return;
}
const message = { act: 'newconn', key: this.key };
// 向工作進(jìn)程發(fā)送一個(gè)類型為 newconn 的消息以及TCP服務(wù)的句柄
sendHelper(worker.process, message, handle, (reply) => {
if (reply.accepted)
handle.close();
else
this.distribute(0, handle); // 工作進(jìn)程不能正常運(yùn)行,啟動(dòng)下一個(gè)
this.handoff(worker);
});
};
在子進(jìn)程中也有對(duì)內(nèi)部消息進(jìn)行監(jiān)聽,在cluster/child.js中,有個(gè)cluster._setupWorker方法,該方法會(huì)對(duì)內(nèi)部消息監(jiān)聽,該方法的在lib/internal/bootstrap/node.js中調(diào)用,這個(gè)文件是每次啟動(dòng)node命令后,由C++模塊調(diào)用的。
function startup() {
// ...
startExecution();
}
function startExecution() {
// ...
prepareUserCodeExecution();
}
function prepareUserCodeExecution() {
if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
const cluster = NativeModule.require('cluster');
cluster._setupWorker();
delete process.env.NODE_UNIQUE_ID;
}
}
startup()
下面看看_setupWorker方法做了什么。
cluster._setupWorker = function() {
// ...
process.on('internalMessage', internal(worker, onmessage));
function onmessage(message, handle) {
// 如果act為 newconn 調(diào)用onconnection方法
if (message.act === 'newconn')
onconnection(message, handle);
else if (message.act === 'disconnect')
_disconnect.call(worker, true);
}
};
function onconnection(message, handle) {
const key = message.key;
const server = handles.get(key);
const accepted = server !== undefined;
send({ ack: message.seq, accepted });
if (accepted)
server.onconnection(0, handle); // 調(diào)用net中的onconnection方法
}
最后子進(jìn)程獲取到客戶端句柄后,調(diào)用net模塊的onconnection,對(duì)Socket進(jìn)行實(shí)例化,后面就與其他http請(qǐng)求的邏輯一致了,不再細(xì)講。
至此,cluster模塊的邏輯就走通了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
nodejs將JSON字符串轉(zhuǎn)化為JSON對(duì)象報(bào)錯(cuò)的解決
這篇文章主要介紹了nodejs將JSON字符串轉(zhuǎn)化為JSON對(duì)象報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
ajax+node+request爬取網(wǎng)絡(luò)圖片的實(shí)例(宅男福利)
下面小編就為大家?guī)硪黄猘jax+node+request爬取網(wǎng)絡(luò)圖片的實(shí)例(宅男福利)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08
nodejs中函數(shù)的調(diào)用實(shí)例詳解
本文通過實(shí)例代碼給大家介紹了nodejs函數(shù)的調(diào)用,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10
npm?install安裝失敗常見問題的解決辦法小結(jié)
有時(shí)候前端安裝npm install 安裝包總是安裝不上,下面這篇文章主要給大家介紹了關(guān)于npm?install安裝失敗常見問題的解決辦法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
nodejs使用Sequelize框架操作數(shù)據(jù)庫(kù)的實(shí)現(xiàn)
這篇文章主要介紹了nodejs使用Sequelize框架操作數(shù)據(jù)庫(kù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

