1 定义
ngx_channel_handler 函数 定义在 ./nginx-1.24.0/src/os/unix/ngx_process_cycle.c
staticvoidngx_channel_handler(ngx_event_t*ev){ngx_int_tn;ngx_channel_tch;ngx_connection_t*c;if(ev->timedout){ev->timedout=0;return;}c=ev->data;ngx_log_debug0(NGX_LOG_DEBUG_CORE,ev->log,0,"channel handler");for(;;){n=ngx_read_channel(c->fd,&ch,sizeof(ngx_channel_t),ev->log);ngx_log_debug1(NGX_LOG_DEBUG_CORE,ev->log,0,"channel: %i",n);if(n==NGX_ERROR){if(ngx_event_flags&NGX_USE_EPOLL_EVENT){ngx_del_conn(c,0);}ngx_close_connection(c);return;}if(ngx_event_flags&NGX_USE_EVENTPORT_EVENT){if(ngx_add_event(ev,NGX_READ_EVENT,0)==NGX_ERROR){return;}}if(n==NGX_AGAIN){return;}ngx_log_debug1(NGX_LOG_DEBUG_CORE,ev->log,0,"channel command: %ui",ch.command);switch(ch.command){caseNGX_CMD_QUIT:ngx_quit=1;break;caseNGX_CMD_TERMINATE:ngx_terminate=1;break;caseNGX_CMD_REOPEN:ngx_reopen=1;break;caseNGX_CMD_OPEN_CHANNEL:ngx_log_debug3(NGX_LOG_DEBUG_CORE,ev->log,0,"get channel s:%i pid:%P fd:%d",ch.slot,ch.pid,ch.fd);ngx_processes[ch.slot].pid=ch.pid;ngx_processes[ch.slot].channel[0]=ch.fd;break;caseNGX_CMD_CLOSE_CHANNEL:ngx_log_debug4(NGX_LOG_DEBUG_CORE,ev->log,0,"close channel s:%i pid:%P our:%P fd:%d",ch.slot,ch.pid,ngx_processes[ch.slot].pid,ngx_processes[ch.slot].channel[0]);if(close(ngx_processes[ch.slot].channel[0])==-1){ngx_log_error(NGX_LOG_ALERT,ev->log,ngx_errno,"close() channel failed");}ngx_processes[ch.slot].channel[0]=-1;break;}}}
ngx_channel_handler 函数 是 Nginx Worker 进程接收 Master 进程控制指令的事件回调函数
2 详解
1 函数签名
staticvoidngx_channel_handler(ngx_event_t*ev)
函数不返回任何值
参数 ngx_event_t *ev 当前被触发的事件 事件驱动框架在检测到文件描述符可读时, 会将该文件描述符对应的 ngx_event_t 对象指针传递给注册的回调函数。
2 逻辑流程
1 局部变量 2 超时检查 3 读取并处理数据
1 局部变量
{ngx_int_tn;ngx_channel_tch;ngx_connection_t*c;
2 超时检查
if(ev->timedout){ev->timedout=0;return;}
检查事件是否因超时触发,而非真正的 I/O 就绪 若 ev->timedout 为真, 表示该事件是在定时器到期后被强行调用的, 此时通道上可能并没有数据。 将超时标志清零后直接返回,不执行任何读取操作。
3 读取并处理数据
c=ev->data;ngx_log_debug0(NGX_LOG_DEBUG_CORE,ev->log,0,"channel handler");
#1 从事件对象中取出关联的 ngx_connection_t 结构。 在通道初始化时(ngx_add_channel_event),会将连接对象指针赋值给 ev->data #2 如果编译时开启了 --with-debug,则输出一条调试日志
for(;;){
无限循环,尝试一次性读取通道中所有待处理的消息 通道是流式 socketpair,可能一次读事件到达时,内核缓冲区中积累了多条消息。 循环读取直到返回 NGX_AGAIN(无数据)或发生错误。
n=ngx_read_channel(c->fd,&ch,sizeof(ngx_channel_t),ev->log);
调用 Nginx 封装的通道读取函数, 从文件描述符 c->fd 中读取一条完整的 ngx_channel_t 消息。
ngx_log_debug1(NGX_LOG_DEBUG_CORE,ev->log,0,"channel: %i",n);
输出一条调试日志
if(n==NGX_ERROR){if(ngx_event_flags&NGX_USE_EPOLL_EVENT){ngx_del_conn(c,0);}ngx_close_connection(c);return;}
判断是否发生致命错误 如果当前使用的是 epoll 事件模型, 从 epoll 监听集合中删除该文件描述符的监听事件。 关闭 c->fd 文件描述符, 并回收 ngx_connection_t 结构体到连接池 return:退出函数,因为连接已关闭,后续循环无意义
if(ngx_event_flags&NGX_USE_EVENTPORT_EVENT){if(ngx_add_event(ev,NGX_READ_EVENT,0)==NGX_ERROR){return;}}if(n==NGX_AGAIN){return;}ngx_log_debug1(NGX_LOG_DEBUG_CORE,ev->log,0,"channel command: %ui",ch.command);
为 Solaris 的 Event Port 事件模型
switch(ch.command){
根据消息中的 command 字段执行对应的操作
caseNGX_CMD_QUIT:ngx_quit=1;break;
设置全局变量 ngx_quit 为 1。 发送方: 通常由 master 进程 在收到 SIGQUIT 信号后,通过通道发送给所有 worker 进程 通知 worker 进程 退出
caseNGX_CMD_TERMINATE:ngx_terminate=1;break;
设置全局变量 ngx_terminate 为 1 master 进程通知 worker 进程 终止
caseNGX_CMD_REOPEN:ngx_reopen=1;break;
设置全局变量 ngx_reopen 为 1 master 进程通知 worker 进程 reopen 日志文件 实现日志轮转
caseNGX_CMD_OPEN_CHANNEL:ngx_log_debug3(NGX_LOG_DEBUG_CORE,ev->log,0,"get channel s:%i pid:%P fd:%d",ch.slot,ch.pid,ch.fd);ngx_processes[ch.slot].pid=ch.pid;ngx_processes[ch.slot].channel[0]=ch.fd;break;
新启动了一个 worker,master 进程通知其他已有进程 关于新进程的信息, ch.slot 新 worker 进程在进程管理表中的槽位 ch.pid 新 worker 进程的 pid ch.fd 与新 worker 进程通信的通道的文件描述符 将以上信息记录到当前进程的 进程管理表中
caseNGX_CMD_CLOSE_CHANNEL:ngx_log_debug4(NGX_LOG_DEBUG_CORE,ev->log,0,"close channel s:%i pid:%P our:%P fd:%d",ch.slot,ch.pid,ngx_processes[ch.slot].pid,ngx_processes[ch.slot].channel[0]);if(close(ngx_processes[ch.slot].channel[0])==-1){ngx_log_error(NGX_LOG_ALERT,ev->log,ngx_errno,"close() channel failed");}ngx_processes[ch.slot].channel[0]=-1;break;}}}
一个 worker 进程A结束了 master 进程发送命令通知其他进程 关闭与进程A通信的通道 输出 log 关闭与 进程A 通信的通道文件描述符 将进程表中的该通道描述符标记为 -1,表示已失效