1. Nginx 频道

ngx_channel_t 频道是 Nginx master 进程与 worker 进程之间通信的常用工具,它是使用本机套接字实现的,即 socketpair 方法,它用于创建父子进程间使用的套接字。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sv[2]);

这个方法可以创建一对关联的套接字 sv[2]。

  • domain:表示域,在 Linux 下通常取值为 AF_UNIX;
  • type:取值为 SOCK_STREAM 或 SOCK_DGRAM,它表示在套接字上使用的是 TCP 还是 UDP;
  • protocol:必须传递 0;
  • sv[2]:是一个含有两个元素的整型数组,实际上就是两个套接字。
  • 当 socketpair 返回 0 时,sv[2] 这两个套接字创建成功,否则 sockpair 返回 -1 表示失败.

当 socketpair 执行成功时,sv[2] 这两个套接字具备下列关系:

  • 向 sv[0] 套接字写入数据,将可以从 sv[1] 套接字中读取到刚写入的数据;

  • 同样,向 sv[1] 套接字写入数据,也可以从 sv[0] 中读取到写入的数据。

  • 通常,在父、子进程通信前,会先调用 socketpair 方法创建这样一组套接字,在调用 fork 方法创建出子进程后,将会在父进程中关闭 sv[1] 套接字,仅使用 sv[0] 套接字用于向子进程发送数据以及接收子进程发送来的数据;

  • 而在子进程中则关闭 sv[0] 套接字,仅使用 sv[1] 套接字既可以接收父进程发送来的数据,也可以向父进程发送数据。

ngx_channel_t 结构体是 Nginx 定义的 master 父进程与 worker 子进程间的消息格式,如下:

typedef struct {
// 传递的 TCP 消息中的命令
ngx_uint_t command;
// 进程 ID,一般是发送命令方的进程 ID
ngx_pid_t pid;
// 表示发送命令方在 ngx_processes 进程数组间的序号
ngx_int_t slot;
// 通信的套接字句柄
ngx_fd_t fd;
}ngx_channel_t;

Nginx 针对 command 成员定义了如下命令:

// 打开频道,使用频道这种方式通信前必须发送的命令
#define NGX_CMD_OPEN_CHANNEL 1 // 关闭已经打开的频道,实际上也就是关闭套接字
#define NGX_CMD_CLOSE_CHANNEL 2 // 要求接收方正常地退出进程
#define NGX_CMD_QUIT 3 // 要求接收方强制地结束进程
#define NGX_CMD_TERMINATE 4 // 要求接收方重新打开进程已经打开过的文件
#define NGX_CMD_REOPEN 5

问:master 是如何启动、停止 worker 子进程的?

答:正是通过 socketpair 产生的套接字发送命令的,即每次要派生一个子进程之前,都会先调用 socketpair 方法。

在 Nginx 派生子进程的 ngx_spawn_process 方法中,会首先派生基于 TCP 的套接字,如下:

ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
char *name, ngx_int_t respawn)
{ if (respawn != NGX_PROCESS_DETACHED) { /* Solaris 9 still has no AF_LOCAL */ // ngx_processes[s].channel 数组正是将要用于父、子进程间通信的套接字对
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
{
return NGX_INVALID_PID;
} // 将 channel 套接字对都设置为非阻塞模式
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;
}
...
}

ngx_processes 数组定义了 Nginx 服务中所有的进程,包括 master 进程和 worker 进程,如下:

#define NGX_MAX_PROCESSES 1024

// 虽然定义了 NGX_MAX_PROCESSES 个成员,但是已经使用的元素仅与启动的进程个数有关
ngx_processes_t ngx_processes[NGX_MAX_PROCESSES];

ngx_processes 数组的类型是 ngx_processes_t,对于频道来说,这个结构体只关心它的 channel 成员:

typedef struct {
...
// socketpair 创建的套接字对
ngx_socket_t channel[2];
}ngx_processes_t;

1. ngx_write_channel:使用频道发送 ngx_channel_t 消息

ngx_int_t
ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
ngx_log_t *log)
{
ssize_t n;
ngx_err_t err;
struct iovec iov[1];
struct msghdr msg; #if (NGX_HAVE_MSGHDR_MSG_CONTROL) union {
struct cmsghdr cm;
char space[CMSG_SPACE(sizeof(int))];
}cmsg; if (ch->fd == -1) {
msg.msg_control = NULL;
msg.msg_controllen = 0; } else {
// 辅助数据
msg.msg_control = (caddr_t)&cmsg;
msg.msg_controllen = sizeof(cmsg); ngx_memzero(&cmsg, sizeof(cmsg)); cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));
cmsg.cm.cmsg_level = SOL_SOCKET;
cmsg.cm.cmsg_type = SCM_RIGHTS; /*
* We have to use ngx_memcpy() instead of simple
* *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
* because some gcc 4.4 with -O2/3/s optimization issues the warning:
* dereferencing type-punned pointer will break strict-aliasing rules
*
* Fortunately, gcc with -O1 compiles this ngx_memcpy()
* in the same simple assignment as in the code above
*/ ngx_memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));
} msg.msg_flags = 0; #else if (ch->fd == -1) {
msg.msg_accrights = NULL;
msg.msg_accrightslen = 0; } else {
msg.msg_accrights = (caddr_t) &ch->fd;
msg.msg_accrightslen = sizeof(int);
} #endif // 指向要发送的 ch 起始地址
iov[0].iov_base = (char *) ch;
iov[0].iov_len = size; // msg_name 和 msg_namelen 仅用于未连接套接字(如UDP)
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1; // 将该 ngx_channel_t 消息发出去
n = sendmsg(s, &msg, 0); if (n == -1) {
err = ngx_errno;
if (err == NGX_EAGAIN) {
return NGX_AGAIN;
} return NGX_ERROR;
} return NGX_OK;
}

2. ngx_read_channel: 读取消息

ngx_int_t
ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
{
ssize_t n;
ngx_err_t err;
struct iovec iov[1];
struct msghdr msg; #if (NGX_HAVE_MSGHDR_MSG_CONTROL)
union {
struct cmsghdr cm;
char space[CMSG_SPACE(sizeof(int))];
} cmsg;
#else
int fd;
#endif iov[0].iov_base = (char *)ch;
iov[0].iov_len = size; msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1; #if (NGX_HAVE_MSGHDR_MSG_CONTROL)
msg.msg_control = (caddr_t) &cmsg;
msg.msg_controllen = sizeof(cmsg);
#else
msg.msg_accrights = (caddr_t) &fd;
msg.msg_accrightslen = sizeof(int);
#endif // 接收命令
n = recvmsg(s, &msg, 0); if (n == -1) {
err = ngx_errno;
if (err == NGX_EAGAIN) {
return NGX_AGAIN;
} return NGX_ERROR;
} if (n == 0) {
return NGX_ERROR;
} // 接收的数据不足
if ((size_t) n < sizeof(ngx_channel_t)) {
return NGX_ERROR;
} #if (NGX_HAVE_MSGHDR_MSG_CONTROL) // 若接收到的命令为"打开频道,使用频道这种方式通信前必须发送的命令"
if (ch->command == NGX_CMD_OPEN_CHANNEL) { if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
return NGX_ERROR;
} if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
{
return NGX_ERROR;
} /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */ ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
} // 若接收到的消息是被截断的
if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"recvmsg() truncated data");
} #else if (ch->command == NGX_CMD_OPEN_CHANNEL) {
if (msg.msg_accrightslen != sizeof(int)) {
return NGX_ERROR;
} ch->fd = fd;
}
#endif return n;
}

在 Nginx 中,目前仅存在 master 进程向 worker 进程发送消息的场景,这时对于 socketpair 方法创建的 channel[2] 套接字来说,master 进程会使用 channel[0] 套接字来发送消息,而 worker 进程会使用 channel[1] 套接字来接收消息。

3. ngx_add_channel_event: 把接收频道消息的套接字添加到 epoll 中

worker 进程调度 ngx_read_channel 方法接收频道消息是通过该 ngx_add_channel_event 函数将接收频道消息的套接字(对于 worker 即为channel[1])添加到 epoll 中,当接收到父进程消息时子进程会通过 epoll 的事件回调相应的 handler 方法来处理这个频道消息,如下:

ngx_int_t
ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
ngx_event_handler_pt handler)
{
ngx_event_t *ev, *rev, *wev;
ngx_connection_t *c; // 获取一个空闲连接
c = ngx_get_connection(fd, cycle->log); if (c == NULL) {
return NGX_ERROR;
} c->pool = cycle->pool; rev = c->read;
wev = c->write; rev->log = cycle->log;
wev->log = cycle->log; rev->channel = 1;
wev->channel = 1; ev = (event == NGX_READ_EVENT) ? rev : wev; // 初始化监听该 ev 事件时调用的回调函数
ev->handler = handler; // 将该接收频道消息的套接字添加到 epoll 中
if (ngx_add_conn && (ngx_event_flags && NGX_USE_EPOLL_EVENT) == 0) {
// 这里是同时监听该套接字的读、写事件
if (ngx_add_conn(c) == NGX_ERROR) {
ngx_free_connection(c);
return NGX_ERROR;
} } else {
// 这里是仅监听 ev 事件
if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
ngx_free_connection(c);
return NGX_ERROR;
}
} return NGX_OK;
}

4. ngx_close_channel: 关闭这个频道通信方式

void
ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log)
{
if (close(fd[0]) == -1) { } if (close(fd[1]) == -1) { }
}

Nginx之进程间的通信机制(Nginx频道)的更多相关文章

  1. Nginx之进程间的通信机制(共享内存、原子操作)

    1. 概述 Linux 提供了多种进程间传递消息的方式,如共享内存.套接字.管道.消息队列.信号等,而 Nginx 框架使用了 3 种传递消息的传递方式:共享内存.套接字.信号. 在进程间访问共享资源 ...

  2. Nginx之进程间的通信机制(信号、信号量、文件锁)

    1. 信号 Nginx 在管理 master 进程和 worker 进程时大量使用了信号.Linux 定义的前 31 个信号是最常用的,Nginx 则通过重定义其中一些信号的处理方法来使用吸纳后,如接 ...

  3. 从AIDL开始谈Android进程间Binder通信机制

    转自: http://tech.cnnetsec.com/585.html 本文首先概述了Android的进程间通信的Binder机制,然后结合一个AIDL的例子,对Binder机制进行了解析. 概述 ...

  4. Nginx学习——Nginx进程间的通信

    nginx进程间的通信 进程间消息传递 共享内存 共享内存还是Linux下提供的最主要的进程间通信方式,它通过mmap和shmget系统调用在内存中创建了一块连续的线性地址空间,而通过munmap或者 ...

  5. Python 35 进程间的通信(IPC机制)、生产者消费者模型

    一:进程间的通信(IPC):先进先出  管道:队列=管道+锁 from multiprocessing import Queue q=Queue(4) q.put(['first',],block=T ...

  6. Android进程间的通信之AIDL

    Android服务被设计用来执行很多操作,比如说,可以执行运行时间长的耗时操作,比较耗时的网络操作,甚至是在一个单独进程中的永不会结束的操作.实现这些操作之一是通过Android接口定义语言(AIDL ...

  7. Python 多进程编程之 进程间的通信(Queue)

    Python 多进程编程之 进程间的通信(Queue) 1,进程间通信Process有时是需要通信的,操作系统提供了很多机制来实现进程之间的通信,而Queue就是其中的一个方法----这是操作系统开辟 ...

  8. python全栈开发day32-进程创建,进程同步,进程间的通信,进程池

    一.内容总结 1.进程创建 1) Process:两种创建一个新进程的方法: 1.实例化Process,通过args=(,)元组形式传参,2创建类继承Process,类初始化的时候传参数 2) p.j ...

  9. Linux进程间的通信

    一.管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: A. 管道是半双工的,数据只能向一个方向流动: B. 需要双工通信时,需要建立起两个管道: C. 只能用于父子进程或者兄弟 ...

随机推荐

  1. git 操作实践

    git操作: - git是一个用于帮助用户实现版本控制的软件 #首先创建项目 1. cd到项目文件目录 2. 鼠标右键点击 Git Bash Here 3. git init #在项目文件目录生成 . ...

  2. mydql 函数和存储过程

    存储过程 CREATE PROCEDURE getUid (IN phone CHAR(11), OUT uid INT ) READS SQL DATA BEGIN select u.id from ...

  3. 分布式任务队列 Celery —— Task对象

    转载至 JmilkFan_范桂飓:http://blog.csdn.net/jmilk  目录 目录 前文列表 前言 Task 的实例化 任务的名字 任务的绑定 任务的重试 任务的请求上下文 任务的继 ...

  4. dict 字典 函数值应用

    函数 说明 D代表字典对象   D.clear() 清空字典 D.pop(key) 移除键,同时返回此键所对应的值 D.copy() 返回字典D的副本,只复制一层(浅拷贝) D.update(D2) ...

  5. 使用pipenv管理你的python项目

    怎么使用pipenv管理你的python项目   原文链接:https://robots.thoughtbot.com/how-to-manage-your-python-projects-with- ...

  6. shell脚本基础编写

    shell脚本的格式 名称:Shell 脚本文件的名称可以任意,但为了避免被误以为是普通文件,建议将 .sh 后缀加上,以表示是一个脚本文件. shell 脚本中一般会出现三种不同的元素: 第一行的脚 ...

  7. 【转载】总结:几种生成HTML格式测试报告的方法

    总结:几种生成HTML格式测试报告的方法 写自动化测试时,一个很重要的任务就是生成漂亮的测试报告. 1.用junit或testNg时,可以用ant辅助生成html格式: <target name ...

  8. [NOI2016]循环之美——结论+莫比乌斯反演

    原题链接 好妙的一道神仙题 题目大意 让你求在\(k\)进制下,\(\frac{x}{y}\)(\(x\in [1,n],y\in [1,m]\))中有多少个最简分数是纯循环小数 SOLUTION 首 ...

  9. [六省联考2017]分手是祝愿——期望DP

    原题戳这里 首先可以确定的是最优策略一定是从大到小开始,遇到亮的就关掉,因此我们可以\(O(nlogn)\)的预处理出初始局面需要的最小操作次数\(tot\). 然后容(hen)易(nan)发现即使加 ...

  10. CentOS7下tftp服务安装配置

    1.软件包安装 root用户或者普通用户使用sudo权限执行如下命令: yum install xinetd tftp tftp-server # root 用户执行 sudo yum install ...