nginx源码分析1———进程间的通信机制六(UNIX域协议)

相关介绍

Unix域协议并不是一个实际的协议族,而是在单机上客户端与服务器通信的一种方法。但是可以通过与网络通信中使用的相同插口API来访问它们。当然也和TCP/IP有很多不同之处。
网际TCP/IP协议:当客户进程通过TCP往服务器进程发送数据时,数据首先由TCP输出处理,然后再经过IP输出处理,最后发往环回驱动器(lo),在环回驱动器中(lo),数据首先被放到IP输入队列,然后经过IP输入和TCP输入处理,最后传送到服务器。这样工作很好,并且对于在相同主机上的对等端来说是透明的。然而,在TCP/IP协议栈里需要大量的处理过程,当数据没有离开主机时,这些处理过程实际上是不需要的。
UNIX域协议:UNIX域协议由于知道数据不会离开主机,所以只需要较少的处理过程,这样书记传输的更快。不需要进行检验和的计算和验证,数据也不会失序,由于内核能控制客服进程和服务器进程的执行过程,流量控制也被大大简化了,等等。UNIX域的优点还在于它们可以使用的接口与网络程序使用的接口完全一样。

相关系统调用

#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);

family参数必须为AF_UNIX(AF_LOCAL)。
type参数可以是SOCK_STREAM(TCP),也可以是SOCK_DGRAM(UDP)
protocol参数为0
sockfd新创建的两个套接字作为sockfd返回

相关结构

struct msghdr {
    void         *msg_name;     
    socklen_t     msg_namelen;    
    struct iovec *msg_iov;        
    size_t        msg_iovlen;    
    void         *msg_control;    
    socklen_t     msg_controllen; 
    int           msg_flags;      
};

1,msg_namemsg_namelen用于未连接的场景,msg_name指向套接字的地址结构,msg_namelen用于指定地址结构的长度,当使用TCP或者已经连接的UDP的时候,msg_name应该为空,msg_namelen应该为0。
2,msg_iovmsg_iovlen指定我将要发送的数据,由于数据是数组形式的,所以需要msg_iovlen来指定数组的长度。
3,msg_controlmsg_controllen是发送辅助数据的结构和辅助数据结构长度

typedef struct {
     ngx_uint_t  command;
     ngx_pid_t   pid;
     ngx_int_t   slot;
     ngx_fd_t    fd;
} ngx_channel_t;

ngx_channel_t是用于发送消息的内容。

struct cmsghdr{
    socklen_t cmsg_len;
    int cmsg_level;
    int cmsg_type;
    /* followed by nsigned char cmsg_data[] */
};

cmsghdr是发送控制数据的结构体,cmsg_len指定辅助数据的长度,cmsg_levelcmsg_type指定辅助数据的等级与辅助数据的类型,最后还会跟上辅助数据,也就是上面注释的地方。
我们会用到辅助数据的类型如下

协议cmsg_levelcmsg_type说明
UNIX域SOL_SOCKETSCM_RIGHTS发送或者接受描述符
UNIX域SOL_SOCKETSCM_CREDS发送或者接受用户凭证

创建

ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn)
{
   .../* 省略 */
        /* Solaris 9 still has no AF_LOCAL */
        //创建套接字,其中channel就是一个int数组
        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;
        }
        //在创建成功以后将调用fcntl和ioctl进行设置描述符控制操作,然后进行fork创建多进程。
   .../* 省略 */
}

写数据

nginx写数据采用套接字api,使用里面的sendmsg发送数据。

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;
    union {
        struct cmsghdr  cm;
        char            space[CMSG_SPACE(sizeof(int))];
    } cmsg;
    //如果不需要传递fd,那么也不需要辅助数据。
    if (ch->fd == -1) {
        msg.msg_control = NULL;
        msg.msg_controllen = 0;
    //如果需要传递fd,那么需要辅助数据,
    } else {
        //指定辅助数据地址与长度
        msg.msg_control = (caddr_t) &cmsg;
        msg.msg_controllen = sizeof(cmsg);

        //初始化cmsg结构
        ngx_memzero(&cmsg, sizeof(cmsg));

        //由于只需要辅助数据传递描述符,那么指定为描述符的大小
        cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));
        //指定为套接层
        cmsg.cm.cmsg_level = SOL_SOCKET;
        //指定type为SCM_RIGHTS,表示发送和接受描述符
        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
         */
        //将fd拷贝到辅助数据data的第一个字节上面
        ngx_memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));
    }


    msg.msg_flags = 0;

    //将ngx_channel_t(将要发送消息内容)给msg_iov
    iov[0].iov_base = (char *) ch;
    //并指定ngx_channel_t长度
    iov[0].iov_len = size;

    //这两项设置为空,只有在未连接的时候才需要
    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    //指定msg_iov,并指定其数组大小为1,因为只传递一个msg_iov
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    //sendmsg
    n = sendmsg(s, &msg, 0);

    if (n == -1) {
        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;
}

读取数据

nginx读数据采用套接字api,使用里面的recvmsg发送数据。

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;

    union {
        struct cmsghdr  cm;
        char space[CMSG_SPACE(sizeof(int))];
    } cmsg;

    //设定数据结构,让recvmsg去填充
    iov[0].iov_base = (char *) ch;
    iov[0].iov_len = size;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    //因为每次发送只有一个,所以msg_iovlen为1就可以收到
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    //设置接受辅助数据的结构,以供于填充
    msg.msg_control = (caddr_t) &cmsg;
    msg.msg_controllen = sizeof(cmsg);

    n = recvmsg(s, &msg, 0);
    //recvmsg出错
    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 (n == 0) {
        ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "recvmsg() returned zero");
        return NGX_ERROR;
    }
    //数据不够
    if ((size_t) n < sizeof(ngx_channel_t)) {
        ngx_log_error(NGX_LOG_ALERT, log, 0,
                      "recvmsg() returned not enough data: %z", n);
        return NGX_ERROR;
    }

    //解析数据,command相当于nginx自定义传输的命令,如果命令是打开套接字,需要获取辅助数据获取套接字。
    if (ch->command == NGX_CMD_OPEN_CHANNEL) {
        //如果控制数据不够
        if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
            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;
        }
        //将辅助数据拷贝到ngx_channel_t,来填充ngx_channel_t的fd参数
        /* 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");
    }
    return n;
}

关闭

关闭就是关闭创建时生成的一对fd,最后普及一下基础知识,close只是对描述符的引用减1操作。

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() channel failed");
    }

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

总结

unix域套接字主要用于nginx中master与worker的通信,他们之间的通信用这个最合适不过了,如果用网际tcp会性能特别低。在ngx_channel.c中,还有ngx_add_channel_event函数没有讲解,主要是将套接字放到epoll中去,将在以后讲解。

相关参考资料

1,UNIX网络编程卷1
2,TCP/IP详解卷3
3,man手册

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值