~13 دقیقه مطالعه • بروزرسانی ۵ دی ۱۴۰۴
1. ایجاد فرآیند فرزند با spawn()
spawn() فرآیند فرزند را بهصورت ناهمزمان ایجاد میکند و جریانهای stdin, stdout, stderr بین والد و فرزند برقرار میشوند.
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
2. مدیریت ورودی/خروجی
- اگر خروجی مصرف نشود، فرآیند فرزند ممکن است بلوکه شود.
- گزینه
{ stdio: 'ignore' }برای نادیده گرفتن خروجی استفاده میشود.
3. جایگزینهای exec و execFile
exec(): اجرای دستور درون یک shell و بازگرداندن خروجی به callback.execFile(): اجرای مستقیم فایل بدون ایجاد shell.fork(): ایجاد یک فرآیند Node.js جدید با کانال IPC برای ارتباط.- نسخههای همگام مانند
execSync()وexecFileSync()حلقهٔ رویداد را بلوکه میکنند.
4. اجرای فایلهای .bat و .cmd در ویندوز
- در ویندوز، فایلهای
.batو.cmdنیازمند shell هستند. - میتوان از
spawn()با گزینه{ shell: true }یاexec()استفاده کرد.
const { exec } = require('node:child_process');
exec('my.bat', (err, stdout, stderr) => {
if (err) console.error(err);
console.log(stdout);
});
5. استفاده از exec با callback
const { exec } = require('node:child_process');
exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
6. نسخهٔ Promise با util.promisify
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
async function lsExample() {
const { stdout, stderr } = await exec('ls');
console.log('stdout:', stdout);
console.error('stderr:', stderr);
}
lsExample();
7. کنترل با AbortController
گزینهٔ signal اجازه میدهد فرآیند با AbortController متوقف شود.
const { exec } = require('node:child_process');
const controller = new AbortController();
const { signal } = controller;
const child = exec('grep ssh', { signal }, (error) => {
console.error(error); // AbortError
});
controller.abort();
نتیجهگیری
ماژول child_process در Node.js ابزاری قدرتمند برای اجرای دستورات سیستم و مدیریت فرآیندهای فرزند است. با استفاده از روشهای ناهمزمان مانند spawn() و exec() میتوان عملکرد بهینه داشت، در حالی که نسخههای همگام برای اسکریپتهای ساده مناسبتر هستند. همچنین، پشتیبانی از AbortController و IPC ارتباط بین فرآیندها را ایمنتر و انعطافپذیرتر میکند.
1. execFile()
- اجرای مستقیم فایل اجرایی بدون shell.
- کارایی بالاتر نسبت به exec چون shell ایجاد نمیشود.
- گزینهها مشابه exec هستند اما قابلیتهایی مثل I/O redirection و globbing پشتیبانی نمیشوند.
const { execFile } = require('node:child_process');
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) throw error;
console.log(stdout);
});
نسخهٔ Promise با util.promisify نیز پشتیبانی میشود.
2. fork()
- نسخهٔ ویژهای از spawn برای ایجاد فرآیند Node.js جدید.
- کانال ارتباطی IPC برای ارسال پیام بین والد و فرزند فراهم میکند.
- هر فرآیند Node.js فرزند مستقل از والد است و حافظه و Isolate جداگانه دارد.
const { fork } = require('node:child_process');
const child = fork('child.js');
child.on('message', (msg) => {
console.log('Message from child:', msg);
});
child.send({ hello: 'world' });
3. spawn()
- ایجاد فرآیند جدید با دستور و آرگومانها.
- گزینهها شامل cwd، env، stdio و غیره هستند.
- در صورت فعال بودن گزینه shell، باید ورودی کاربر پاکسازی شود تا از اجرای دستورات ناخواسته جلوگیری شود.
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
4. مدیریت خطا و AbortController
- هر سه تابع از گزینهٔ
signalپشتیبانی میکنند. - با استفاده از
AbortControllerمیتوان فرآیند را متوقف کرد.
const controller = new AbortController();
const { signal } = controller;
const child = spawn('grep', ['ssh'], { signal });
controller.abort(); // توقف فرآیند
نتیجهگیری
توابع execFile، fork و spawn ابزارهای قدرتمندی برای مدیریت فرآیندهای فرزند در Node.js هستند. با استفادهٔ صحیح از این توابع میتوان برنامههای مقیاسپذیر و انعطافپذیر ساخت که از قابلیتهای سیستمعامل و ارتباط بین فرآیندها بهرهمند میشوند.
1. رفتار در ویندوز
- با تنظیم
detached: true، فرآیند فرزند پس از خروج والد همچنان اجرا میشود. - فرآیند فرزند یک پنجرهٔ کنسول مستقل خواهد داشت.
- پس از فعالسازی، این ویژگی قابل غیرفعالسازی نیست.
2. رفتار در سیستمهای شبهیونیکس
- فرآیند فرزند رهبر یک گروه و نشست جدید میشود (
setsid(2)). - فرآیندهای فرزند میتوانند حتی بدون detached نیز پس از خروج والد ادامه دهند.
3. مدیریت انتظار والد
- بهطور پیشفرض، والد منتظر پایان فرآیند فرزند detached میماند.
- برای جلوگیری از این رفتار، باید از
subprocess.unref()استفاده کرد. - این کار باعث میشود حلقهٔ رویداد والد مستقل از فرزند خاتمه یابد.
4. پیکربندی stdio
- برای اجرای فرآیند طولانیمدت در پسزمینه، stdio نباید به والد متصل باشد.
- اگر stdio والد به ارث برسد، فرزند همچنان به ترمینال کنترلکننده متصل خواهد بود.
5. مثالها
اجرای فرآیند طولانیمدت با detached و نادیده گرفتن stdio:
const { spawn } = require('node:child_process');
const subprocess = spawn(process.argv[0], ['child_program.js'], {
detached: true,
stdio: 'ignore',
});
subprocess.unref();
اجرای فرآیند با خروجی در فایل:
const { openSync } = require('node:fs');
const { spawn } = require('node:child_process');
const out = openSync('./out.log', 'a');
const err = openSync('./out.log', 'a');
const subprocess = spawn('prg', [], {
detached: true,
stdio: [ 'ignore', out, err ],
});
subprocess.unref();
نتیجهگیری
گزینهٔ detached در ماژول child_process ابزاری قدرتمند برای اجرای فرآیندهای مستقل و طولانیمدت است. با استفادهٔ صحیح از unref() و پیکربندی مناسب stdio، میتوان فرآیندهایی ایجاد کرد که پس از خروج والد همچنان در پسزمینه اجرا شوند.
1. گزینهٔ stdio
گزینهٔ stdio نحوهٔ اتصال جریانهای ورودی/خروجی بین والد و فرزند را تعیین میکند:
'pipe': ایجاد pipe بین والد و فرزند (پیشفرض).'overlapped': مشابه pipe اما با پرچم FILE_FLAG_OVERLAPPED (ویژهٔ ویندوز).'ignore': اتصال fdها به/dev/nullو نادیده گرفتن آنها.'inherit': استفاده از جریانهای والد (معادل process.stdin, process.stdout, process.stderr).'ipc': ایجاد کانال IPC برای ارسال پیام بین والد و فرزند.<Stream>: اشتراکگذاری یک stream خواندنی/نوشتنی با فرزند.Positive integer: اشتراکگذاری یک fd باز در والد با فرزند.null/undefined: استفاده از مقدار پیشفرض (pipe برای fdهای 0,1,2 و ignore برای بقیه).
const { spawn } = require('node:child_process');
const process = require('node:process');
// Child inherits parent's stdio
spawn('prg', [], { stdio: 'inherit' });
// Child shares only stderr
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });
// Extra fd=4 for custom interaction
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });
2. ارتباط IPC
- اگر کانال IPC ایجاد شود و فرزند یک Node.js instance باشد، متدهای
process.send()وprocess.disconnect()فعال میشوند. - کانال IPC تا زمانی که فرزند handler رویداد ثبت نکند، unref باقی میماند و اجازه میدهد فرزند بهطور طبیعی خاتمه یابد.
3. فرآیندهای همگام
spawnSync(),execSync(),execFileSync()فرآیند را بهصورت همگام اجرا میکنند.- این متدها حلقهٔ رویداد را بلوکه میکنند و برای اسکریپتهای ساده یا بارگذاری پیکربندی مناسب هستند.
4. execFileSync()
- مشابه
execFile()اما همگام. - تا پایان اجرای فرآیند فرزند بازگشت نمیکند.
- در صورت timeout یا exit code غیر صفر، خطا پرتاب میشود.
const { execFileSync } = require('node:child_process');
try {
const stdout = execFileSync('my-script.sh', ['my-arg'], {
stdio: 'pipe',
encoding: 'utf8',
});
console.log(stdout);
} catch (err) {
const { stdout, stderr } = err;
console.error({ stdout, stderr });
}
نتیجهگیری
گزینهٔ stdio انعطافپذیری بالایی در مدیریت ارتباط بین والد و فرزند فراهم میکند، از pipeهای ساده تا کانالهای IPC. همچنین متد execFileSync() اجرای همگام فرآیندها را ممکن میسازد و برای سناریوهایی که نیاز به بلوکهسازی دارند بسیار کاربردی است.
1. execSync()
- اجرای یک دستور درون shell بهصورت همگام.
- تا پایان اجرای فرآیند فرزند بازگشت نمیکند.
- در صورت timeout یا exit code غیر صفر، خطا پرتاب میشود.
- خروجی فرآیند بهصورت
Bufferیاstringبازگردانده میشود.
const { execSync } = require('node:child_process');
try {
const output = execSync('ls -lh /usr', { encoding: 'utf8' });
console.log(output);
} catch (err) {
console.error('Error:', err);
}
2. spawnSync()
- مشابه
spawn()اما همگام. - خروجی شامل pid، stdout، stderr، status و signal است.
- در صورت timeout یا خطا، یک شیء خطا بازگردانده میشود.
const { spawnSync } = require('node:child_process');
const result = spawnSync('ls', ['-lh', '/usr'], { encoding: 'utf8' });
if (result.error) {
console.error('Error:', result.error);
} else {
console.log('stdout:', result.stdout);
console.log('stderr:', result.stderr);
console.log('status:', result.status);
}
3. Class: ChildProcess
- نمایندهٔ یک فرآیند فرزند است.
- از
EventEmitterارثبری میکند. - با متدهای
spawn(),exec(),execFile(),fork()ایجاد میشود.
رویدادهای مهم:
'exit': پس از پایان فرآیند فرزند.'close': پس از بسته شدن جریانهای stdio.'error': هنگام خطا در ایجاد یا اجرای فرآیند.'disconnect': پس از قطع ارتباط IPC.
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process closed with code ${code}`);
});
ls.on('exit', (code) => {
console.log(`child process exited with code ${code}`);
});
ls.on('error', (err) => {
console.error('Failed to start subprocess:', err);
});
نتیجهگیری
متدهای execSync() و spawnSync() ابزارهای قدرتمندی برای اجرای فرآیندهای فرزند بهصورت همگام هستند و برای اسکریپتهای ساده یا بارگذاری پیکربندی در ابتدای برنامه مناسباند. کلاس ChildProcess نیز امکان مدیریت رویدادها و ارتباط با فرآیندهای فرزند را فراهم میکند و پایهای برای ساخت برنامههای مقیاسپذیر و انعطافپذیر محسوب میشود.
1. Event: 'message'
- زمانی فعال میشود که فرآیند فرزند با
process.send()پیامی ارسال کند. - پیام پس از serialization و parsing ممکن است با مقدار اصلی تفاوت داشته باشد.
- با گزینهٔ
serialization: 'advanced'میتوان دادههایی فراتر از JSON ارسال کرد.
2. Event: 'spawn'
- پس از ایجاد موفقیتآمیز فرآیند فرزند فعال میشود.
- اگر فرآیند ایجاد نشود، رویداد
'error'بهجای آن فعال خواهد شد. - این رویداد قبل از دریافت داده از stdout یا stderr رخ میدهد.
3. IPC Channel
subprocess.channel: مرجع کانال IPC بین والد و فرزند.channel.ref(): نگهداشتن حلقهٔ رویداد والد فعال.channel.unref(): اجازهٔ خاتمهٔ والد حتی با کانال باز.
4. Connection Management
subprocess.connected: نشان میدهد آیا ارتباط IPC هنوز برقرار است.subprocess.disconnect(): بستن کانال IPC و خاتمهٔ ارتباط.
5. Process Control
subprocess.exitCode: کد خروج فرآیند فرزند (یا null اگر هنوز فعال باشد).subprocess.kill([signal]): ارسال سیگنال به فرآیند فرزند (پیشفرض SIGTERM).subprocess.killed: نشان میدهد آیا سیگنال با موفقیت ارسال شده است.subprocess.pid: شناسهٔ فرآیند فرزند.
const { spawn } = require('node:child_process');
const grep = spawn('grep', ['ssh']);
console.log(`Spawned child pid: ${grep.pid}`);
grep.kill('SIGHUP');
6. Reference Management
subprocess.unref(): حذف وابستگی از حلقهٔ رویداد والد.subprocess.ref(): بازگرداندن وابستگی و مجبور کردن والد به انتظار برای فرزند.
const { spawn } = require('node:child_process');
const subprocess = spawn(process.argv[0], ['child_program.js'], {
detached: true,
stdio: 'ignore',
});
subprocess.unref();
subprocess.ref();
نتیجهگیری
کلاس ChildProcess در Node.js مجموعهای از رویدادها و ویژگیها برای مدیریت فرآیندهای فرزند ارائه میدهد. با استفاده از رویدادهایی مانند 'message' و 'spawn' و متدهایی مانند kill() و disconnect()، توسعهدهندگان میتوانند کنترل کامل بر فرآیندهای فرزند داشته باشند و ارتباطات IPC را بهصورت ایمن مدیریت کنند.
1. subprocess.send()
- ارسال پیام از والد به فرزند از طریق IPC.
- پیامها پس از serialization و parsing منتقل میشوند.
- در صورت استفاده از
serialization: 'advanced'میتوان دادههایی فراتر از JSON ارسال کرد. - بازگشت
falseنشاندهندهٔ بسته بودن کانال یا پر بودن صف پیامهاست.
// Parent script
const { fork } = require('node:child_process');
const forked = fork(__dirname + '/sub.js');
forked.on('message', (msg) => {
console.log('PARENT got message:', msg);
});
forked.send({ hello: 'world' });
// Child script (sub.js)
process.on('message', (msg) => {
console.log('CHILD got message:', msg);
});
process.send({ foo: 'bar', baz: NaN });
2. ارسال server یا socket
- با استفاده از
sendHandleمیتوان یک TCP server یا socket را به فرزند منتقل کرد. - فرزند میتواند اتصالها را مدیریت کند.
// Parent
const { fork } = require('node:child_process');
const { createServer } = require('node:net');
const child = fork('subprocess.js');
const server = createServer();
server.listen(1337, () => {
child.send('server', server);
});
// Child
process.on('message', (m, server) => {
if (m === 'server') {
server.on('connection', (socket) => {
socket.end('handled by child');
});
}
});
3. ویژگیهای مرتبط
subprocess.signalCode: سیگنال دریافتی توسط فرزند (یا null).subprocess.spawnargs: آرگومانهای کامل اجرای فرآیند.subprocess.spawnfile: نام فایل اجرایی فرآیند فرزند.
4. نکات مهم
- پیامهای با پیشوند
NODE_برای استفاده داخلی Node.js رزرو شدهاند. - ارسال IPC sockets در ویندوز پشتیبانی نمیشود.
- callback اختیاری پس از ارسال پیام فراخوانی میشود.
نتیجهگیری
متد subprocess.send() و ویژگیهای مرتبط در کلاس ChildProcess ابزارهای قدرتمندی برای ارتباط بین فرآیند والد و فرزند در Node.js هستند. با استفاده از این قابلیتها میتوان پیامها، سرورها و سوکتها را به اشتراک گذاشت و فرآیندهای توزیعشده و مقیاسپذیر ایجاد کرد.
1. subprocess.stderr
- یک
Readable Streamکه نمایندهٔ خروجی خطای فرآیند فرزند است. - اگر stdio[2] به چیزی غیر از 'pipe' تنظیم شده باشد، مقدار آن null خواهد بود.
- معادل
subprocess.stdio[2].
2. subprocess.stdin
- یک
Writable Streamکه ورودی فرآیند فرزند را نشان میدهد. - اگر stdio[0] به چیزی غیر از 'pipe' تنظیم شده باشد، مقدار آن null خواهد بود.
- معادل
subprocess.stdio[0].
3. subprocess.stdout
- یک
Readable Streamکه خروجی استاندارد فرآیند فرزند را نشان میدهد. - اگر stdio[1] به چیزی غیر از 'pipe' تنظیم شده باشد، مقدار آن null خواهد بود.
- معادل
subprocess.stdio[1].
const { spawn } = require('node:child_process');
const subprocess = spawn('ls');
subprocess.stdout.on('data', (data) => {
console.log(`Received chunk ${data}`);
});
4. subprocess.stdio
- یک آرایهٔ پراکنده از pipeها که با گزینهٔ stdio هنگام
spawn()تعیین میشود. - مقادیر 0، 1، و 2 بهترتیب معادل stdin، stdout، و stderr هستند.
const subprocess = child_process.spawn('ls', {
stdio: [
0, // Use parent's stdin
'pipe', // Pipe stdout
fs.openSync('err.out', 'w'), // Redirect stderr to file
],
});
5. subprocess.unref()
- به والد اجازه میدهد بدون انتظار برای پایان فرزند خاتمه یابد.
- حلقهٔ رویداد والد وابستگی به فرزند را از دست میدهد.
const subprocess = spawn(process.argv[0], ['child_program.js'], {
detached: true,
stdio: 'ignore',
});
subprocess.unref();
6. maxBuffer و Unicode
- حداکثر تعداد بایت مجاز در stdout یا stderr.
- اگر خروجی بیش از این مقدار باشد، فرآیند فرزند خاتمه مییابد.
- تأثیرگذار بر خروجیهایی با کاراکترهای چندبایتی مانند UTF-8.
7. Advanced Serialization
- افزوده شده در نسخههای v13.2.0 و v12.16.0.
- بر اساس الگوریتم HTML structured clone.
- پشتیبانی از انواع دادهٔ پیشرفته مانند BigInt، Map، Set، ArrayBuffer، Buffer، Error، RegExp.
- نیازمند فعالسازی با گزینهٔ
serialization: 'advanced'.
نتیجهگیری
ویژگیهای stdin, stdout, stderr و آرایهٔ stdio ابزارهای اصلی برای مدیریت ارتباط بین والد و فرزند در Node.js هستند. با استفاده از unref()، maxBuffer و قابلیت serialization پیشرفته، توسعهدهندگان میتوانند کنترل کامل بر رفتار فرآیندهای فرزند داشته باشند و برنامههای مقیاسپذیر و انعطافپذیر ایجاد کنند.
نوشته و پژوهش شده توسط دکتر شاهین صیامی