Linux下的IPC很多,nginx的进程都是有亲缘关系的进程,对于他们的通信我们选择TCP socket进行通信。 TCP socket 用来做进程通信的好处有, 1.socket是文件描述符,操作简单。 2.双向流动。3.另外还有一个重要好处:可记录可重现,我们可以用tcpdump抓取信息,方便调试。
当然对于进程间大量数据的共享 自然而然的我们采用共享内存。
采用socketpair()函数创造匿名socket 为master process(父进程) 和 work process(子进程) 以及work process 之间的通信。
关于socketpair() 参见这里即可。
先看看nginx process 的定义
ngx_process.h
typedef struct { ngx_pid_t pid; int status; ngx_socket_t channel[2];//这里就是用来存放 socketpair 的两个描述符 ngx_spawn_proc_pt proc; void *data;
在nginx_process.c中
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 (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) //注意是在fork之前哦 { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "socketpair() failed while spawning \"%s\"", name); return NGX_INVALID_PID; } ... pid=fork(); ...
在执行fork之前,先调用 socketpair()创建一对socket 描述符存放变量ngx_process[s].channel内,在fork()之后,子进程继承了父进程的这一对socket描述符,Nginx此时会把channel[0]给父进程使用,把channel[1]给子进程使用。从这以后就可以实现父子进程的通信。
if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler) //子进程把channel加入到监听事件,ngx_channel_handler为事件响应的回调函数 == NGX_ERROR) {
那么子进程和子进程直接是怎么通信的呢?
实际上子进程work_process之间也是通过这些socket进行通信的 : master父进程每次fork一个新进程的时候都会把这个新进程的信息告知前面已经生成的子进程
在ngx_process_cycle.c中
ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch) { ngx_int_t i; for (i = 0; i < ngx_last_process; i++) { if (i == ngx_process_slot || ngx_processes[i].pid == -1 || ngx_processes[i].channel[0] == -1) { continue; }
参数ch包含了刚刚创建的新的子进程pid,进程信息,channel描述符在全局数组中。
不过截至目前,Nginx并没有子进程向父进程写入信息,子进程间的通信也没有实际用处。
这样子进程直接都有了各自的信息包括socket描述符。这样可以彼此可以通过此完成通信。
参考书籍:《深入剖析Nginx> 高群凯
Nginx开发从入门到精通
Nginx 进程间通信