Nginx源码分析与实践---进程间通信机制(套接字)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZX714311728/article/details/65633262

上一篇中,我们看到了nginx共享内存方式的进程间通信。这次我们看下nginx使用套接字的进程间通信方式。


同样的几个问题:

1.什么时候需要使用套接字方式的进程间通信机制呢?

举个栗子:我们知道nginx有master进程和worker进程,那么master进程是如何向worker进程发送消息的呢?worker进程又是如何接收master发送过来的消息呢?答案就是使用套接字。注意的是虽然套接字是双工的,但目前套接字仅用于master进程管理worker进程,而没用于worker发消息给master,或者worker进程间通信(参考:《深入理解Nginx》).


2.nginx的套接字是如何实现的呢?

nginx的套接字是本机套接字,即通过socketpair创建的,不是网络套接字(通过socket创建)。


socketpair:

int socketpair(int family, int type, int protocol, int sockfd[2]);

返回值:成功:0;出错:-1


socketpair仅适用于unix域套接字,因此,family必须为AF_LOCAL,protocol必须为0。type可以为SOCK_STREAM或SOCK_DGRAM,表示使用TCP还是UDP。sockfd[2]是套接字对,套接字是双工的。因此,通常都是父进程fork子进程之前调用socketpair创建套接字对,接着父进程fork子进程,子进程继承套接字对。父进程关闭sockfd[1],子进程关闭sockfd[0]。父进程通过sockfd[0]发送接收消息,子进程通过sockfd[1]发送接收消息。


下面看一个关键函数: ngx_spawn_process

.../os/unix/ngx_process.c:

/* 创建套接字 派生子进程 */
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 */

        /* 创建套接字用于master与worker进程间通信 */
        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;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
                       "channel %d:%d",
                       ngx_processes[s].channel[0],
                       ngx_processes[s].channel[1]);

        /* 套接字设置为非阻塞 */
        if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_nonblocking_n " failed while spawning \"%s\"",
                          name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

	......
	       /* master 创建worker进程 */
	        pid = fork();
	......

我们看到,在ngx_spawn_process函数内,先通过socketpair创建套接字,创建的套接字放在ngx_processes[s].channel内。而ngx_processes是个结构体数组,元素类型是ngx_process_t


.../os/unix/ngx_process.h:

typedef struct {
    ngx_pid_t           pid;
    int                 status;
    ngx_socket_t        channel[2];    /* socketpair创建的套接字对,用于master与worker间通信*/

    ngx_spawn_proc_pt   proc;
    void               *data;
    char               *name;

    unsigned            respawn:1;
    unsigned            just_spawn:1;
    unsigned            detached:1;
    unsigned            exiting:1;
    unsigned            exited:1;
} ngx_process_t;
socketpair创建的套接字正是放在ngx_process_t结构体的channel成员这。因为nginx一个进程是单线程的,每个进程要处理数以万,以几十万计的连接,因此都非常忙,所以关键一点就是nginx的进程一定不能阻塞!!!一阻塞,nginx效率就会大幅度下降。因此,可以看到在ngx_spawn_process函数内部,创建完套接字后,接着就是通过ngx_nonblocking将套接字设置为非阻塞,然后才可以通过fork()创建worker进程。


3.master如何通过套接字向worker发送消息呢?

上面一节介绍了如何创建套接字,下面讲如何通过套接字来发送消息。介绍之前得先知道nginx目前只是将套接字用于master向worker发送命令,而没用于worker间通信。

有了套接字就相当于有了发送命令的窗口,接下来就是要构造包装命令的工具,以及发送接收命令等相配套的方法了。

nginx是通过ngx_channel_t(频道)来包装命令的。master通过频道就可以向worker发送消息。


下面看ngx_channel_t的源码:

.../os/unix/ngx_channel.h

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#ifndef _NGX_CHANNEL_H_INCLUDED_
#define _NGX_CHANNEL_H_INCLUDED_


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>


/* 频道定义 */
typedef struct {
    ngx_uint_t  command;    /* 传递的命令 */
    ngx_pid_t   pid;        /* 发送命令方的进程ID */
    ngx_int_t   slot;       /* 发送命令方在进程数组ngx_processex中的序号 */
    ngx_fd_t    fd;         /* 套接字句柄 */
} ngx_channel_t;

/* 频道相关的方法 */
ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
    ngx_log_t *log);
ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
    ngx_log_t *log);
ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd,
    ngx_int_t event, ngx_event_handler_pt handler);
void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log);


#endif /* _NGX_CHANNEL_H_INCLUDED_ */


ngx_channel_t的command就是要发送的命令,类型有:

.../os/unix/ngx_process_cycle.h:

/* 打开频道,使用频道前必须先发送此命令 */
#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进程了。之后是4个频道配套的方法。

发送频道:

ngx_write_channel:s是创建的套接字,ch是频道,size是频道大小,log是日志对象。可以看到函数通过套接字s发送了频道ch。

接收频道:

ngx_read_channel。参数都一样,注意的是master是用s[0]发送,worker是用s[1]接收。
添加频道事件:

ngx_add_channel_event。nginx的worker进程都是非常繁忙的,如何让worker知道有频道消息过来了呢?方法就是将套接字加入到epoll中,使worker进程既能处理用户请求,又能接收master进程的消息。当woker从epoll读取到相应的套接字,就可以调用ngx_read_channel读取频道消息了。

cycle:进程的结构体;fd:接收套接字,即s[1];event:epoll事件,EPOLLIN;handler:回调函数

关闭频道:

ngx_close_channel。fd:套接字数组,即s[2]


参考:《深入理解Nginx》




猜你喜欢

转载自blog.csdn.net/ZX714311728/article/details/65633262