目录
示例代码托管在:http://www.github.com/dashnowords/blogs
博客园地址:《大史住在大前端》原创博文目录
华为云社区地址:【你要的前端打怪升级指南】
阅读本章需要先阅读本系列前两章内容预热一下。
一. 引言
前两篇博文中已经分别介绍了使用cluster
模块建立集群时主进程执行cluster.fork( )
方法时的执行逻辑,以及net
模块在不同场景下建立通讯的基本原理。本篇继续分析cluster
模块,从第一个子进程开始建立服务器讲起,cluster
基本用法示例代码再来一遍:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 正在运行`);
// 衍生工作进程。
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已退出`);
});
} else {
// 工作进程可以共享任何 TCP 连接。
// 在本例子中,共享的是 HTTP 服务器。
http.createServer((req, res) => {
res.writeHead(200);
res.end('你好世界\n');
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
代码是足够精简的,实现过程也确实是很庞大的工程。每一个子进程中执行的逻辑都是http.createServer().listen()
,我们来看看它是如何一步一步运作而最终建立通讯机制的,你会发现它和上一节中的简易模型非常相似。
二.server.listen方法
在http
模块的源码中很容易找到http.createServer( )
方法的逻辑就是透传参数生成了一个net.Server
实例,这个实例在上一节中就已经介绍过,实际上就只是生成了一个server
的实例,所以这里跳转到net.Server.prototype.listen()
(net.js
文件1306-1404行),基本逻辑如下:
Server.prototype.listen = function(...args){
const normalized = normalizeArgs(args);
var options = normalized[0];
/*..获取监听参数中的句柄对象..*/
options = options._handle || options.handle || options;
//如果options上有句柄,句柄是一个TCP实例
if(options instanceof TCP){
//......
listenInCluster(......);
}
//如果配置参数中有fd(file descriptor)
if(typeof options.fd === 'number' && options.fd >=0){
//......
listenInCluster(......);
}
//如果参数中有port端口号
if(typeof options.port === 'number' || typeof options.port === 'string'){
//.....
listenInCluster(......);
}
//如果参数中有port端口号 或 字符型的pipe名称
if(typeof options.port === 'number' || typeof options.port === 'string'){
//.....
listenInCluster(......);
}
}
这里不难看出它的逻辑就和net
模块官方文档中描述的server.listen( )
的几种场景对应,可以监听带有非空handle
属性的句柄对象,数字型端口号,字符串型命名管道地址,或者直接传入配置参数合集options
,然后分别根据几种不同的情况来调用listenInCluster
方法(集群功能的逻辑主线是数字型port,假设传入了12315
)。
listenInCluster
方法定义如下:
大致可以看出,如果是主进程,就直接调用server._listen2()
方法然后return
了,否则(也就是在工作进程中的逻辑,敲黑板!!!这里是重点了),构造一个serverQuery
的参数集,可以看到里面记录了以各种不同姿势调用这个方法时传入的参数,所以有的参数为null
也很正常,然后调用了cluster._getServer( )
方法,这就是工作进程在引用cluster
模块时引入的child.js
中定义并挂载在cluster
上的方法,最后一个参数listenOnMasterHandle
是一个回调函数,也是一个错误前置风格的函数,可以看到,它接收了一个句柄对象,并把这个句柄对象挂载在了子进程这个server
实例的_handle
属性上,接着也调用了server._listen2( )
方法,可以看到两种情况下调用这个方法时传入的参数是一样的。接着来到server._listen2( )
方法,它绑定了setupListenHandle
方法(别抓狂,这是net
模块中相关逻辑的最后一步了),简化代码如下:
function setupListenHandle(......){
if (this._handle) {
//工作进程在执行上一步逻辑时,在cluster._getServer()回调函数中把一个handle传递给了server._handle
debug('setupListenHandle: have a handle already');
} else {
//主进程会执行的逻辑
debug('setupListenHandle: create a handle');
//......
rval = createServerHandle(address, port, addressType, fd, flags);
//......
this._handle = rval;
}
//......
this._handle.onconnection = onconnection;
this._handle[owner_symbol] = this;
//....
}
工作进程通过cluster._getServer( )
方法拿到了一个handle
,所以不会再生成,而主进程server.listen(port)
执行时会走到else分支,然后生成一个新的绑定了端口号的特殊