1. 首页
  2. IT资讯

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,这样就达到了数据相互关联的作用,至此,整个事件的注册流程就大致清晰了。

谢谢观看52 记得点赞 · 加油学习 43 · 有问题私信我 9万+

原文始发于:

主题测试文章,只做测试使用。发布者:夏枳★,转转请注明出处:http://www.cxybcw.com/132711.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code