nginx 进程间通信--- channel方式
主要实现思路:通过socketpair创建出一对双通道的套接字并通过fork的方式实现父子进程间的通信
解释一下上图:
第一次fork的时候,图中的红色、绿色、紫色部分并没有,是后来通过sendmsg的方式获取到的。那么第一次fork之前做了什么呢?很简单, 通过socketpair创建一对套接字并放到全局数据中,在fork后 父子进程都会有全局数组中的这对套接字,然后子进程将channel[0]关闭了并将channle[1]设置为可读状态。
第二次fork的时候,图中的绿色、紫色部分并没有,是后来通过sendmsg的方式获取到的。第二次在fork之前同样通过socketpair创建一对套接字,并放入全局数组中(此时在数组的第二位置,注意由于是全局数组,此时数组中第一的位置上其实是第一次fork出来的套接字),在fork后父子进程都会有全局数组的相关信息,然后子进程将本次的channel[0]关闭了并将channle[1]设置为可读状态,并遍历数组将之前的channel[1]关闭(读者可以猜想一下这个是为什么,见注1)。 同时,父进程在干什么呢? 他会遍历数组,将本次创建的channel[0], 向每个数组中的channel[0]中发送(通过sendmsg),这样由于第一次fork的时候channel[1]一直是可读的状态,所以第一次fork出来的channel[1]会收到这个channel[0],这样第一次fork时的 红色channel[0]就这样被填充上了
第三次fork的时候,同样紫色的部分并没有。 在第三次fork之前同样通过socketpir创建一对套接字,并放入全局数组中(此时在数组的第三位置,注意由于是全局数组,此时数组中第一、第二的位置上其实是第一次,第二次fork出来的套接字),在fork后父子进程都会有全局数组的相关信息,然后子进程将本次的channel[0]关闭了并将channle[1]设置为可读状态,并遍历数组将之前的channel[1]关闭。同时父进程会遍历数组,将本次创建的channel[0], 向每个数组中的channel[0]中发送(通过sendmsg),这样由于第一次fork的时候channel[1]一直是可读的状态,所以第一次fork出来的channel[1]会收到这个channel[0],这样第一次fork时的 绿色channel[0]就这样被填充上了。 循环第二次的时候,第二次fork时channel[1]一直是可读的状态,所以第二次fork出来的channel[1]会收到这个channel[0],这样第二次fork时的 绿channel[0]就这样被填充上了
第四次,同上
注1:其实是为了防止在向第一次创建的套接字对channel[0]写入数据时,会从第二次fork时继承过来的channel[1](这个是第一次通过socketpair得到的)读取到。
关键代码:
函数 ngx_start_worker_processes
for (i = 0; i < n; i++) {
ngx_spawn_process(cycle, ngx_worker_process_cycle,
(void *) (intptr_t) i, "worker process", type);
ch.pid = ngx_processes[ngx_process_slot].pid;
ch.slot = ngx_process_slot;
ch.fd = ngx_processes[ngx_process_slot].channel[0];
ngx_pass_open_channel(cycle, &ch);
}
函数:ngx_spawn_process
//在全局数组ngx_processes[s]中创建套接字对
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;
}
//紧跟着进行fork
pid = fork();
//这样父子进程都会共享这个全局数组
//注意在第n次执行到这里后,全局数组中的所有数据都会继承过来(重中之重,这个数组是父进程的数组)
/// 父进程都做了什么??
函数:ngx_pass_open_channel
{
ngx_int_t i;
for (i = 0; i < ngx_last_process;
if (i == ngx_process_slot //这样代码,表示本次创建的套接字不参与下面的流程
|| ngx_processes[i].pid == -1
|| ngx_processes[i].channel[0] == -1)
{
continue;
}
ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"pass channel s:%i pid:%P fd:%d to s:%i pid:%P fd:%d",
ch->slot, ch->pid, ch->fd,
i, ngx_processes[i].pid,
ngx_processes[i].channel[0]);
/* TODO: NGX_AGAIN */
//循环遍历数组中的元素,向每个数组中的channel[0]写入ch(本次创建的套接字对,channel[0])
ngx_write_channel(ngx_processes[i].channel[0],
ch, sizeof(ngx_channel_t), cycle->log);
}
}
/// 子进程在做什么呢?
函数:ngx_worker_process_cycle
函数:ngx_worker_process_init
//初始化epoll //ngx_event_process_init
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->init_process) {
if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
/* fatal */
exit(2);
}
}
}
//循环遍历,将之前数组中的channel[1]全部关闭掉
for (n = 0; n < ngx_last_process; n++) {
if (ngx_processes[n].pid == -1) {
continue;
}
if (n == ngx_process_slot) {
continue;
}
if (ngx_processes[n].channel[1] == -1) {
continue;
}
if (close(ngx_processes[n].channel[1]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"close() channel failed");
}
}
//把本次创建的channel[0]关闭掉
if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"close() channel failed");
}
//将本次创建的channel[1] 设置为可读状态
if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
ngx_channel_handler)
== NGX_ERROR)
{
/* fatal */
exit(2);
}