ホームページ  >  記事  >  ウェブフロントエンド  >  Node.jsのchild_processモジュールから親プロセスと子プロセス間の通信を学習するサンプルコードの詳細説明

Node.jsのchild_processモジュールから親プロセスと子プロセス間の通信を学習するサンプルコードの詳細説明

黄舟
黄舟オリジナル
2017-03-28 14:51:351493ブラウズ

この記事では、親プロセスと子プロセス間の通信を学ぶために、主にNode.jsのchild_processモジュールを紹介します。興味のある友人はそれを参照できます。

child_process モジュールは、popen(3) と同じメソッドを提供して、自己プロセスを生成します。この関数は、主に child_process.spawn 関数を通じて提供されます。デフォルトでは、Node.js プロセスと子プロセスの間に stdin、stdout、および stderr パイプがすでに存在します。通常、このメソッドはノンブロッキング方法でデータを渡すことができます (一部のプログラムではラインバッファー I/O が使用されることに注意してください)。これは Node.js には影響しないため、子プロセスに渡されたデータはすぐに消費されない可能性があります)

メソッドはプロセスから生成されるため、非同期です。 Node.js の
イベント

ループ はブロックされません。ただし、child-process.spawnSync メソッドは同期であり、生成されたプロセスが終了するか終了するまでのみイベント ループをブロックします。 child_process.exec:シェルクライアントにアクセスし、シェルを使用してプログラムを実行します。完了すると、stdout と stderr をコールバック関数

child_process.execFile: exec に似ていますが、すぐには生成されません。 child_process.fork: 新しい Node.js プロセスを生成し、特定のモジュールを実行して、親プロセスと子プロセスの間でデータを送信するための IPC チャネルを生成します
child_process.execSync: exec と exec の違い child_process の違い。 execFileSync と execFile は Node.js のイベント ループをブロックしますが、子プロセス


の方が便利である可能性があります。ほとんどの場合、同期メソッドはパフォーマンスに大きな影響を与えます。イベントループをブロックします


child_process.spawn()、child_process.fork()、child_process.exec()、および child_process.execFile() はすべて非同期

API

です。各メソッドは ChildProcess インスタンスを生成し、この
オブジェクト

は Node.js の EventEmitter API を実装するため、親プロセスはリッスン関数を登録し、子プロセスの特定のイベントがトリガーされたときに呼び出すことができます。 child_process.exec() および child_process.execFile() は、子プロセスの終了時に呼び出されるオプションのコールバック関数を指定できます。


Windows プラットフォームで .bat と .cmd を実行します: child_process.exec と child_process.execFile の違いはプラットフォームによって異なる場合があります。 Unit/
Linux

/OSX プラットフォームでは、execFile はシェルを生成しないため、より効率的です。 Windows では、.bat/.cmd はターミナルなしでは実行できないため、execFile は使用できません (child_process.spawn も使用できません)。 Windows では、.bat/.cmd は spawn メソッドを使用してシェル オプションを指定するか、child_process.exec を使用するか、cmd.exe を生成して .bat/.cmd ファイルをパラメータとして渡します (child_process.exec はこれを行う)。

const spawn = require('child_process').spawn; 
const ls = spawn('ls', ['-lh', '/usr']); 
ls.stdout.on('data', (data) => { 
 console.log(`stdout: ${data}`); 
}); 
ls.stderr.on('data', (data) => { 
 console.log(`stderr: ${data}`); 
}); 
ls.on('close', (code) => { 
 console.log(`child process exited with code $[code]`); 
});
または、次のメソッドを使用することもできます:
const spawn = require('child_process').spawn; 
const bat = spawn('cmd.exe', ['/c', 'my.bat']);//使用shell方法指定一个shell选项 
bat.stdout.on('data', (data) => { 
 console.log(data); 
}); 
bat.stderr.on('data', (data) => { 
 console.log(data); 
}); 
 
bat.on('exit', (code) => { 
 console.log(`Child exited with code $[code]`); 
});

child_process.exec(command[, options][, callback])

options の maxBuffer パラメータは、stdout/stderr で許可されるデータの最大量を表します。 , データ量を超えると、子プロセスは強制終了されます。デフォルトは 200*1024 ビットです。デフォルトの killSignal は「SIGTERM」です。コールバック関数はプロセスの終了時に呼び出され、パラメータは error、stdout、stderr です。このメソッドは ChildProcess オブジェクトを返します。

const exec = require('child_process').exec;//产生exec,同时传入.bat文件 
exec('my.bat', (err, stdout, stderr) => { 
 if (err) { 
  console.error(err); 
  return; 
 } 
 console.log(stdout); 
});

上記のコードはシェルを生成し、このシェルを使用してコマンドを実行し、結果をキャッシュします。コールバック関数の error.code 属性は子プロセスの終了コードを表し、error.signal はプロセスを終了する信号を表し、0 以外のコードはエラーを表します。デフォルトのオプションパラメータ値は次のとおりです:

const exec = require('child_process').exec; 
const child = exec('cat *.js bad_file | wc -l', 
 (error, stdout, stderr) => { 
  console.log(`stdout: ${stdout}`); 
  console.log(`stderr: ${stderr}`); 
  if (error !== null) { 
   console.log(`exec error: ${error}`); 
  } 
});
タイムアウトが 0 以外の場合、親プロセスはシグナルを送信します。デフォルトは子プロセスを終了する「SIGTERM」です。子プロセスがタイムアウトで指定された時間を超えました。注: POSIX システムでの exec メソッドの呼び出しとは異なり、child_process.exec は現在のスレッドを置き換えませんが、シェルを使用してコマンド

child_process.execFile(file[, args][, options][, callback ]) を実行します。

ここで、 file は実行する必要があるファイルを表します。 child_process.execFile() は exec とよく似ていますが、このメソッドはシェルを生成しません。指定した実行可能ファイルはすぐに新しいスレッドを生成するため、child_process.exec よりも効率が高くなります。

const execFile = require('child_process').execFile; 
const child = execFile('node', ['--version'], (error, stdout, stderr) => { 
 if (error) { 
  throw error; 
 } 
 console.log(stdout); 
});

因为不会产生shell,一些I/O redirection和file globbing这些行为不支持

child_process.fork(modulePath[, args][, options])

其中modulePath表示要在子线程中执行的模块。其中options中的参数silent如果设置为true,那么子进程的stdin, stdout, stderr将会被传递给父进程,如果设置为false那么就会从父进程继承。execArgv默认的值为process.execArgv,execPath表示用于创建子进程的可执行文件。这个fork方法是child_process.spawn的一种特殊用法,用于产生一个Node.js的子进程,和spawn一样返回一个ChildProcess对象。返回的ChildProcess会有一个内置的传输通道用于在子进程和父进程之间传输数据(用ChildProcess的send方法完成)。我们必须注意,产生的Node.js进程和父进程之间是独立的,除了他们之间的IPC传输通道以外。每一个进程有独立的内存,有自己独立的V8引擎。由于产生一个子进程需要其他额外的资源分配,因此产生大量的子进程不被提倡。默认情况下,child_process.fork会使用父进程的process.execPath来产生一个Node.js实例,options中的execPath允许指定一个新的路径。通过指定execPath产生的新的进程和父进程之间通过文件描述符(子进程的环境变量NODE_CHANNEL_FD)来通信。这个文件描述符上的input/output应该是一个JSON对象。和POSIX系统调用fork不一样的是,child_process.fork不会克隆当前的进程

最后,我们来看看子进程和父进程之间是如何通信的:

服务器端的代码:

var http = require('http'); 
var cp = require('child_process'); 
var server = http.createServer(function(req, res) { 
  var child = cp.fork(dirname + '/cal.js'); 
  //每个请求都单独生成一个新的子进程 
  child.on('message', function(m) { 
    res.end(m.result + '\n'); 
  }); 
  //为其指定message事件 
  var input = parseInt(req.url.substring(1)); 
  //和postMessage很类似,不过这里是通过send方法而不是postMessage方法来完成的 
  child.send({input : input}); 
}); 
server.listen(8000);

子进程的代码:

function fib(n) { 
  if (n < 2) { 
    return 1; 
  } else { 
    return fib(n - 2) + fib(n - 1); 
  } 
} 
//接受到send传递过来的参数 
process.on(&#39;message&#39;, function(m) { 
  //console.log(m); 
  //打印{ input: 9 } 
  process.send({result: fib(m.input)}); 
});

child_process.spawn(command[, args][, options])

其中options对象的stdio参数表示子进程的stdio配置;detached表示让子进程在父进程下独立运行,这个行为与平台有关;shell如果设置为true那么就会在shell中执行命令。这个方法通过指定的command来产生一个新的进程,如果第二个参数没有指定args那么默认就是一个空的数组,第三个参数默认是如下对象,这个参数也可以用于指定额外的参数:

{
 cwd: undefined, //产生这个进程的工作目录,默认继承当前的工作目录
 env: process.env//这个参数用于指定对于新的进程可见的环境变量,默认是process.env
}

其中cwd用于指定子进程产生的工作目录,如果没有指定表示的就是当前工作目录。env用于指定新进程的环境变量,默认为process.env。下面的例子展示了使用ls -lh/usr来获取stdout,stderr以及exit code:

const spawn = require(&#39;child_process&#39;).spawn; 
const ls = spawn(&#39;ls&#39;, [&#39;-lh&#39;, &#39;/usr&#39;]); 
ls.stdout.on(&#39;data&#39;, (data) => { 
 console.log(`stdout: ${data}`); 
}); 
ls.stderr.on(&#39;data&#39;, (data) => { 
 console.log(`stderr: ${data}`); 
}); 
ls.on(&#39;close&#39;, (code) => { 
 console.log(`child process exited with code $[code]`); 
});

下面是一个很详细的运行"ps ax|grep ssh"的例子:

const spawn = require(&#39;child_process&#39;).spawn; 
const ps = spawn(&#39;ps&#39;, [&#39;ax&#39;]); 
const grep = spawn(&#39;grep&#39;, [&#39;ssh&#39;]); 
ps.stdout.on(&#39;data&#39;, (data) => { 
 grep.stdin.write(data); 
}); 
ps.stderr.on(&#39;data&#39;, (data) => { 
 console.log(`ps stderr: ${data}`); 
}); 
ps.on(&#39;close&#39;, (code) => { 
 if (code !== 0) { 
  console.log(`ps process exited with code $[code]`); 
 } 
 grep.stdin.end(); 
}); 
grep.stdout.on(&#39;data&#39;, (data) => { 
 console.log(`${data}`); 
}); 
grep.stderr.on(&#39;data&#39;, (data) => { 
 console.log(`grep stderr: ${data}`); 
}); 
grep.on(&#39;close&#39;, (code) => { 
 if (code !== 0) { 
  console.log(`grep process exited with code $[code]`); 
 } 
});

用下面的例子来检查错误的执行程序:

const spawn = require(&#39;child_process&#39;).spawn; 
const child = spawn(&#39;bad_command&#39;); 
child.on(&#39;error&#39;, (err) => { 
 console.log(&#39;Failed to start child process.&#39;); 
});

options.detached:

在windows上,把这个参数设置为true的话,这时候如果父进程退出了那么子进程还会继续运行,而且子进程有自己的console window。如果把子进程设置了这个为true,那么就不能设置为false了。在非window平台上,如果把这个设置为true,子进程就会成为进程组合和session的leader,这时候子进程在父进程退出以后会继续执行,不管子进程是否detached。可以参见setsid(2)。

默认情况下,父进程会等待detached子进程,然后退出。要阻止父进程等待一个指定的子进程可以使用child.unref方法,这个方法会让父进程的事件循环不包括子进程,这时候允许父进程独立退出,除非在父进程和子进程之间有一个IPC通道。下面是一个detach长期运行的进程然后把它的输出导向到一个文件:

const fs = require(&#39;fs&#39;); 
const spawn = require(&#39;child_process&#39;).spawn; 
const out = fs.openSync(&#39;./out.log&#39;, &#39;a&#39;); 
const err = fs.openSync(&#39;./out.log&#39;, &#39;a&#39;); 
const child = spawn(&#39;prg&#39;, [], { 
 detached: true,//依赖于父进程 
 stdio: [ &#39;ignore&#39;, out, err ] 
}); 
child.unref();//允许父进程单独退出,不用等待子进程

当使用了detached选项去产生一个长期执行的进程,这时候如果父进程退出了那么子进程就不会继续执行了,除非指定了一个stdio配置(不和父进程之间有联系)。如果父进程的stdio是继承的,那么子进程依然会和控制终端之间保持关系。

options.stdio

这个选项用于配置父进程和子进程之间的管道。默认情况下,子进程的stdin,stdout,stderr导向了ChildProcess这个对象的child.stdin,child.stdout,child.stderr流,这和设置stdio为['pipe','pipe','pipe']是一样的。stdio可以是下面的任何一个字符串

'pipe':相当于['pipe','pipe','pipe'],为默认选项

'ignore':相当于['ignore','ignore','ignore']

'inherit':相当于[process.stdin,process.stdout,process.stderr]或者[0,1,2]

一般情况下,stdio是一个数组,每一个选项对应于子进程的fd。其中0,1,2分别对应于stdin,stdout,stderr。如果还设置了多于的fds那么就会用于创建父进程和子进程之间的额外的管道,可以是下面的任何一个值:

'pipe':为子进程和父进程之间创建一个管道。父进程管道的末端会作为child_process对象的ChildProcess.stdio[fd]而存在。fds0-2创建的管道在ChildProcess.stdin,ChildProcess.stdout,ChildProcess.stderr也是存在的

'ipc':用于创建IPC通道用于在父进程和子进程之间传输消息或者文件描述符。ChildProcess对象最多有一个IPC stdio文件描述符,使用这个配置可以启用ChildProcess的send方法,如果父进程在文件描述符里面写入了JSON对象,那么ChildProcess.on("message")事件就会在父进程上触发。如果子进程是Node.js进程,那么ipc配置就会启用子进程的process.send(), process.disconnect(), process.on('disconnect'), and process.on('message')方法。

'ignore':让Node.js子进程忽视文件描述符。因为Node.js总是会为子进程开启fds0-2,设置为ignore就会导致Node.js去开启/dev/null,同时把这个值设置到子进程的fd上面。

'strem':和子进程之间共享一个可读或者可写流,比如file,socket,pipe。这个stream的文件描述符和子进程的文件描述符fd是重复的。注意:流必须有自己的文件描述符

正整数:表示父进程的打开的文件描述符。和stream对象可以共享一样,这个文件描述符在父子进程之间也是共享的
null/undefined:使用默认值。stdio的fds0,1,2管道被创建(stdin,stdout,stderr)。对于fd3或者fdn,默认为'ignore'

const spawn = require(&#39;child_process&#39;).spawn; 
// Child will use parent&#39;s stdios 
//使用父进程的stdios 
spawn(&#39;prg&#39;, [], { stdio: &#39;inherit&#39; }); 
//产生一个共享process.stderr的子进程 
// Spawn child sharing only stderr 
spawn(&#39;prg&#39;, [], { stdio: [&#39;pipe&#39;, &#39;pipe&#39;, process.stderr] }); 
// Open an extra fd=4, to interact with programs presenting a 
// startd-style interface. 
spawn(&#39;prg&#39;, [], { stdio: [&#39;pipe&#39;, null, null, null, &#39;pipe&#39;] });

注意:当子进程和父进程之间建立了IPC通道,同时子进程为Node.js进程,这时候开启的具有IPC通道的子进程(使用unref)直到子进程注册了一个disconnect的事件处理句柄process.on('disconnect'),这样就会允许子进程正常退出而不会由于IPC通道的打开而持续运行。

Class: ChildProcess

这个类的实例是一个EventEmitters,用于代表产生的子进程。这个类的实例不能直接创建,必须使用 child_process.spawn(), child_process.exec(), child_process.execFile(), or child_process.fork()来完成

'close'事件:

其中code表示子进程退出的时候的退出码;signal表示终止子进程发出的信号;这个事件当子进程的stdio stream被关闭的时候触发,和exit事件的区别是多个进程可能共享同一个stdio streams!(所以一个进程退出了也就是exit被触发了,这时候close可能不会触发)

'exit'事件:

其中code表示子进程退出的时候的退出码;signal表示终止子进程发出的信号。这个事件当子进程结束的时候触发,如果进程退出了那么code表示进程退出的exit code,否则没有退出就是null。如果进程是由于收到一个信号而终止的,那么signal就是这个信号,是一个string,默认为null。

注意:如果exit事件被触发了,子进程的stdio stream可能还是打开的;Node.js为SUGUBT,SIGTERM创建信号处理器,而且Node.js进程在收到信号的时候不会马上停止。Node.js会进行一系列的清理工作,然后才re-raise handled signal。见waitpid(2)

'disconnect'事件:

在子进程或者父进程中调用ChildProcess.disconnect()方法的时候会触发。这时候就不能再发送和接受信息了,这是ChildProcess.connected就是false了

'error'事件:

当进程无法产生的时候,进程无法杀死的时候,为子进程发送消息失败的时候就会触发。注意:当产生错误的时候exit事件可能会也可能不会触发。如果你同时监听了exit和error事件,那么就要注意是否会无意中多次调用事件处理函数

'message'事件:

message参数表示一个解析后的JSON对象或者初始值;sendHandle可以是一个net.Socket或者net.Server对象或者undefined。当子进程调用process.send时候触发

child.connected:

调用了disconnect方法后就会是false。表示是否可以在父进程和子进程之间发送和接受数据,当值为false就不能发送数据了
 child.disconnect()

关闭子进程和父进程之间的IPC通道,这时候子进程可以正常退出如果没有其他的连接使得他保持活动。这时候父进程的child.connected和子进程的process.connected就会设置为false,这时候不能传输数据了。disconnect事件当进程没有消息接收到的时候被触发,当调用child.disconnect时候会立即触发。注意:当子进程为Node.js实例的时候如child_process.fork,这时候process.disconnect方法就会在子进程中调用然后关闭IPC通道。

child.kill([signal])

为子进程传入消息,如果没有指定参数那么就会发送SIGTERM信号,可以参见signal(7)来查看一系列信号

const spawn = require(&#39;child_process&#39;).spawn; 
const grep = spawn(&#39;grep&#39;, [&#39;ssh&#39;]); 
grep.on(&#39;close&#39;, (code, signal) => { 
 console.log( 
  `child process terminated due to receipt of signal ${signal}`); 
}); 
// Send SIGHUP to process 
grep.kill(&#39;SIGHUP&#39;);

ChildProcess对象在无法传输信号的时候会触发error事件。为一个已经退出的子进程发送信号虽然无法报错但是可能导致无法预料的结果。特别的,如果这个PID已经被分配给另外一个进程那么这时候也会导致无法预料的结果。

child.pid:

返回进程的PID值

const spawn = require(&#39;child_process&#39;).spawn; 
const grep = spawn(&#39;grep&#39;, [&#39;ssh&#39;]); 
console.log(`Spawned child pid: ${grep.pid}`); 
grep.stdin.end();//通过grep.stdin.end结束

child.send(message[, sendHandle][, callback])

当父子进程之间有了IPC通道,child.send就会为子进程发送消息,当子进程为Node.js实例,那么可以用process.on('message')事件接收

父进程为:

const cp = require(&#39;child_process&#39;); 
const n = cp.fork(`${dirname}/sub.js`); 
n.on(&#39;message&#39;, (m) => { 
 console.log(&#39;PARENT got message:&#39;, m); 
}); 
n.send({ hello: &#39;world&#39; });

子进程为:

process.on(&#39;message&#39;, (m) => { 
 console.log(&#39;CHILD got message:&#39;, m); 
}); 
process.send({ foo: &#39;bar&#39; });

子进程使用process.send方法为父进程发送消息。有一个特例,发送{cmd: 'NODE_foo'}。当一个消息在他的cmd属性中包含一个NODE_前缀会被看做使用Node.js核心(被Node.js保留)。这时候不会触发子进程的process.on('message')。而是使用process.on('internalMessage')事件,同时会被Node.js内部消费,一般不要使用这个方法。sendHandle这个用于给子进程传入一个TCP Server或者一个socket,为process.on('message')回调的第二个参数接受。callback当消息已经发送,但是子进程还没有接收到的时候触发,这个函数只有一个参数成功为null否则为Error对象。如果没有指定callback同时消息也不能发送ChildProcess就会触发error事件。当子进程已经退出就会出现这个情况。child.send返回false如果父子进程通道已经关闭,或者积压的没有传输的数据超过一定的限度,否则这个方法返回true。这个callback方法可以用于实现流控制:

下面是发送一个Server的例子:

const child = require(&#39;child_process&#39;).fork(&#39;child.js&#39;); 
// Open up the server object and send the handle. 
const server = require(&#39;net&#39;).createServer(); 
server.on(&#39;connection&#39;, (socket) => { 
 socket.end(&#39;handled by parent&#39;); 
}); 
server.listen(1337, () => { 
 child.send(&#39;server&#39;, server); 
});

子进程接受这个消息:

process.on(&#39;message&#39;, (m, server) => { 
 if (m === &#39;server&#39;) { 
  server.on(&#39;connection&#39;, (socket) => { 
   socket.end(&#39;handled by child&#39;); 
  }); 
 } 
});

这时候server就被子进程和父进程共享了,一些连接可以被父进程处理,一些被子进程处理。上面的例子如果使用dgram那么就应该监听message事件而不是connection,使用server.bind而不是server.listen,但是当前只在UNIX平台上可行。
下面的例子展示发送一个socket对象(产生两个子进程,处理normal和special优先级):

父进程为:

const normal = require(&#39;child_process&#39;).fork(&#39;child.js&#39;, [&#39;normal&#39;]); 
const special = require(&#39;child_process&#39;).fork(&#39;child.js&#39;, [&#39;special&#39;]); 
// Open up the server and send sockets to child 
const server = require(&#39;net&#39;).createServer(); 
server.on(&#39;connection&#39;, (socket) => { 
 // If this is special priority 
 if (socket.remoteAddress === &#39;74.125.127.100&#39;) { 
  special.send(&#39;socket&#39;, socket); 
  return; 
 } 
 // This is normal priority 
 normal.send(&#39;socket&#39;, socket); 
}); 
server.listen(1337);

子进程为:

process.on(&#39;message&#39;, (m, socket) => { 
 if (m === &#39;socket&#39;) { 
  socket.end(`Request handled with ${process.argv[2]} priority`); 
 } 
});

当socket被发送到子进程的时候那么父进程已经无法追踪这个socket什么时候被销毁的。这时候.connections属性就会成为null,因此我们建议不要使用.maxConnections。注意:这个方法在内部JSON.stringify去序列化消息

child.stderr:

一个流对象,是一个可读流表示子进程的stderr。他是child.stdio[2]的别名,两者表示同样的值。如果子进程是通过stdio[2]产生的,设置的不是pipe那么值就是undefined。

child.stdin:

一个可写的流对象。注意:如果子进程等待读取输入,那么子进程会一直等到流调用了end方法来关闭的时候才会继续读取。如果子进程通过stdio[0]产生,同时不是设置的pipe那么值就是undefined。child.stdin是child.stdio[0]的别名,表示同样的值。

const spawn = require(&#39;child_process&#39;).spawn;  
const grep = spawn(&#39;grep&#39;, [&#39;ssh&#39;]);  
console.log(`Spawned child pid: ${grep.pid}`);  
grep.stdin.end();//通过grep.stdin.end结束

child.stdio:

一个子进程管道的稀疏数组,是 child_process.spawn()函数的stdio选项,同时这个值被设置为pipe。child.stdio[0], child.stdio[1], 和 child.stdio[2]也可以通过child.stdin, child.stdout, 和 child.stderr访问。下面的例子中只有子进程的fd1(也就是stdout)被设置为管道,因此只有父进程的child.stdio[1]是一个流,其他的数组中对象都是null:

const assert = require(&#39;assert&#39;); 
const fs = require(&#39;fs&#39;); 
const child_process = require(&#39;child_process&#39;); 
const child = child_process.spawn(&#39;ls&#39;, { 
  stdio: [ 
   0, // Use parents stdin for child 
   &#39;pipe&#39;, // Pipe child&#39;s stdout to parent 
   fs.openSync(&#39;err.out&#39;, &#39;w&#39;) // Direct child&#39;s stderr to a file 
  ] 
}); 
assert.equal(child.stdio[0], null); 
assert.equal(child.stdio[0], child.stdin); 
assert(child.stdout); 
assert.equal(child.stdio[1], child.stdout); 
assert.equal(child.stdio[2], null); 
assert.equal(child.stdio[2], child.stderr);

child.stdout:

一个可读流,代表子进程的stdout。如果子进程产生的时候吧stdio[1]设置为除了pipe以外的任何数,那么值就是undefined。其值和child.stdio[1]一样

以上がNode.jsのchild_processモジュールから親プロセスと子プロセス間の通信を学習するサンプルコードの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。