手机版

在Node.js中创建和管理外部进程的详细说明

时间:2021-10-07 来源:互联网 编辑:宝哥软件园 浏览:

Node旨在高效地处理I/O操作,但是您应该知道某些类型的程序不适合这种模式。例如,如果您计划使用节点来处理CPU密集型任务,您可能会阻塞事件循环,从而降低程序的响应。另一种方法是将CPU密集型任务分配给单独的进程,从而释放事件循环。节点允许您生成一个进程,并将这个新进程用作其父进程的子进程。在Node中,子进程可以与父进程双向通信,在某种程度上,父进程也可以对子进程进行监控和管理。

需要使用子流程的另一种情况是,您希望简单地执行一个外部命令,并让Node获得该命令的返回值。例如,您可以执行UNIX命令、脚本或其他不能在节点中直接执行的命令。

本章将向您展示如何执行外部命令、创建子流程并与之通信,以及终止子流程。关键是让你知道如何在Node流程之外完成一系列任务。执行外部命令。

当需要执行外部shell命令或可执行文件时,可以使用child_process模块,这样导入:复制代码如下: varchild _ process=REQUIRE(' child _ process '),然后可以使用模块中的exec函数执行外部命令:复制代码如下: varcexec=child _ process . exec;

exec(命令,回调);exec的第一个参数是要执行的shell命令字符串,第二个参数是回调函数。当exec执行外部命令或发生错误时,将调用此回调函数。回调有三个参数:error、stdout、stderr。参见下面的例子:复制代码如下:Exec ('ls ',函数(err,stdout,stderr) {。

//译者注:如果使用windows,可以改成windows命令,比如dir,后面就不详细介绍了。

});

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

清单8-1展示了一个更复杂的执行外部命令的例子。

清单8-1:执行外部命令。

复制的代码如下://导入执行函数var exec=require ('child_process ')。子进程模块的exec//叫“猫*”。js | wc -l" command exec ('cat * ')js | WCl ',函数(err,stdout,stderr ){ //第四行//如果(err){//外部进程未能启动console . log(' child _ process exit)则命令退出或调用失败。返回;}}

在第四行,我们传递“cat *”。js | wc -l”作为exec的第一个参数,您可以尝试任何其他命令,只要您在shell中使用过它。

然后取一个回调函数作为第二个参数,当出现错误或者子过程终止时,将调用这个参数。

您也可以在回调函数之前传递第三个可选参数,其中包含一些配置选项,例如:复制如下代码: var EXEC=REQUIRE(' child _ process ')。EXEC

var options={ timeout: 1000,kill signal : ' SIGKILL ' };

exec('cat * ')js | WCl ',选项,函数(err,stdout,stderr){//…});

可以使用的参数有:

1.cwd ——当前目录,可以指定当前工作目录。2 .编码——子流程输出内容的编码格式。默认值是utf8,这是UTF-8编码。如果子流程的输出不是utf8,您可以使用此参数进行设置。支持的编码格式为:复制代码:asciiutf8ucs2base64。

如果您想了解Node支持的这些编码格式,请参考第4章“使用缓冲区处理、编码和解码二进制数据”。

1.timeout ——以毫秒为单位的命令执行超时默认为0,即没有限制,会一直等到子进程结束。2.maxBuffer ——指定stdout流和stderr流允许输出的最大字节数。如果达到最大数量,子进程将被终止。默认值为200*1024。3.killSignal ——是超时或输出缓冲区达到最大值时发送给子进程的终止信号。默认值为“SIGTERM”,它将向子进程发送终止信号。这通常用于有序地结束该过程。当使用SIGTERM信号时,进程也可以在收到信号后处理或重写信号处理器的默认行为。如果目标进程需要它,您可以同时向它发送其他信号(如SIGUSR1)。您也可以选择发送SIGKILL信号,该信号将由操作系统处理,并强制立即结束子流程,这样就不会执行子流程的任何清理操作。

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

1.evn ——指定传递给子进程的环境变量,默认为null,这意味着子进程将继承所有父进程在创建之前的环境变量。

注意:使用killSignal选项,您可以以字符串的形式向目标进程发送信号。信号以字符串的形式存在于节点中。以下是UNIX信号和相应默认操作的列表:

您可能希望为子进程提供一组可扩展的父环境变量。如果直接修改process.env对象,会改变Node进程中所有模块的环境变量,会带来很多麻烦。另一种方法是创建一个新对象,并复制process.env中的所有参数,如示例8-2所示:

清单8-2:使用参数化的环境变量来执行命令(源代码:第8/02章_ env _ vars _ enclosure . js)。

复制代码如下: varenv=process.env,varname,envcopy={ },exec=require ('child _ access ')。执行董事;//将process.env复制到envcopy for(ev中的va名称){ envcopy[varname]=env[varname];}

//设置一些自定义变量envcopy ['custom envvar1']='一些值';envCopy['CUSTOM ENV VAR2']='一些其他值';

//使用process.env和自定义变量执行命令exec(' lsla ',{env3360envcopy},函数(err,stdout,stderr){ if(err){ throw err;} console.log('stdout: ',stdout);console.log('stderr: ',stderr);}

在上面的例子中,创建了一个envCopy变量来保存环境变量。首先,它从process.env复制Node进程的环境变量,然后添加或替换一些需要修改的环境变量。最后,它将envCopy作为环境变量参数传递给exec函数,并执行外部命令。

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

在下面的示例中,将在同一个目录中创建两个Node脚本:parent.js和child.js第一个脚本将调用第二个脚本。让我们在下面创建这两个文件:

清单8-3:父进程设置环境变量。

复制的代码如下: var EXEC=REQUIRE(' child _ process ')。EXEC

exec('node child.js ',{env: {number: 123}},函数(err,stdout,stderr){ 0

if(err){ throw err;}

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

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

});

将这段代码保存到parent.js下面是子流程的源代码。将它们保存到child.js中(参见示例8-4)。

示例8-4:子进程解析环境变量(第8/04章_ environment _ number _ child.js)。

复制代码如下: var number=process . env . number;

console . log(type of(number));//“字符串”

number=parseInt(数字,10);

console . log(type of(number));//“数字”

当您将此文件保存为child.js时,可以在此目录中运行以下命令:

复制代码如下: $ nodeparent.js。

您将看到以下输出:

复制代码如下:sdtou:

线

数字

stderr:

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

生成子进程。

如您所见,您可以使用child_process.exec()函数启动一个外部进程,并在进程结束时调用回调函数,这种方法使用起来非常简单,但也有一些缺点:

1.除了使用命令行参数和环境变量之外,不能使用exec()与子进程通信。2.子流程的输出是缓存的,所以您不能流式传输它,它可能会耗尽内存。

幸运的是,Node的子进程模块允许对子进程的启动、停止和其他常规操作进行更细粒度的控制。您可以在应用程序中启动一个新的子流程。节点提供了一个双向通信通道,允许父进程和子进程发送和接收字符串数据。父进程还可以对子进程进行一些管理操作,向子进程发送信号,并强制关闭子进程。

创建子流程。

您可以使用child_process .产卵函数创建一个新的子进程,如示例8-5:所示。

示例8-5:生成子流程。(第8/05章_产卵_child.js)复制代码如下://导入child_process模块的产卵函数。

var spawn=required(' child _ process ')。产卵;

//生成用于执行命令“tail -f /var/log/system.log”的子进程。

var child=产卵(' tail ',['-f ','/var/log/system . log ']);

上面的代码生成了一个执行tail命令的子过程,并以“-f”和“/bar/log/system.log”作为参数。tail命令将监视/var/log/system.og文件(如果存在的话),然后将所有附加的新数据输出到stdout标准输出流。产卵函数返回一个ChildProcess对象,它是一个指针对象,封装了真实进程的访问接口。在这个例子中,我们将这个新的描述符分配给一个名为child的变量。

监听来自子进程的数据。

任何包含stdout属性的子流程句柄都将把stdout(子流程的标准输出)作为一个流对象。您可以将数据事件绑定到这个流对象,这样每当有数据块可用时,就会调用相应的回调函数。请参见以下示例:按如下方式复制代码://将子流程的输出打印到控制台。

child.stdout.on('data ',function(data){ 0

console.log('tail output: '数据);

});

每当子进程向标准输出stdout输出数据时,父进程都会收到通知,并将数据打印到控制台。

除了标准输出,流程还有另一个默认输出流:标准错误流,通常用于输出错误信息。

在本例中,如果/var/log/system.log文件不存在,尾部进程将输出类似如下的消息:“/var/log/system.log:没有这样的文件或目录”,通过侦听stderr流,当出现此错误时,将通知父进程。

父进程可以如下监听标准错误流:复制代码如下:child.stderr.on ('data ',function (data) {。

console.log('尾部错误输出: ',数据);

});

像stdout一样,stderr属性是一个只读流。每当子进程将数据输出到标准错误流中时,父进程都会收到通知并输出数据。

向子进程发送数据。

除了从子进程的输出流中接收数据之外,父进程还可以通过childPoces.stdin属性将数据写入子进程的标准输入中,从而向子进程发送数据和从子进程接收数据。

子进程可以通过process.stdin只读流侦听标准输入数据,但请注意,您必须首先恢复标准输入流,因为默认情况下它处于暂停状态。

示例8-6将创建一个具有以下功能的程序:

1.1应用程序:一个简单的应用程序,可以从标准输入接收整数,将它们相加,然后将相加的结果输出到标准输出流。作为一个简单的计算服务,该应用程序将节点进程模拟为一个可以执行特定任务的外部服务。

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

使用下面示例8-6中的代码创建一个名为plus_one.js的文件:

示例8-6: 1应用程序(第8/06_plus_one.js章)的复制代码如下://还原默认处于挂起状态的标准输入流process . stdin . resume();process.stdin.on('data ',function(data){ var number;尝试{//将输入数据解析为整数number=par sent(data . tostring(),10);//1个数=1;//输出结果process . stdout . write(number ' \ n ');} catch(err){ process . stderr . write(err . message ' \ n ');}});

在上面的代码中,我们等待来自stdin标准输入流的数据。只要数据可用,我们就假设它是一个整数,并将其解析为一个整数变量,然后加上1,将结果输出到标准输出流。

您可以使用以下命令运行该程序:复制代码如下: $ nodeplus _ one.js。

运行后,程序开始等待输入。如果输入一个整数并按回车键,屏幕上将显示一个加了1的数字。

您可以按下Ctrl-C退出程序.

测试客户。

现在,您想要创建一个Node进程来使用之前的“1应用程序”提供的计算服务。

首先,创建一个名为plus _ one _ test.js的文件。

示例8-7:测试1的应用(第8/07章_plus_one_test.js)。

复制的代码如下: var spawn=require(' child _ process ')。产卵;//生成执行1个应用程序varchild=spawn ('node ',['plus _ one.js'])的子流程;//每秒调用函数set interval(function(){//创建一个随机数small erthan 10.000 var number=math . floor(math . random()* 10000);//将该号码发送给子进程: child.stdin.write(号码' \ n ');//获取子进程的响应并打印出来: child.stdout.once('data ',function(data){ console . log(' child用: '数据回复了' number ');});}, 1000);child.stderr.on('data ',function(data){ process . stdout . write(data);});

从第一行到第四行,启动运行“1应用”的子流程,然后使用setInterval函数每秒执行一次以下操作:

1.创建一个小于10000的新随机数。2.将此数字作为字符串传递给子流程。3.等待子进程回复一个字符串。4.因为您一次只想接收一个数字的计算结果,所以您需要使用child.stdout.once而不是child.stdout.on.如果使用后者,每1秒就会注册一个数据事件的回调函数,当子流程的stdout接收到数据时,每个注册的回调函数都会被执行,这样就会发现同样的计算结果会输出很多次,这显然是错误的。

当子进程退出时接收通知。

当子进程退出时,会触发退出事件。示例8-8展示了如何聆听它:

示例8-8:监控子进程的退出事件(第8/09章_ listen _ child _ exit.js)。

复制代码如下: var spawn=require(' child _ process ')。产卵;//生成子进程来执行' ls -la '命令var child=spawn('ls ',['-la ']);child.stdout.on('data ',function(data){ console . log(' data from child : ' data);});

//当子进程退出时:strongchild.on ('exit '),function (code) {console.log('子进程以code' code结尾);});/strong

最后几行黑色代码,父进程使用子进程的exit事件来监视其exit事件,当事件发生时,控制台显示相应的输出。子进程的退出代码作为第一个参数传递给回调函数。有些程序使用非零退出代码来表示失败状态。例如,如果您尝试执行命令“ls -al click filename.txt”,但该文件在当前目录中不存在,您将获得一个值为1的退出代码,如示例8-9所示:

示例8-9:获取子进程的退出代码(第8/10章_child_exit_code.js)并复制代码如下: var spawn=require(' child _ process ')。产卵;//生成子进程并执行' ls does _ not _ exist.txt '命令varchild=spawn ('ls ',[' distinct _ exists . txt ']);//当子进程退出child.on ('exit ',function(code){ console . log(' child process '以code' code结尾)时;});

在本例中,exit事件触发了回调函数,并将子进程的退出代码作为第一个参数传递。如果子进程因被信号杀死而异常退出,相应的信号代码将作为第二个参数传递给回调函数,如例8-10所示:

清单8-10:获取子进程的退出信号。

复制代码如下: var spawn=require(' child _ process ')。产卵;//生成子进程并运行' sleep 10 '命令var child=spawn('sleep ',[' 10 ']);setTimeout(函数(){ child . kill();}, 1000);child . on(‘exit’,函数(代码,信号){ if(代码){ console . log(‘child process’以code‘code’结尾);} else if (signal) { console.log('子进程因signal ' signal而终止);}});

在本例中,一个子进程开始执行休眠10秒钟,但是在10秒钟之前,一个SIGKILL信号被发送到该子进程,这将导致以下输出:

复制代码如下:子进程因信号sigterm而终止。

发送信号并终止进程。

在本节中,您将学习如何使用信号来管理子流程。Signal是父进程与子进程通信甚至杀死子进程的简单方式。

不同的信号代码代表不同的含义,信号也有很多,其中最常见的是用来扼杀进程的。如果进程收到不知道如何处理的信号,程序就会异常中断。一些信号由子进程处理,而另一些只能由操作系统处理。

在正常情况下,可以使用child.kill方法向子进程发送信号,默认情况下发送SIGTERM信号:

复制代码如下: var spawn=require(' child _ process ')。产卵;var child=产卵(' sleep ',[' 10 ']);setTimeout(函数(){ child . kill();}, 1000);

您还可以通过传入一个字符串来发送特定的信号,该字符串将该信号标识为kill方法的唯一参数:

复制代码如下: child . kill(' sigusr 2 ');

需要注意的是,虽然这个方法的名字是kill,但是发送的信号并不一定会杀死子进程。如果孩子处理信号,默认的信号行为将被覆盖。用Node编写的子过程可以重写信号处理器的定义,如下所示:

复制代码如下:process.on ('sigusr2 ',function () {console.log('得到sigusr2信号');});

现在,您定义了SIGUSR2信号处理器,当您的进程再次接收到SIGUSR2信号时,它不会被杀死,而是会输出短语“Got a SIGUSR2信号”。使用这种机制,您可以设计一种简单的方式与子流程通信,甚至命令它。虽然没有使用标准输入函数那么丰富,但是这个方法要简单得多。

总结

在本章中,我学习了如何使用child_process.exec方法来执行外部命令。这样,我可以通过定义环境变量而不是使用命令行参数将参数传递给子流程。

我还学习了如何通过调用child_process .产卵方法来生成子进程来调用外部命令。这样,您可以使用输入流和输出流与子进程通信,或者使用信号与子进程通信并杀死它们。

版权声明:在Node.js中创建和管理外部进程的详细说明是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。