nginxでは、マスタープロセスとワーカープロセスの間で全二重の通信方式であるソケットペアが使用されます。ソケットペア関数が正常に実行されると、接続が確立されたソケットのペアが作成され、相互に通信する 2 つのプロセスは、一方のソケットを使用してそれぞれ読み取り操作と書き込み操作を実行できるため、2 つのプロセス間の通信が可能になります。
nginx のソースコードを見ると、次の関数がソケットペアを作成していることがわかります
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) { u_long on; ngx_pid_t pid; ngx_int_t s; /. ......省略...... ./ if (respawn != NGX_PROCESS_DETACHED) { /* Solaris 9 still has no AF_LOCAL */ //创建socketpair if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "socketpair() failed while spawning \"%s\"", name); return NGX_INVALID_PID; } //非阻塞 if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //非阻塞 if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //异步 on = 1; if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。 if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //设置close_on_exec,当通过exec函数族创建了新进程后,原进程的该socket会被关闭 if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } //设置close_on_exec,当通过exec函数族创建了新进程后,原进程的该socket会被关闭 if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) { ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; } ngx_channel = ngx_processes[s].channel[1]; } else { ngx_processes[s].channel[0] = -1; ngx_processes[s].channel[1] = -1; } ngx_process_slot = s; pid = fork(); switch (pid) { case -1: ngx_close_channel(ngx_processes[s].channel, cycle->log); return NGX_INVALID_PID; case 0: //fork成功,子进程创建,同时相关socket描述符也会被复制一份 ngx_pid = ngx_getpid(); proc(cycle, data); break; default: break; } /. ......省略...... ./ return pid; }
フォークが成功した後、ディスクリプタがフォークプロセス中に使用されなくなった場合は、元のプロセスのディスクリプタもコピーされます。 、速やかに閉鎖する必要があります。
fork->exec 関数ファミリーを使用して新しいプロセスを作成する場合、リソース リークを避けるために元の記述子が正常に閉じられるようにするためのより良い方法を使用できます。つまり、上記のコードでは fcntl(FD_CLOEXEC) 関数がソケット上で呼び出され、ソケットの属性が設定されます。exec 関数ファミリーが呼び出されると、ソケットは自動的に閉じられます。ソケットの作成直後に FD_CLOEXEC 属性を設定するこの方法を使用すると、exec 作成プロセスの前に関連するソケットを手動で閉じる必要がなくなり、特に多数の記述子が作成および管理される場合に非常に実用的です。
多くの場合、操作には次のメソッドが使用されます:
#include <sys/types.h> #include <sys/socket.h> #include <stdlib.h> #include <stdio.h> int main() { pid_t pid; int fds[2]; int valRead, valWrite; if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) { return 0; } pid = fork(); if (0 == pid) { pid = getpid(); printf("[%d]-child process start", pid); close(fds[0]); //read write on fds[1] write(fds[1], &valWrite, sizeof(valWrite)); read(fds[1], &valRead, sizeof(valRead)); } else if (0 < pid) { pid = getpid(); printf("[%d]-parent process continue", pid); close(fds[1]); //read write on fds[0] read(fds[0], &valRead, sizeof(valRead)); write(fds[0], &valWrite, sizeof(valWrite)); } else { printf("%s", "fork failed"); } return 0; }
fork の前に、現在のプロセスがソケットのペア (socketpair) を作成したことがわかります。このソケットのペアでは、1 つはサーバー側の fds[0]、もう 1 つはクライアント側の fds[1] と見なすことができ、fds[0] と fds[1] の間に確立されたリンクを通じて完了できます。全二重通信。
forkが実行されると、子プロセスが作成されます。子プロセスでは、親プロセスが作成したソケットペアが当然fdsとしてコピーされ、子プロセスに存在します。親プロセスは実行を継続します。このとき、親プロセスと子プロセスには同じソケットペアが存在します。
メインプロセスで fds[0] にデータを書き込み、そのデータが子プロセスの fds'[1] で読み取られることで、親プロセスと子プロセス間の通信が実現するとします。もちろん、メインプロセス fds[1] にデータが書き込まれると、書き込まれたデータはサブプロセス fds'[0] にも読み込まれます。実際の使用では、通信用に 1 組のソケットを予約するだけでよく、残りの 2 つのソケットはそれぞれ親プロセスと子プロセスで閉じることができます。
もちろん、何らかの方法で fds 内のソケットを別のプロセスに渡すことができれば、socketpair のプロセス間通信も実現できます。
上記では、nginx のプロセス間通信ソケットペアを、関連する内容も含めて紹介しています。PHP チュートリアルに興味のある友人に役立つことを願っています。