nginx에서는 마스터 프로세스와 작업자 프로세스 간에 전이중 통신 방식인 소켓페어를 사용합니다. 소켓 쌍 기능이 성공적으로 실행되면 연결이 설정된 소켓 쌍이 생성됩니다. 서로 통신하는 두 프로세스는 소켓 중 하나를 사용하여 각각 읽기 및 쓰기 작업을 수행할 수 있으므로 두 프로세스 간의 통신이 가능해집니다.
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; }
포크 이전에는 현재 프로세스에서 소켓 쌍, 즉 소켓 쌍을 생성한 것을 볼 수 있습니다. 이 소켓 쌍의 경우 하나는 서버측 fds[0]으로 간주되고 다른 하나는 클라이언트측 fds[1]로 간주될 수 있습니다. fds[0]과 fds[1] 사이에 설정된 링크를 통해 완료할 수 있습니다. 전이중 통신.
Fork가 실행된 후 하위 프로세스가 생성됩니다. 자식 프로세스에서는 부모 프로세스에 의해 생성된 소켓 쌍이 자연스럽게 fds'로 복사되어 자식 프로세스에 존재하게 됩니다. 상위 프로세스가 계속 실행됩니다. 이때 상위 프로세스와 하위 프로세스에는 동일한 소켓 쌍이 존재하게 됩니다.
메인 프로세스의 fds[0]에 데이터를 쓰고, 자식 프로세스의 fds'[1]에서 데이터를 읽어 부모 프로세스와 자식 간의 관계를 구현한다고 상상해보세요. . 프로세스 간 통신. 물론, 메인 프로세스 fds[1]에 데이터가 기록되면, 기록된 데이터는 하위 프로세스 fds'[0]에서도 읽혀집니다. 실제 사용에서는 통신을 위해 한 쌍의 소켓만 예약하면 되며 나머지 두 소켓은 각각 상위 프로세스와 하위 프로세스에서 닫힐 수 있습니다.
물론 fds의 소켓을 어떤 방식으로든 다른 프로세스에 전달할 수 있다면 소켓쌍 프로세스 간 통신도 달성할 수 있습니다.
위 내용은 관련 내용을 포함하여 nginx 프로세스 간 통신 소켓 쌍을 소개한 내용이 PHP 튜토리얼에 관심이 있는 친구들에게 도움이 되기를 바랍니다.