Nginx学习之路(八)Nginx中的事件驱动过程详解-----以listenfd注册过程为例

Nginx的高效得益于它的事件驱动机制,整个事件驱动机制基本框架就是linux下的select,poll,epoll这几个IO多路复用模式,但是nginx绝不单单只是使用它们这么简单,今天以epoll模式为例,从nginx最开始的listenfd的监听的过程来说明nginx是怎么实现的事件驱动。

首先需要说明的是,整个事件模型(event)是一个模块(module),module在nginx中是一个很重要的概念,这也是nginx牛B的地方之一,模块带来的好处就是非常非常棒的水平扩展能力,在nginx上做二次开发基本也是模块的编写,下面我们就来分析下event module:

先来认识下ngx_module_t这个结构体

struct ngx_module_s {  
    ngx_uint_t            ctx_index;      
    /*分类的模块计数器 
    nginx模块可以分为四种:core、event、http和mail 
    每个模块都会各自计数,ctx_index就是每个模块在其所属类组的计数*/  
      
    ngx_uint_t            index;          
    /*一个模块计数器,按照每个模块在ngx_modules[]数组中的声明顺序,从0开始依次给每个模块赋值*/  
  
    ngx_uint_t            spare0;  
    ngx_uint_t            spare1;  
    ngx_uint_t            spare2;  
    ngx_uint_t            spare3;  
  
    ngx_uint_t            version;      //nginx模块版本  
  
    void                 *ctx;            
    /*模块的上下文,不同种类的模块有不同的上下文,因此实现了四种结构体*/  
      
    ngx_command_t        *commands;  
    /*命令定义地址 
    模块的指令集 
    每一个指令在源码中对应着一个ngx_command_t结构变量*/  
      
    ngx_uint_t            type;         //模块类型,用于区分core event http和mail  
  
    ngx_int_t           (*init_master)(ngx_log_t *log);         //初始化master时执行  
  
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);     //初始化module时执行  
  
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);    //初始化process时执行  
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);     //初始化thread时执行  
    void                (*exit_thread)(ngx_cycle_t *cycle);     //退出thread时执行  
    void                (*exit_process)(ngx_cycle_t *cycle);    //退出process时执行  
  
    void                (*exit_master)(ngx_cycle_t *cycle);     //退出master时执行  
  
//空闲的钩子函数 
    uintptr_t             spare_hook0;  
    uintptr_t             spare_hook1;  
    uintptr_t             spare_hook2;  
    uintptr_t             spare_hook3;  
    uintptr_t             spare_hook4;  
    uintptr_t             spare_hook5;  
    uintptr_t             spare_hook6;  
    uintptr_t             spare_hook7;  
};  
  
typedef struct ngx_module_s      ngx_module_t;  

这里面最重要的就是那几个回调函数(init_master到exit_master之间),比如init_master任务,在ngx_module.c中有个全局变量:

ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    &ngx_errlog_module,
    &ngx_conf_module,
    &ngx_dso_module,
    &ngx_conf_extend_module,
    &ngx_syslog_module,
...
}

在ngx_worker_process_init中就会从这里面去调用各种module的init_master方法:

   for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_process) {
            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }

那么如何判定到底采用哪一种IO复用方式呢?

在ngx_event.c中有如下声明:

extern ngx_module_t ngx_kqueue_module;
extern ngx_module_t ngx_eventport_module;
extern ngx_module_t ngx_devpoll_module;
extern ngx_module_t ngx_epoll_module;
extern ngx_module_t ngx_rtsig_module;
extern ngx_module_t ngx_select_module;

这些声明以及配置文件中的相应部分设定用来确定你用哪一个IO复用的模块,那么事件是如何注册的呢?

关于事件注册的最重要的结构体如下:

typedef struct {
    ngx_str_t              *name;

    void                 *(*create_conf)(ngx_cycle_t *cycle);
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);

    ngx_event_actions_t     actions;
} ngx_event_module_t;

typedef struct {
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

    ngx_int_t  (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                   ngx_uint_t flags);

    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;

我们以epoll为例,来看下epoll的ngx_module_t是怎么实现的:

ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
        NULL,                            /* process the changes */
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

那么,调用到这些注册的方法的过程是怎样的呢?我们以ngx_epoll_add_event为例,在ngx_worker_process_init的listenfd注册到epoll的过程如下:

还记得刚刚说到的init_master那个回调吗,我们来看看epoll中的ngx_module_t

ngx_module_t  ngx_event_core_module = {
    NGX_MODULE_V1,
    &ngx_event_core_module_ctx,            /* module context */
    ngx_event_core_commands,               /* module directives */
    NGX_EVENT_MODULE,                      /* module type */
    NULL,                                  /* init master */
    ngx_event_module_init,                 /* init module */
    ngx_event_process_init,                /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

可以看到在之前说明的过程中,会调用调用ngx_event_process_init函数,在这个函数中

if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
                return NGX_ERROR;
            }

至此就完成了listenfd注册到epoll的过程,在这里要说明下这个rev,说到rev就要说明一下nginx中连接(connection)的概念,nginx中注册到epoll中的epoll_event里的data部分是指向一个连接的,nginx中是提前分配好了一个连接池,每次需要连接的时候就从连接池里拿一个出来,连接池的东西我们后面再细讲,那么rev是怎么跟连接关联起来的呢,我们来看下rev的结构体:

struct ngx_event_s {
    void            *data;//关注这个指针,这个指针通常都是指向一个连接
.
.
.
    ngx_event_handler_pt  handler;//最需要关注的是这个handler,这个handler就是这个event被调用是所调用的函数
.
.
.
};
这是个简化的结构体,我只写出了要关注的部分,再来看下connection的结构体

struct ngx_connection_s {  
    //连接未使用时候,data域充当连接链表中的next指针.  
    //当连接被使用时候,data域的意义由模块而定.  
    void               *data;  
    //连接对应的读事件,这个read指针就指向了刚刚代码中的rev 
    ngx_event_t        *read;  
    //连接对应的写事件  
    ngx_event_t        *write;  
  
    //套接字句柄  
    ngx_socket_t        fd;  
  
    //直接接收网络字节流的方法  
    ngx_recv_pt         recv;  
    //直接放松网络字节流的方法  
    ngx_send_pt         send;  
    //以ngx_chain链表为参数,接收网络字节流的方法  
    ngx_recv_chain_pt   recv_chain;  
    //以ngx_chain链表为参数,发送网络字节流的方法  
    ngx_send_chain_pt   send_chain;  
  
    //这个链接对应的listening_t监听对象.  
    //此链接由ngx_listening_t监听的事件建立  
    ngx_listening_t    *listening;  
  
    //这个连接已经发送出去的字节数  
    off_t               sent;  
  
    //记录日志  
    ngx_log_t          *log;  
  
    //在accept一个新连接的时候,会创建一个内存池,而这个连接结束时候,会销毁一个内存池.  
    //这里所说的连接是成功建立的tcp连接.内存池的大小由pool_size决定  
    //所有的ngx_connect_t结构体都是预分配的  
    ngx_pool_t         *pool;  
  
    //连接客户端的结构体  
    struct sockaddr    *sockaddr;  
    //连接客户端的结构体长度  
    socklen_t           socklen;  
    //连接客户端的ip(字符串形式)  
    ngx_str_t           addr_text;  
  
#if (NGX_SSL)  
    ngx_ssl_connection_t  *ssl;  
#endif  
  
    //本机中监听端口对应的socketaddr结构体  
    //也就是listen监听对象中的socketaddr成员  
    struct sockaddr    *local_sockaddr;  
  
    //用于接收和缓存客户端发来的字符流  
    ngx_buf_t          *buffer;  
  
    //该字段表示将该连接以双向链表形式添加到cycle结构体中的  
    //reusable_connections_queen双向链表中,表示可以重用的连接.  
    ngx_queue_t         queue;  
  
    //连接使用次数,每次建立一条来自客户端的连接,  
    //或者建立一条与后端服务器的连接,number+1  
    ngx_atomic_uint_t   number;  
  
    //处理请求的次数  
    ngx_uint_t          requests;  
  
    //  
    unsigned            buffered:8;  
  
    //日志级别  
    unsigned            log_error:3;     /* ngx_connection_log_error_e */  
  
    //不期待字符流结束  
    unsigned            unexpected_eof:1;  
    //连接超时  
    unsigned            timedout:1;  
    //连接处理过程中出现错误  
    unsigned            error:1;  
    //标识此链接已经销毁,内存池,套接字等都不可用  
    unsigned            destroyed:1;  
  
    //连接处于空闲状态  
    unsigned            idle:1;  
    //连接可以重用  
    unsigned            reusable:1;  
    //连接关闭  
    unsigned            close:1;  
  
    //正在将文件中的数据法网另一端  
    unsigned            sendfile:1;  
    //连接中发送缓冲区的数据高于低潮,才发送数据.  
    //与ngx_handle_write_event方法中的lowat相对应  
    unsigned            sndlowat:1;  
    //使用tcp的nodely特性  
    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */  
    //使用tcp的nopush特性  
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */
。
。
。
}

也就是说,connection中有read指针指向了read事件rev,同事rev也有指针指向了对应的connection,这样就达到了数据相互关联的作用,至此,整个事件的注册流程就大致清晰了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章