详解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 相关文章推荐
FileUpload上传图片(图片不变形)
Aug 05 Javascript
js输入框邮箱自动提示功能代码实现
Dec 10 Javascript
jQuery ui 利用 datepicker插件实现开始日期(minDate)和结束日期(maxDate)
May 22 Javascript
js jquery获取当前元素的兄弟级 上一个 下一个元素
Sep 01 Javascript
KnockoutJs快速入门教程
May 16 Javascript
JQuery EasyUI学习教程之datagrid 添加、修改、删除操作
Jul 09 Javascript
基于jQuery实现Accordion手风琴自定义插件
Oct 13 Javascript
jQuery zTree 异步加载添加子节点重复问题
Nov 29 jQuery
angular项目中bootstrap-datetimepicker时间插件的使用示例
Mar 15 Javascript
解决LayUI表单获取不到data的问题
Aug 20 Javascript
Vue 理解之白话 getter/setter详解
Apr 16 Javascript
一起深入理解js中的事件对象
Feb 06 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
投票管理程序
2006/10/09 PHP
mysql_fetch_row,mysql_fetch_array,mysql_fetch_assoc的区别
2009/04/24 PHP
解析php二分法查找数组是否包含某一元素
2013/05/23 PHP
php抽奖小程序的实现代码
2013/06/18 PHP
php中file_exists函数使用详解
2015/05/08 PHP
phpmailer简单发送邮件的方法(附phpmailer源码下载)
2016/06/13 PHP
IE6不能修改NAME问题的解决方法
2010/09/03 Javascript
Javascript 面试题随笔
2011/03/31 Javascript
引用 js在IE与FF之间的区别详细解析
2013/11/20 Javascript
捕获和分析JavaScript Error的方法
2014/03/25 Javascript
在浏览器中打开或关闭JavaScript的方法
2015/06/03 Javascript
基于jquery实现省市区三级联动效果
2015/12/25 Javascript
JavaScript实现星星等级评价功能
2017/03/22 Javascript
jq checkbox 的全选并ajax传参的实例
2017/04/01 Javascript
Three.js如何实现雾化效果示例代码
2017/09/27 Javascript
使用JavaScript中的lodash编写双色球效果
2018/06/24 Javascript
vue中 v-for循环的用法详解
2020/02/19 Javascript
Vue使用富文本编辑器Vue-Quill-Editor(含图片自定义上传服务、清除复制粘贴样式等)
2020/05/15 Javascript
vue 基于abstract 路由模式 实现页面内嵌的示例代码
2020/12/14 Vue.js
[43:43]完美世界DOTA2联赛PWL S2 LBZS vs Forest 第三场 11.29
2020/12/02 DOTA
python3利用Dlib19.7实现人脸68个特征点标定
2018/02/26 Python
Python基于递归算法实现的汉诺塔与Fibonacci数列示例
2018/04/18 Python
python实现人人自动回复、抢沙发功能
2018/06/08 Python
Pycharm无法显示动态图片的解决方法
2018/10/28 Python
python 使用re.search()筛选后 选取部分结果的方法
2018/11/28 Python
python实现抠图给证件照换背景源码
2019/08/20 Python
Python字典深浅拷贝与循环方式方法详解
2020/02/09 Python
浅谈keras使用中val_acc和acc值不同步的思考
2020/06/18 Python
Python 数据分析之逐块读取文本的实现
2020/12/14 Python
python实现scrapy爬虫每天定时抓取数据的示例代码
2021/01/27 Python
一张图片能隐含千言万语之隐藏你的程序代码
2012/12/13 HTML / CSS
西安交大自主招生自荐信
2014/01/27 职场文书
詹天佑教学反思
2014/04/30 职场文书
竞选班干部演讲稿400字
2014/08/20 职场文书
客房部经理岗位职责
2015/02/02 职场文书
修改Nginx配置返回指定content-type的方法
2022/09/23 Servers