5.Nginx进程通信

在4中我们看到了,Nginx的master进程在创建worker进程时也会创建一个流管道(Unix域字节流socketpair),然后master进程和worker进程之间的通信就借助这个流管道来进行。

在Nginx中,把这个流管道称之为channel,相关的发送接收消息函数在nginx_channel.c源文件中。结合4和下面的代码学习,我们可以看出Nginx的worker进程也可以借助上面的流管道来通信。


我们在4中的源代码中看到,在创建一个新的worker进程和与之通信的sockerpair后,master进程会向之前的worker进程发送NGX_CMD_OPEN_CHANNEL指令消息,以此将socketpair的一端通过描述符传递复制到这些worker进程中,然后这些worker进程就可以通过这个描述符来与新worker进程通信。

/* 源文件: os/unix/ngx_channel.c */
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_channel.h>

/*
    发送消息
    param s   : socket描述符, 是流管道的一端
          ch  : 封装消息内容的结构体指针, 这里存放的是将要发送的消息
          size: ch所指结构体的字节大小
          log : 日志句柄
*/
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 (HAVE_MSGHDR_MSG_CONTROL)
    // 如果系统的msghdr带有msg_control成员, 那么意味着我们可以通过msg_control发送辅助数据
    union {
        struct cmsghdr  cm;
        char            space[CMSG_SPACE(sizeof(int))];
    } cmsg;
    
    if (ch->fd == -1) {
        // 如果消息的fd成员为-1, 那么就不需要发送任何辅助数据
        msg.msg_control = NULL;
        msg.msg_controllen = 0;

    } else {
        // 如果消息的fd成员不为-1, 那么就将此描述符通过辅助数据进行传递
        // SCM_RIGHTS: 辅助数据的类型之一, 用于发送/接收描述符
        msg.msg_control = (caddr_t) &cmsg;
        msg.msg_controllen = sizeof(cmsg);

        cmsg.cm.cmsg_len = sizeof(cmsg);
        cmsg.cm.cmsg_level = SOL_SOCKET; 
        cmsg.cm.cmsg_type = SCM_RIGHTS;
        *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
    }

#else
    // 如果系统的msghdr不带有msg_control成员, 那么就通过msg_accrights成员来进行描述符传递
    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消息设为一个io缓冲区, 以作发送之用 
    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;
    // 调用sendmsg来发送消息
    n = sendmsg(s, &msg, 0);

    if (n == -1) {
        // 发送不成功, 返回出错原因, ngx_errno宏就是errno
        // NGX_EAGAIN宏就是EWOULDBLOCK
        err = ngx_errno;
        if (err == NGX_EAGAIN) {
            return NGX_AGAIN;
        }

        ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");
        return NGX_ERROR;
    }

    return NGX_OK;
}


/*
    接收消息
    param s   : socket描述符, 是流管道的一端
          ch  : 封装消息内容的结构体指针, 这里用来存放接收的消息
          size: ch所指结构体的字节大小
          log : 日志句柄
*/
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 (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 (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
    // 调用recvmsg来接收消息
    n = recvmsg(s, &msg, 0);

    if (n == -1) {
        // 接收不成功, 返回失败原因
        err = ngx_errno;
        if (err == NGX_EAGAIN) {
            return NGX_AGAIN;
        }

        ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed");
        return NGX_ERROR;
    }

    // 接收的消息大小与消息结构体的字节大小不一致, 表明是无效消息
    if ((size_t) n < sizeof(ngx_channel_t)) {
        ngx_log_error(NGX_LOG_ALERT, log, 0,
                      "recvmsg() returned not enough data");
        return NGX_ERROR;
    }

    // 如果消息的指令是NGX_CMD_OPEN_CHANNEL, 那么意味着会进行描述符传递,
    // 那么我们需要从辅助数据或者msg_accrights成员获取该描述符
#if (HAVE_MSGHDR_MSG_CONTROL)
    
    if (ch->command == NGX_CMD_OPEN_CHANNEL) {

        if (cmsg.cm.cmsg_len < sizeof(cmsg)) {
            ngx_log_error(NGX_LOG_ALERT, log, 0,
                          "recvmsg() returned too small ancillary data");
            return NGX_ERROR;
        }

        if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
        {
            ngx_log_error(NGX_LOG_ALERT, log, 0,
                          "recvmsg() returned invalid ancillary data "
                          "level %d or type %d",
                          cmsg.cm.cmsg_level, cmsg.cm.cmsg_type);
            return NGX_ERROR;
        }

        ch->fd = *(int *) CMSG_DATA(&cmsg.cm);
    }

    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)) {
            ngx_log_error(NGX_LOG_ALERT, log, 0, 
                          "recvmsg() returned no ancillary data");
            return NGX_ERROR;
        }

        ch->fd = fd;
    }

#endif

    return n;
}


/*
    关闭通道
*/
void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log)
{
    // 通道分两端, 下面分别关闭这两端
    if (close(fd[0]) == -1) {
        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() failed");
    }

    if (close(fd[1]) == -1) {
        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() failed");
    }
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值