zoukankan      html  css  js  c++  java
  • [转载]进程通信模块child_process——nodejs中间件系列

    从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

    关于作者

    • 张丹(Conan), 创业者,程序员(Java,R,Javascript/Nodejs)
    • weibo:@Conan_Z
    • blog: http://blog.fens.me
    • email: bsspirit@gmail.com

    转载请注明出处:
    http://blog.fens.me/nodejs-child-process/

    child_process

    前言

    Node.js是一种单线程的编程模型,对Node.js的赞美和诟病的也都是因为它的单线程模型,所有的任务都在一个线程中完成(I/O等例外)。单线程模型,不仅让代码非常简洁,更是直接避免了线程调度的复杂性;同样也是因为单线程,让CPU密集型计算应用,完全不适用。

    在Node.js的内核中,给了我们一种新的选择,通过child_process模块创建新进程,从而实现多核并行计算。

    目录

    1. child_process介绍
    2. child_process的基本使用:spawn, exec, execFile, fork

    1. child_process介绍

    child_process是Node.js的一个十分重要的模块,通过它可以实现创建多进程,以利用单机的多核计算资源。虽然,Nodejs天生是单线程单进程的,但是有了child_process模块,可以在程序中直接创建子进程,并使用主进程和子进程之间实现通信,等到子进程运行结束以后,主进程再用回调函数读取子进程的运行结果。

    本文仅从使用上对child_process模块进行介绍,对于深入的内容请参考官方文档,https://nodejs.org/api/child_process.html

    关于Node.js线程不错的文章:https://cnodejs.org/topic/518b679763e9f8a5424406e9

    2. child_process的基本使用

    child_process模块,在v0.12.0版本主要包括4个异步进程函数(spawn,exec,execFile,fork)和3个同步进程函数(spawnSync,execFileSync,execSync)。以异步函数中spawn是最基本的创建子进程的函数,其他三个异步函数都是对spawn不同程度的封装。spawn只能运行指定的程序,参数需要在列表中给出,而exec可以直接运行复杂的命令。

    比如要运行 du -sh /disk1 命令, 使用spawn函数需要写成spawn(‘du ‘, [‘-sh ‘, ‘/disk1’]),而使用exec函数时,可以直接写成exec(‘du -sh /disk1’)。exec是会先进行Shell语法解析,因此用exec函数可以更方便的使用复杂的Shell命令,包括管道、重定向等。下面我们就针对每个异步函数,进行测试一下。

    系统环境

    • Linux Ubuntu 14.04.1 LTS 64bit
    • Nodejs:v0.13.0-pre
    • Npm:1.4.28

    创建项目

    1 ~ cd /disk1/demo
    2 ~ mkdir nodejs-childprocess && cd nodejs-childprocess

    2.1 spawn函数

    spawn从定义来看,有3个参数。

    1 child_process.spawn(command[, args][, options])
    • command: 只执行的命令
    • args: 参数列表,可输入多的参数
    • options: 环境变量对象

    其中环境变量对象包括7个属性:

    • cwd: 子进程的当前工作目录
    • env: 环境变量键值对
    • stdio: 子进程 stdio 配置
    • customFds: 作为子进程 stdio 使用的文件标示符
    • detached: 进程组的主控制
    • uid: 用户进程的ID.
    • gid: 进程组的ID.

    首先,我们运行一下,上文提到的du的命令。直接在命令行,运行的结果。

    1 ~ du -sh /disk1
    2 582M    /disk1

    新建文件spawn.js

     1 ~ vi spawn.js
     2 
     3 var child = require('child_process');
     4 var du = child.spawn('du', ['-sh', '/disk1']);
     5 du.stdout.on('data', function (data) {
     6     console.log('stdout: ' + data);
     7 });
     8 du.stderr.on('data', function (data) {
     9     console.log('stderr: ' + data);
    10 });
    11 du.on('exit', function (code) {
    12     console.log('child process exited with code ' + code);
    13 });

    通过node运行的结果

    1 ~ node spawn.js
    2 
    3 stdout: 582M    /disk1
    4 child process exited with code 0

    输出的结果是一样的,这样我们就可以很方便地以异步的方式调用系统命令了,spawn是不支持callback函数的,通过stream的方式发数据传给主进程,从而实现了多进程之间的数据交换。

    这个功能的直接用应用场景就是“系统监控”。在Linux下,我们有很多命令行工具,可以实时监控CPU,内存,IO,网络等数据,那么用Node的child_process模块可以很容易地把这些数据采集的我们自己的应用中。

    比如,我们用mpstat命令,监控用户CPU的使用情况。先看看mpstat命令直接使用的效果。

     1 ~ mpstat 1
     2 Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
     3 11:45:56 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
     4 11:45:57 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
     5 11:45:58 AM  all   96.50    0.00    3.25    0.00    0.25    0.00    0.00    0.00    0.00    0.00
     6 11:45:59 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
     7 11:46:00 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
     8 11:46:01 AM  all   96.26    0.00    3.24    0.00    0.25    0.00    0.25    0.00    0.00    0.00
     9 11:46:02 AM  all   96.75    0.00    3.25    0.00    0.00    0.00    0.00    0.00    0.00    0.00
    10 11:46:03 AM  all   96.51    0.00    3.24    0.00    0.25    0.00    0.00    0.00    0.00    0.00
    11 ^C
    12 Average:     all   96.50    0.00    3.35    0.00    0.11    0.00    0.04    0.00    0.00    0.00

    我们新建文件mpstat.js,读取mpstat命令的数据,然后只输出%usr的数据。

     1 ~ vi mpstat.js
     2 
     3 var child = require('child_process');
     4 var mpstat = child.spawn('mpstat', ['1']);
     5 
     6 //Linux 3.13.0-32-generic (ape3)  03/20/2015      _x86_64_        (4 CPU)
     7 //11:27:12 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
     8 //11:27:13 AM  all   96.50    0.00    3.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00
     9 
    10 var line = 0;
    11 var cols = ["time","day","CPU","%usr","%nice","%sys","%iowait","%irq","%soft","%steal","%guest","%gnice","%idle"];
    12 mpstat.stdout.on('data', function (data) {
    13     var str = data.toString();
    14     if(line > 2) {
    15         var arr = str.split(/s+/);
    16         console.log(arr[0]+" "+cols[3]+" "+arr[3]);
    17     }else{
    18        line++;
    19     }
    20 });
    21 mpstat.stderr.on('data', function (data) {
    22     console.log('stderr: ' + data);
    23 });
    24 mpstat.on('exit', function (code) {
    25     console.log('child process exited with code ' + code);
    26 });

    运行程序:

    1 ~ node mpstat.js
    2 11:47:57 %usr 96.75
    3 11:47:58 %usr 96.52
    4 11:47:59 %usr 96.75
    5 11:48:00 %usr 96.25
    6 11:48:01 %usr 96.74
    7 11:48:02 %usr 96.51
    8 11:48:03 %usr 96.74
    9 11:48:04 %usr 96.51

    这样就完成系统数据的采集,我们可以把采集到的数据存储到数据库中,通过websocket等协议直接输出的浏览器前端进行数据的展示。监控系统的例子,可以参考我之前写过的一篇文章:websocket服务器监控

    2.2 exec函数

    exec函数是对spawn的一种友好封装,增加Shell命令解析,可以直接嵌入复杂的命令,比如,管道用法 cat spawn.js exec.js | wc。

    我先试用wc命令来统计一下当前目录的文件字数,分别对应3列为 字节数、字数、行数。

    1 ~ wc *
    2    9   29  275 exec.js
    3   25   95  878 mpstat.js
    4   16   41  343 spawn.js
    5   50  165 1496 total
    6 
    7 ~ cat *.js | wc
    8      50     165    1496

    接下来,我们使用exec函数,来使用Linux管道命令。

     1 ~ vi exec.js
     2 
     3 var childProcess = require('child_process');
     4 
     5 var ls = childProcess.exec('cat *.js | wc', function (error, stdout, stderr) {
     6    if (error) {
     7      console.log(error.stack);
     8      console.log('Error code: '+error.code);
     9    }
    10    console.log('Child Process STDOUT: '+stdout);
    11 });

    运行程序:

    1 ~ node exec.js
    2 Child Process STDOUT:      50     165    1496

    输出结果与Linux命令类似,而且用exec时,命令可以写成一个完整的字符串了,不用像spawn函数时分开写成多个参数数组的形式。最后通过一个callback函数来返回,更符合JavaScript的函数调用习惯,通常情况我们可以用exec来替换spawn的使用。

    2.3 execFile函数

    execFile函数会直接执行特定的程序,参数作为数组传入,不会被bash解释,因此具有较高的安全性。execFile与spawn的参数相似,也需要分别指定执行的命令和参数,但可以接受一个回调函数,与exec的回调函数相同。

    我们看一个execFile函数的例子。

    1 ~ vi execFile.js
    2 
    3 var childProcess = require('child_process');
    4 var path = ".";
    5 childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    6     console.log(result)
    7 });

    运行程序

    1 ~ node execFile.js
    2 total 16
    3 -rw-r--r-- 1 root root 527 Mar 20 13:23 execFile.js
    4 -rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
    5 -rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
    6 -rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js

    那么,什么时候使用exec,什么时候使用execFile呢?

    如果命令参数是由用户来输入的,对于exec函数来说是有安全性风险的,因为Shell会运行多行命令,比如’ls -l .;pwd,如逗号分隔,之后的命令也会被系统运行。但使用exeFile命令时,命令和参数分来,防止了参数注入的安全风险。

    我们用程序测试一下。

     1 ~ vi execFile.js
     2 
     3 // exec
     4 var cmd = 'ls -l .;pwd'
     5 var ls = childProcess.exec(cmd, function (error, stdout, stderr) {
     6    if (error) {
     7      console.log(error.stack);
     8      console.log('Error code: '+error.code);
     9    }
    10    console.log('Child Process STDOUT: '+stdout);
    11 });
    12 
    13 // execFile
    14 var path = ".;pwd";
    15 childProcess.execFile('/bin/ls', ['-l', path], function (err, result) {
    16     console.log(result)
    17 });

    运行程序

     1 ~  node execFile.js
     2 
     3 { [Error: Command failed: /bin/ls -l .;pwd
     4 /bin/ls: cannot access .;pwd: No such file or directory
     5 ] killed: false, code: 2, signal: null, cmd: '/bin/ls -l .;pwd' }
     6 
     7 Child Process STDOUT: total 16
     8 -rw-r--r-- 1 root root 547 Mar 20 13:31 execFile.js
     9 -rw-r--r-- 1 root root 275 Mar 20 13:11 exec.js
    10 -rw-r--r-- 1 root root 878 Mar 20 11:53 mpstat.js
    11 -rw-r--r-- 1 root root 343 Mar 20 11:11 spawn.js
    12 /disk1/demo/nodejs-childprocess

    从输出结果看到,exec函数被正常执行,而execFile函数,则提示参数错误。

    2.4 fork

    fork函数,用于在子进程中运行的模块,如 fork(‘./son.js’) 相当于 spawn(‘node’, [‘./son.js’]) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。

    我们一个主进程和子进程通信的例子,主进程文件main.js和子进程文件son.js。

    新建主进程文件。

    1 ~ main.js
    2 
    3 var childProcess = require('child_process');
    4 var n = childProcess.fork('./son.js');
    5 
    6 n.on('message', function(m) {
    7   console.log('Main Listen: ', m);
    8 });
    9 n.send({ hello: 'son' });

    新建子进程文件。

    1 ~ vi son.js
    2 
    3 process.on('message', function(m) {
    4   console.log('Son Listen:', m);
    5 });
    6 process.send({ Hello: 'conan' });

    运行程序:

    1 ~ node main.js
    2 Main Listen:  { Hello: 'conan' }
    3 Son Listen: { hello: 'son' }

    通过main.js启动子进程son.js,通过process在两个进程之间传递数据。我们对系统进程进行检查,看看是否确实有两个进程。

    1 ~ ps -aux|grep node
    2 root     22777  0.2  0.1 603240 13252 pts/3    Sl+  14:25   0:00 node main.js
    3 root     22782  0.2  0.1 603540 13516 pts/3    Sl+  14:25   0:00 /usr/local/bin/node ./son.js

    有2个node进程分别是node main.js和node ./son.js。

    掌握了进程之间的通信,我们可以做的事情就比较多了,比如自己做一个Node.js多进程管理器,调度器之类。相对于Java的多线程管理或者进程调度来说,Node程序是如此简单明了的。我已经非常明显的感觉到了编程语言在进步!!

    转载请注明出处:
    http://blog.fens.me/nodejs-child-process

  • 相关阅读:
    docker安装nginx
    docker部署SpringBoot项目
    Springboot项目mysql迁移达梦数据库
    导入maven仓库中没有的jar包
    使用static binaries离线安装docker
    国产化项目Debian系Linux离线安装docker
    Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
    http文件上传方式
    解决 Ubuntu 下 ssh 服务器中文显示乱码
    make[9]: *** No rule to make target `radiomodule.dd', needed by `libradiomodule'. Stop.
  • 原文地址:https://www.cnblogs.com/sunws/p/4783327.html
Copyright © 2011-2022 走看看