详解node child_process模块学习笔记


Posted in Javascript onJanuary 24, 2018

NodeJs是一个单进程的语言,不能像Java那样可以创建多线程来并发执行。当然在大部分情况下,NodeJs是不需要并发执行的,因为它是事件驱动性永不阻塞。但单进程也有个问题就是不能充分利用CPU的多核机制,根据前人的经验,可以通过创建多个进程来充分利用CPU多核,并且Node通过了child_process模块来创建完成多进程的操作。

child_process模块给予node任意创建子进程的能力,node官方文档对于child_proces模块给出了四种方法,映射到操作系统其实都是创建子进程。但对于开发者而已,这几种方法的api有点不同

child_process.exec(command[, options][, callback]) 启动子进程来执行shell命令,可以通过回调参数来获取脚本shell执行结果

child_process.execfile(file[, args][, options][, callback]) 与exec类型不同的是,它执行的不是shell命令而是一个可执行文件

child_process.spawn(command[, args][, options])仅仅执行一个shell命令,不需要获取执行结果

child_process.fork(modulePath[, args][, options])可以用node执行的.js文件,也不需要获取执行结果。fork出来的子进程一定是node进程

exec()与execfile()在创建的时候可以指定timeout属性设置超时时间,一旦超时会被杀死

如果使用execfile()执行可执行文件,那么头部一定是#!/usr/bin/env node

进程间通信

node 与 子进程之间的通信是使用IPC管道机制完成。如果子进程也是node进程(使用fork),则可以使用监听message事件和使用send()来通信。

main.js

var cp = require('child_process');
//只有使用fork才可以使用message事件和send()方法
var n = cp.fork('./child.js');
n.on('message',function(m){
 console.log(m);
})

n.send({"message":"hello"});

child.js

var cp = require('child_process');
process.on('message',function(m){
 console.log(m);
})
process.send({"message":"hello I am child"})

父子进程之间会创建IPC通道,message事件和send()便利用IPC通道通信.

句柄传递

学会如何创建子进程后,我们创建一个HTTP服务并启动多个进程来共同做到充分利用CPU多核。

worker.js

var http = require('http');
http.createServer(function(req,res){
 res.end('Hello,World');
 //监听随机端口
}).listen(Math.round((1+Math.random())*1000),'127.0.0.1');

main.js

var fork = require('child_process').fork;
var cpus = require('os').cpus();
for(var i=0;i<cpus.length;i++){
 fork('./worker.js');
}

上述代码会根据你的cpu核数来创建对应数量的fork进程,每个进程监听一个随机端口来提供HTTP服务。

上述就完成了一个典型的Master-Worker主从复制模式。在分布式应用中用于并行处理业务,具备良好的收缩性和稳定性。这里需要注意,fork一个进程代价是昂贵的,node单进程事件驱动具有很好的性能。此例的多个fork进程是为了充分利用CPU的核,并非解决并发问题.

上述示例有个不太好的地方就是占有了太多端口,那么能不能对于多个子进程全部使用同一个端口从而对外提供http服务也只是使用这一个端口。尝试将上述的端口随机数改为8080,启动会发现抛出如下异常。

events.js:72
  throw er;//Unhandled 'error' event
Error:listen EADDRINUSE
XXXX

抛出端口被占有的异常,这意味着只有一个worker.js才能监听8080端口,而其余的会抛出异常。

如果要解决对外提供一个端口的问题,可以参考nginx反向代理的做法。对于Master进程使用80端口对外提供服务,而对于fork的进程则使用随机端口,Master进程接受到请求就将其转发到fork进程中

对于刚刚所说的代理模式,由于进程每收到一个连接会使用掉一个文件描述符,因此代理模式中客户端连接到代理进程,代理进程再去连接fork进程会使用掉两个文件描述符,OS中文件描述符是有限的,为了解决这个问题,node引入进程间发送句柄的功能。

在node的IPC进程通讯API中,send(message,[sendHandle])的第二个参数就是句柄。

句柄就是一种标识资源的引用,它的内部包含了指向对象的文件描述符。句柄可以用来描述一个socket对象,一个UDP套接子,一个管道主进程向工作进程发送句柄意味着当主进程接收到客户端的socket请求后则直接将这个socket发送给工作进程,而不需要再与工作进程建立socket连接,则文件描述符的浪费即可解决。我们来看示例代码:

main.js

var cp = require('child_process');
var child = cp.fork('./child.js');
var server = require('net').createServer();
//监听客户端的连接
server.on('connection',function(socket){
 socket.end('handled by parent');
});
//启动监听8080端口
server.listen(8080,function(){
//给子进程发送TCP服务器(句柄)
 child.send('server',server);
});

child.js

process.on('message',function(m,server){
 if(m==='server'){
 server.on('connection',function(socket){
  socket.end('handle by child');
 });
 }
});

使用telnet或curl都可以测试:

wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handle by child
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
 

测试结果是每次对于客户端的连接,有可能父进程处理也有可能被子进程处理。现在我们尝试仅提供http服务,并且为了让父进程更加轻量,仅让父进程传递句柄给子进程而不做请求处理:

main.js

var cp = require('child_process');
var child1 = cp.fork('./child.js');
var child2 = cp.fork('./child.js');
var child3 = cp.fork('./child.js');
var child4 = cp.fork('./child.js');
var server = require('net').createServer();
//父进程将接收到的请求分发给子进程
server.listen(8080,function(){
 child1.send('server',server);
 child2.send('server',server);
 child3.send('server',server);
 child4.send('server',server);
 //发送完句柄后关闭监听
 server.close();
});

child.js

var http = require('http');
var serverInChild = http.createServer(function(req,res){
 res.end('I am child.Id:'+process.pid);
});
//子进程收到父进程传递的句柄(即客户端与服务器的socket连接对象)
process.on('message',function(m,serverInParent){
 if(m==='server'){
 //处理与客户端的连接
 serverInParent.on('connection',function(socket){
  //交给http服务来处理
  serverInChild.emit('connection',socket);
 });
 }
});

当运行上述代码,此时查看8080端口占有会有如下结果:

 wang@wang ~/code/nodeStudy $ lsof -i:8080
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node    5120 wang   11u  IPv6  44561      0t0  TCP *:http-alt (LISTEN)
node    5126 wang   11u  IPv6  44561      0t0  TCP *:http-alt (LISTEN)
node    5127 wang   11u  IPv6  44561      0t0  TCP *:http-alt (LISTEN)
node    5133 wang   11u  IPv6  44561      0t0  TCP *:http-alt (LISTEN)

运行curl查看结果:

wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5127
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5120
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
利用jQuery的$.event.fix函数统一浏览器event事件处理
Dec 21 Javascript
比Jquery的document.ready更快的方法
Apr 28 Javascript
JQuery中操作Css样式的方法
Feb 12 Javascript
简介JavaScript中toUpperCase()方法的使用
Jun 06 Javascript
Node.js中Request模块处理HTTP协议请求的基本使用教程
Mar 31 Javascript
微信端开发--登录小程序步骤
Jan 11 Javascript
Vue中引入样式文件的方法
Aug 18 Javascript
用ES6的class模仿Vue写一个双向绑定的示例代码
Apr 20 Javascript
浅谈JS对象添加getter与setter的5种方法
Jun 09 Javascript
js实现无缝轮播图效果
Mar 09 Javascript
js抽奖转盘实现方法分析
May 16 Javascript
node使用async_hooks模块进行请求追踪
Jan 28 Javascript
浅谈Node.js 子进程与应用场景
Jan 24 #Javascript
除Console.log()外更多的Javascript调试命令
Jan 24 #Javascript
深入理解node.js http模块
Jan 24 #Javascript
微信、QQ、微博、Safari中使用js唤起App
Jan 24 #Javascript
基于node打包可执行文件工具_Pkg使用心得分享
Jan 24 #Javascript
Angular整合zTree的示例代码
Jan 24 #Javascript
使用classList来实现两个按钮样式的切换方法
Jan 24 #Javascript
You might like
ninety plus是什么?ninety plus咖啡好吗?
2021/03/04 新手入门
解析PHP中VC6 X86和VC9 X86的区别及 Non Thread Safe的意思
2013/06/28 PHP
PHP将字符分解为多个字符串的方法
2014/11/22 PHP
100行PHP代码实现socks5代理服务器
2016/04/28 PHP
window.onload 加载完毕的问题及解决方案(下)
2009/07/09 Javascript
javascript跨域刷新实现代码
2011/01/01 Javascript
JS实现匀速运动的代码实例
2013/11/29 Javascript
jQuery1.9.1针对checkbox的调整方法(prop)
2014/05/01 Javascript
自己使用js/jquery写的一个定制对话框控件
2014/05/02 Javascript
javascript带回调函数的异步脚本载入方法实例分析
2015/07/02 Javascript
JS实现带圆弧背景渐变效果的导航菜单代码
2015/10/13 Javascript
jQuery基本过滤选择器用法示例
2016/09/09 Javascript
JS正则表达式验证中文字符
2017/05/08 Javascript
JavaScript设计模式之代理模式详解
2017/06/09 Javascript
Javascript快速实现浏览器系统通知
2017/08/26 Javascript
jQuery选择器之子元素过滤选择器
2017/09/28 jQuery
详解如何在vue-cli中使用vuex
2018/08/07 Javascript
jquery获取file表单选择文件的路径、名字、大小、类型
2019/01/18 jQuery
[04:09]显微镜下的DOTA2第十二期—NaVi美如画的团战
2014/06/23 DOTA
[01:07:53]RNG vs VG 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
Python字符串和文件操作常用函数分析
2015/04/08 Python
python爬取个性签名的方法
2018/06/17 Python
python PrettyTable模块的安装与简单应用
2019/01/11 Python
很酷的python表白工具 你喜欢我吗
2019/04/11 Python
如何把外网python虚拟环境迁移到内网
2020/05/18 Python
python中常见错误及解决方法
2020/06/21 Python
Python pip 常用命令汇总
2020/10/19 Python
图解CSS3制作圆环形进度条的实例教程
2016/05/26 HTML / CSS
HomeAway澳大利亚:预订你的度假屋,公寓、度假村、别墅等
2019/02/20 全球购物
英国在线发型和美容产品商店:Beauty Cutie
2019/04/27 全球购物
SQL SERVER面试资料
2013/03/30 面试题
怎样写好自我鉴定
2013/12/04 职场文书
大型活动组织方案
2014/05/10 职场文书
2015年数学教研组工作总结
2015/05/23 职场文书
勇敢的心观后感
2015/06/09 职场文书
工作服管理制度范本
2015/08/06 职场文书