创建和管理Node.js外部过程

节点的设计能够有效地处理我的I/O操作,但是你应该知道程序的一些类型是不适合这种模式。例如,如果你要处理一个Node CPU密集型任务,你可能会阻塞事件循环,从而降低程序的响应。另一种方法是分配CPU密集型任务释放事件循环的一个单独的进程。节点可以产生过程做这个新的过程作为它的父进程的子进程,节点,子进程与父进程可以双向交流,并在一定程度上,父进程可以同时监控和管理过程。

另一种情况,需要使用子过程是当你想简单地执行外部命令并得到节点得到的命令返回的值。例如,你可以执行一个UNIX命令,脚本,或任何其他的命令,不能在节点直接执行。

本章将向你展示如何执行外部命令,创建的子进程,沟通,和孩子的过程。关键是要让你知道如何完成一系列外部节点处理任务。

执行外部命令


当你需要执行外部命令或可执行文件,你可以使用child_process模块导入这样:

复制代码代码如下所示:

无功child_process =需要('child_process)



然后,外部命令可以在模块内使用执行函数执行。

复制代码代码如下所示:

var = child_process.exec exec;

执行(命令,回调);



在执行第一个参数是shell命令字符串,你要执行,第二个参数是一个回调函数。回调函数将被调用时,exec执行外部命令或如果有一个错误。回调函数有三个参数:错误,stdout,stderr,看看下面的实例:

复制代码代码如下所示:

exec(' ',功能(呃,标准输出,标准错误){

译者注:如果您使用Windows,可以将其更改为Windows命令,如迪尔,将不会返回。

});



如果出现错误,第一个参数将是错误类的一个实例。如果第一个参数不包含错误,第二参数输出将包含该命令的标准输出,最后一个参数包含命令相关的错误输出。

表8-1展示了一个复杂的外部命令的一个例子

清单8-1:执行外部命令(源代码:八 / 01_external_command js)

复制代码代码如下所示:

功能模块为child_process / /执行

VaR的执行=需要('child_process。exec;

打电话给猫。JS WC L / / |命令

执行()

命令退出或调用失败

如果(错误){

启动/外部进程失败

console.log('child_process退出,错误代码:',错误代码);

返回;

}

}



第四、我们认为猫*。JS WC L exec |作为第一个参数传递,你也可以尝试其他的命令,只要你使用shell命令可以。

然后一个回调函数作为第二个参数,这将被称为发生错误时或者子进程的终止。

还可以在回调函数之前传递第三个可选参数,其中包含一些配置选项,如:

复制代码代码如下所示:

VaR的执行=需要('child_process。exec;

var选项{

超时时间:1000,

killsignal:SIGKILL ' '

};

执行()



});



可以使用的参数是:

1.cwd -当前目录中,您可以指定当前工作目录。

2.encoding -子进程的输出内容的编码格式,默认值是UTF8,即UTF-8编码。如果子进程的输出不是UTF8,你可以设置这个参数,并支持的编码格式:

复制代码代码如下所示:

ASCII码

UTF8

UCS2

Base64



如果您想了解更多关于节点支持的编码格式,请参考第四章,对二进制数据进行缓冲处理、编码和解码。

1.timeout的超时时间进行毫秒,默认为0,即无限,等待子进程的结束。

2.maxbuffer -指定的字节允许stdout流输出和标准错误输出流是最大值,如果达到最大值,子进程将被杀死。默认值是200×1024。

3.killsignal -结束的信号被发送到子进程时超时或输出缓存达到最大值,默认值是SIGTERM,发送结束信号给子进程。这种有序的方式通常是用来结束进程。当sigterm信号使用,工艺可以加工或改写信号处理器的默认行为时收到。如果目标进程的需要,您可以发送其他信号的时候(如SIGUSR1)。您也可以选择发送SIGKILL信号,将处理由操作系统强制终止子进程立即,所以任何清洗操作的子进程将不会执行。

如果你想进一步的控制过程结束时,你可以使用child_process.spawn命令,这将在后面介绍。

1.evn指定传递给子进程的环境变量,默认为空,即子进程将继承所有的父母的所有环境变量才是创造的过程。

注:与killsignal选项,您可以发送信号中的字符串形式的目标的过程。在节点,信号是在一个字符串的形式,然后是UNIX信号和默认操作列表。







你可能需要提供一套可扩展的父进程的环境变量。如果你修改process.env对象直接,你将改变在结过程中的所有模块的环境变量,这将带来很多麻烦。另一种方法是创建一个新的对象,在process.env复制所有的参数,和见例8-2:

清单8-2:执行命令使用参数化的环境变量(源代码:八 / 02_env_vars_augment js)

复制代码代码如下所示:

VaR env = process.env,

VarName,

envcopy = { },

执行=需要('child_prcess)。exec;

/ /复制到envcopy process.env

对于(vaname eV){

envcopy { } { } = env varname varname;

}

设置一些自定义变量

envcopy { } var1的定制环境=有价值;

envcopy { } =定制env VAR2的其他一些价值;

/ /使用process.env和自定义变量来执行命令

exec('香格里拉',{ env:envcopy },功能(呃,标准输出,标准错误){

如果(错误){抛出错误;}

console.log('stdout:',stdout);

console.log('stderr:',stderr);

}



上面的例子中,创建一个环境变量是用来保存envcopy变量,首先复制process.env结过程的环境变量,然后添加或替换一些需要修改环境变量,最后把envcopy作为环境变量参数传递给exec函数执行外部命令。

记住,环境变量是通过进程间的操作系统传递的,所有类型的环境变量值都以字符串的形式到达。例如,如果父进程使用数字123作为环境变量,子进程将以字符串的形式接收到123。

下面的例子将在同一个目录设置了2个节点的脚本:parent.js和child.js。第一个脚本将调用第二个,然后我们将创建这两个文件。

清单8-3:父进程设置环境变量(八/ 03_environment_number_parent js)

复制代码代码如下所示:

VaR的执行=需要('child_process)。exec;

exec('node孩子。JS,{ env:{ 123 } },功能多:(呃,标准输出,标准错误){

如果(错误){抛出错误;}

console.log('stdout:',stdout);

console.log('stderr:',stderr);

});



保存此代码parent.js,子进程的源程序,并将它们保存到child.js(见8-4)。

例8-4:子过程解析环境变量(八/ 04_environment_number_child js)

复制代码代码如下所示:

VaR值= process.env.number;

console.log(typeof(数) / /串)。

数= parseInt(号,10);

console.log(typeof(数) / /数)。



当你保存文件为child.js,你可以运行下面的这个目录中的命令。

复制代码代码如下所示:

parent.js美元结



您将看到以下输出:

复制代码代码如下所示:

Sdtou:

字符串



Stderr:



可以看出,虽然父进程传递数字环境变量,但子进程以字符串形式接收它(参见输出的第二行),在第三行中,将字符串解析为数字。

生孩子的过程

正如你看到的,你可以用child_process.exec()函数启动外部程序,并调用回调函数的过程结束时,这是非常简单的使用,但也有一些缺点。

1。除了使用命令行参数和环境变量外,使用

2的输出。子进程被缓存,所以您不能流它,它可能会耗尽内存。

幸运的是,该节点的child_process模块允许更精细控制的启动、停止、和控制过程等常规操作。你可以在应用一个新的子进程启动。节点提供了一个双向沟通的渠道,允许父进程和子进程之间发送和接收的字符串数据相互。父进程也可以有一些针来管理孩子的过程中,发送信号给子进程,子进程被强制力。

创建子进程

你可以使用child_process.spawn函数创建一个新的子进程,看到8-5:

例8-5:产生一个子进程。(八/ 05_spawning_child js)

复制代码代码如下所示:

功能模块为child_process / /产卵

VaR产卵=需要('child_processSpawn);

尾- F / /生成用于执行 /无功/日志/ system.log命令过程

VaR的孩子=产卵('tail,{F、 / / /系统VaR日志。日志});



上面的代码生成的子进程执行尾指挥用F / / / system.log酒吧日志作为参数。tail命令将监控 / / / system.og VaR的日志文件(如果存在的话),然后输出所有添加新的数据到stdout标准输出流。生成函数返回子处理对象,这是一个指针对象封装了实际过程的访问接口。在这个例子中,我们把这一新的描述符的变量称为孩子。

侦听子进程中的数据

stdout属性包含的任何子进程句柄,将stdout标准输出流对象的过程中,你可以记录流对象中的数据绑定到事件,所以每当有一个数据块是可用的,回调函数会调用相应的,见下面的例子:

复制代码代码如下所示:

将输出打印到控制台进程

Child.stdout.on(数据功能(数据){(数据){

console.log('tail输出:+数据);

});



每次有一个孩子的过程输出数据到标准输出stdout,父进程得到通知和数据打印到控制台。

除了标准输出之外,进程还有另外一个默认输出流:标准错误流,它通常使用此流输出错误信息。

在这种情况下,如果 / / / var日志system.Log里不存在,尾部的过程中会输出以下信息: / / /系统VaR日志。日志:没有这样的文件或目录,通过监测标准错误流,父进程会通知时发生错误。

父进程可以以这种方式监视标准错误流。

复制代码代码如下所示:

Child.stderr.on(数据功能(数据){

console.log('tail错误输出,数据);

});



stderr属性和标准输出,是一个只读的流,在子进程的标准错误输出流的数据,家长会通知,输出数据。

发送数据的过程

除了输出流从接收数据的过程中,父进程也可以通过childpoces.stdin属性为标准输入进程写数据和发送数据从孩子的过程。

子过程可以听标准的输入数据通过process.stdin只读流,但是请注意,你必须先恢复(恢复)的标准输入流,因为它处于暂停状态的默认。

例8-6将创建一个程序,包含以下功能:

1。+ 1应用程序:一个简单的应用程序,它可以从标准输入中接收整数,然后添加,然后在标准输出流之外添加结果。这个应用程序作为一个简单的计算服务,模拟节点进程作为一个外部服务,可以执行特定的任务。

2。测试+ 1应用程序的客户端,发送一个随机整数,然后输出结果,用于演示节点进程如何生成一个子进程,然后让它执行一个特定的任务。

创建一个文件名为plus_one.js与代码的示例8-6:

例8-6:+ 1应用(第8章 / 06_plus_one js)

复制代码代码如下所示:

恢复默认值是标准的输入流挂起状态。

process.stdin.resume();

process.stdin.on(数据功能(数据){

VaR值;

{试

用于整数分析的输入数据。

数= parseInt(data.tostring()(),10);

/ / 1

数字= 1;

输出结果

process.stdout.write(数+ ;

} catch(错误){

process.stderr.write(err.message + ;

}

});



在上面的代码中,我们等待从标准输入流中的数据。每当有可用数据时,它就假定它是一个整数,并将其解析为整数变量,然后添加1,然后将结果输出到标准输出流。

程序可以按下面的命令运行:

复制代码代码如下所示:

plus_one.js美元结



运行之后,程序开始等待输入。如果输入一个整数并按下返回键,您会看到屏幕后面显示了一个1后的数字。

你可以退出程序按Ctrl+C组合键。

测试客户端

现在您必须创建一个节点进程,以使用前面的+ 1应用程序提供的计算服务。

首先,创建一个文件名为plus_one_test.js,随着例8-7内容:

例8-7:测试+ 1应用(第8章 / 07_plus_one_test js)

复制代码代码如下所示:

VaR产卵=需要('child_processSpawn);

生成一个子过程来实现+ 1的应用程序

VaR的孩子=产卵('node,{ 'plus_one .js});

每一个调用中的每秒钟

setInterval(){()函数(

创建一个小于10的随机数

VaR值= math.floor(Math.random)*(10000);

将该号码发送给子进程:

child.stdin.write(数+ ;

从子进程获取响应并打印它:

Child.stdout.once(数据功能(数据){

console.log(儿童回答+数字+:+数据);

});

},1000);

Child.stderr.on(数据功能(数据){

process.stdout.write(数据);

});



从第一到第四,子进程开始运行+ 1的应用程序,然后如下操作进行每个二使用setInterval函数:

一个新的小于10000的随机数是在1中建立的。

2。将这个数字作为字符串传递给子进程

三.等待子进程的回复一个字符串

4。因为你想每次只接收1个数字的结果,所以你需要使用child.stdout.once代替child.stdout.on.if你使用回调函数的每一秒都会登记数据的事件,回调函数会在每个子过程的输出数据注册接收被执行,所以你会发现同样的结果将是许多倍的输出,这种行为显然是错误的。

收到通知时,子进程退出

当进程退出,退出事件将被触发。例8-8展示如何倾听它:

例8-8:听子进程退出事件(八/ 09_listen_child_exit js)

复制代码代码如下所示:

VaR产卵=需要('child_processSpawn);

执行ls - LA命令的生成过程

VaR的孩子=产卵(' ',{香格里拉});

Child.stdout.on(数据功能(数据){

console.log(数据来自孩子:' +数据);

});

当流程退出时:

Child.on('exit功能(代码){

console.log(终止码+代码'过程);

});



最后几行添加了黑色代码。父进程侦听子进程的退出事件。当事件发生时,在控制台上显示相应的输出,子进程的退出代码作为第一个参数传递给回调函数。一些程序使用一个非0的退出代码代表一个国家的失败。例如,如果您尝试执行命令ls u2013 Al点击filename.txt,但当前目录中没有这个文件,你会与一个值1退出代码。见示例8-9:

例8-9:获得子进程的退出码(八/ 10_child_exit_code js)

复制代码代码如下所示:

VaR产卵=需要('child_processSpawn);

/ / 的生成过程,执行ls does_not_exist .txt命令

VaR的孩子=产卵(' ',{ 'does_not_exist .txt});

当流程退出时

Child.on('exit功能(代码){

console.log(终止码+代码'过程);

});



在这个例子中,出口事件触发一个回调函数,并将处理出口代码作为传递给它的第一个参数来处理。如果子进程是由信号杀死引起的异常退出,则相应的信号代码将作为第二个参数传递给回调函数,例如8-10:

清单8-10:获取子进程的退出信号(八/ 11_child_exit_signal js)

复制代码代码如下所示:

VaR产卵=需要('child_processSpawn);

生成过程,运行睡眠10命令

VaR的孩子=产卵(睡眠,{ 10'});

setTimeout(){()函数(

Child.kill();

},1000);

Child.on('exit功能(编码、信号){

如果(代码){

console.log(终止码+代码'过程);

否则如果(信号){ }

console.log(儿童过程终止因信号+信号);

}

});



在这种情况下,我们启动的子进程执行睡眠10秒,但我们发送SIGKILL信号的子过程在10秒以内,这将导致以下输出。

复制代码代码如下所示:

由于子进程终止信号SIGTERM



发送信号并杀死进程

在本节中,您将学习如何使用一个信号控制子进程。信号是家长与孩子沟通的过程中或甚至杀死一个孩子的一种简单方法。

不同的信号码代表不同的含义,有许多信号,其中最常见的是杀死进程,如果一个进程接收到一个不知道该如何处理的信号,程序就会被不正常地中断,一些信号将由子进程处理,有些信号只能由操作系统处理。

In general, you can use the child.kill method to send a signal to a subprocess and send a SIGTERM signal by default:

复制代码代码如下所示:

VaR产卵=需要('child_processSpawn);

VaR的孩子=产卵(睡眠,{ 10'});

setTimeout(){()函数(

Child.kill();

},1000);



还可以通过将一串标识信号传递为杀死方法的唯一参数来发送特定信号。

复制代码代码如下所示:

Child.kill('sigusr2);



需要注意的是,虽然这种方法的名字叫杀重要,传输的信号不一定杀死子进程。如果信号是通过子进程处理,默认的信号行为覆盖。子进程写入节点可以重写一个信号处理器的定义如下:

复制代码代码如下所示:

process.on('sigusr2,函数(){(){

(你有一console.log SIGUSR2信号);

});



现在,你可以定义SIGUSR2信号处理器,当你的程序接收SIGUSR2信号了,它不会被杀死,但输出有SIGUSR2信号。这种机制,你可以设计一个简单的方式来与孩子沟通的过程中甚至命令它。它不是作为标准输入功能丰富,但这是一个简单的多。

总结

在这一章中,我们使用child_process.exec方法执行外部命令。这种方式可以使用命令行参数而不是命令行参数,但是通过定义环境变量将参数传递给子进程。

我们还学会了通过调用child_process.spawn方法生成的子进程调用外部命令的方法。通过这种方式,您可以使用输入流、输出流来与子进程通信,或者与子进程通信,并用信号杀死进程。