详解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 相关文章推荐
javascript面向对象之Javascript 继承
May 04 Javascript
查看源码的工具 学习jQuery源码不错的工具
Dec 26 Javascript
快速学习jQuery插件 Form表单插件使用方法
Dec 01 Javascript
jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween
Dec 03 Javascript
jQuery实现的自定义弹出层效果实例详解
Sep 04 Javascript
基于jQuery和Bootstrap框架实现仿知乎前端动态列表效果
Nov 09 Javascript
js+div+css下拉导航菜单完整代码分享
Dec 28 Javascript
浅谈jQuery中的$.extend方法来扩展JSON对象
Feb 12 Javascript
React组件之间的通信的实例代码
Jun 27 Javascript
JS实现动态添加外部js、css到head标签的方法
Jun 05 Javascript
vue全局使用axios的操作
Sep 08 Javascript
如何利用 JS 脚本实现网页全自动秒杀抢购功能
Oct 12 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
?算你??的 PHP 程式大小
2006/12/06 PHP
用php实现百度网盘图片直链的代码分享
2012/11/01 PHP
php页面函数设置超时限制的方法
2014/12/01 PHP
Laravel中批量赋值Mass-Assignment的真正含义详解
2017/09/29 PHP
PHP使用HTML5 FormData对象提交表单操作示例
2019/07/02 PHP
PHP实现简易用户登录系统
2020/07/10 PHP
Laravel 自动转换长整型雪花 ID 为字符串的实现
2020/10/27 PHP
移动节点的jquery代码
2014/01/13 Javascript
js判断为空Null与字符串为空简写方法
2014/02/24 Javascript
JS运动框架之分享侧边栏动画实例
2015/03/03 Javascript
jQuery 遍历map()方法详解
2016/11/04 Javascript
JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)
2016/12/14 Javascript
使用travis-ci如何持续部署node.js应用详解
2017/07/30 Javascript
Three.js实现绘制字体模型示例代码
2017/09/26 Javascript
vue.js通过路由实现经典的三栏布局实例代码
2018/07/08 Javascript
对angularJs中自定义指令replace的属性详解
2018/10/09 Javascript
layui多图上传实现删除功能的例子
2019/09/23 Javascript
使用layui前端框架弹出form表单以及提交的示例
2019/10/25 Javascript
vue+canvas实现移动端手写签名
2020/05/21 Javascript
[01:53]2016完美“圣”典风云人物:Maybe专访
2016/12/05 DOTA
python装饰器decorator介绍
2014/11/21 Python
python+matplotlib演示电偶极子实例代码
2018/01/12 Python
Python实现针对给定字符串寻找最长非重复子串的方法
2018/04/21 Python
Python实现的旋转数组功能算法示例
2019/02/23 Python
python自动发微信监控报警
2019/09/06 Python
python 实现从高分辨图像上抠取图像块
2020/01/02 Python
python随机模块random的22种函数(小结)
2020/05/15 Python
世界上最大的各式箱包网络零售店:eBag
2016/07/21 全球购物
绘画设计学生的个人自我评价
2013/09/20 职场文书
视光学专业毕业生推荐信
2013/10/28 职场文书
美国探亲签证邀请信
2014/02/05 职场文书
端午节演讲稿
2014/05/23 职场文书
授权委托书协议书
2014/10/16 职场文书
乡镇保密工作承诺书
2015/05/04 职场文书
vue使用localStorage持久性存储实现评论列表
2022/04/14 Vue.js
Django中celery的使用项目实例
2022/07/07 Python