nginx&http 第二章 ngx 事件event处理 数据结构
阅读原文时间:2023年07月09日阅读:1

ngx_event.c :这个文件主要放置Nginx事件event模块的核心代码。

包含:进程事件分发器(ngx_process_events_and_timers)、事件模块的模块和配置、模块初始化/配置初始化等事件模块初始化的核心函数。

ngx_event_timer.c:定时器事件管理。主要放置定时器的代码。

ngx_event_posted.c:主要用于 拿到accept锁的进程 处理accept和read事件的回调函数。

ngx_event_pipe.c:主要用于处理管道

ngx_event_openssl.c:主要用于处理SSL通道。HTTPS协议

ngx_event_connect.c:主要用于处理TCP的连接通道。

ngx_event_accept.c:核心是ngx_event_accept和ngx_event_recvmsg,主要是处理accept事件的回调函数handler。

而后续的read事件被ngx_event_accept中回调ngx_listen_t结构中的ls->handler回调函数回调,并且将rev->handler修改成ngx_http_wait_request_handler方法。
modules/xxxx.c:主要封装了各种平台的事件模型。这边主要看ngx_epoll_module.c模块。

重要数据结构

下面几个数据结构会在event模块中非常常见,必须得非常熟悉。

ngx_listening_s:主要是监听套接字结构,存放socket的信息;ngx_listening_t结构体代表着Nginx服务器监听的一个端口;实际上这些ngx_listening_s结构体是从 cycle->listening.elts中来的,见ngx_event_process_init

ngx_connection_s:存储连接有关的信息和读写事件。一个ngx_connection_s对应一个ngx_event_s read和一个ngx_event_s write,其中事件的fd是从ngx_connection_s->fd获取,他们
                              在ngx_worker_process_init->ngx_event_process_init中关联起来 ;ngx_event_t事件和ngx_connection_t连接是处理TCP连接的基础数据结构,

通过ngx_get_connection从连接池中获取一个ngx_connection_s结构,被动连接(客户端连接nginx)对应的数 据结构是ngx_connection_s,主动连接(nginx连接后端服务器)对应的数据结构是ngx_peer_connection_s

ngx_event_s:主要存放事件的数据结构。cycle->read_events和cycle->write_events这两个数组存放的是ngx_event_s,他们是对应的,见ngx_event_process_init ngx_event_t事件和ngx_connection_t连接是一一对应的
ngx_event_t事件和ngx_connection_t连接是处理TCP连接的基础数据结构, 在Nginx中,每一个事件都由ngx_event_t结构体来表示
1.ngx_event_s可以是普通的epoll读写事件(参考ngx_event_connect_peer->ngx_add_conn或者ngx_add_event),通过读写事件触发
2.也可以是普通定时器事件(参考ngx_cache_manager_process_handler->ngx_add_timer(ngx_event_add_timer)),通过ngx_process_events_and_timers中的
epoll_wait返回,可以是读写事件触发返回,也可能是因为没获取到共享锁,从而等待0.5s返回重新获取锁来跟新事件并执行超时事件来跟新事件并且判断定
时器链表中的超时事件,超时则执行从而指向event的handler,然后进一步指向对应r或者u的->write_event_handler read_event_handler
3.也可以是利用定时器expirt实现的读写事件(参考ngx_http_set_write_handler->ngx_add_timer(ngx_event_add_timer)),触发过程见2,只是在handler中不会执行write_event_handler read_event_handler

ngx_connection_s:

struct ngx_connection_s { //cycle->read_events和cycle->write_events这两个数组存放的是ngx_event_s,他们是对应的,见ngx_event_process_init
/*
连接未使用时,data成员用于充当连接池中空闲连接链表中的next指针(ngx_event_process_init)。当连接被使用时,data的意义由使用它的Nginx模块而定,
如在HTTP框架中,data指向ngx_http_request_t请求

在服务器端accept客户端连接成功(ngx\_event\_accept)后,会通过ngx\_get\_connection从连接池获取一个ngx\_connection\_t结构,也就是每个客户端连接对于一个ngx\_connection\_t结构,  
并且为其分配一个ngx\_http\_connection\_t结构,ngx\_connection\_t->data = ngx\_http\_connection\_t,见ngx\_http\_init\_connection  
 \*/ 

/*
在子请求处理过程中,上层父请求r的data指向第一个r下层的子请求,例如第二层的r->connection->data指向其第三层的第一个
创建的子请求r,c->data = sr见ngx_http_subrequest,在subrequest往客户端发送数据的时候,只有data指向的节点可以先发送出去
listen过程中,指向原始请求ngx_http_connection_t(ngx_http_init_connection ngx_http_ssl_handshake),接收到客户端数据后指向ngx_http_request_t(ngx_http_wait_request_handler)
http2协议的过程中,在ngx_http_v2_connection_t(ngx_http_v2_init)
*/
void *data; /* 如果是subrequest,则data最终指向最下层子请求r,见ngx_http_subrequest */
//如果是文件异步i/o中的ngx_event_aio_t,则它来自ngx_event_aio_t->ngx_event_t(只有读),如果是网络事件中的event,则为ngx_connection_s中的event(包括读和写)
ngx_event_t *read;//连接对应的读事件 赋值在ngx_event_process_init,空间是从ngx_cycle_t->read_event池子中获取的
ngx_event_t *write; //连接对应的写事件 赋值在ngx_event_process_init 一般在ngx_handle_write_event中添加些事件,空间是从ngx_cycle_t->read_event池子中获取的

ngx\_socket\_t        fd;//套接字句柄  
/\* 如果启用了ssl,则发送和接收数据在ngx\_ssl\_recv ngx\_ssl\_write ngx\_ssl\_recv\_chain ngx\_ssl\_send\_chain \*/  
//服务端通过ngx\_http\_wait\_request\_handler读取数据  
ngx\_recv\_pt         recv; //直接接收网络字符流的方法  见ngx\_event\_accept或者ngx\_http\_upstream\_connect   赋值为ngx\_os\_io  在接收到客户端连接或者向上游服务器发起连接后赋值  
ngx\_send\_pt         send; //直接发送网络字符流的方法  见ngx\_event\_accept或者ngx\_http\_upstream\_connect   赋值为ngx\_os\_io  在接收到客户端连接或者向上游服务器发起连接后赋值

/\* 如果启用了ssl,则发送和接收数据在ngx\_ssl\_recv ngx\_ssl\_write ngx\_ssl\_recv\_chain ngx\_ssl\_send\_chain \*/  
//以ngx\_chain\_t链表为参数来接收网络字符流的方法  ngx\_recv\_chain  
ngx\_recv\_chain\_pt   recv\_chain;  //赋值见ngx\_event\_accept     ngx\_event\_pipe\_read\_upstream中执行  
//以ngx\_chain\_t链表为参数来发送网络字符流的方法    ngx\_send\_chain  
//当http2头部帧发送的时候,会在ngx\_http\_v2\_header\_filter把ngx\_http\_v2\_send\_chain.send\_chain=ngx\_http\_v2\_send\_chain  
ngx\_send\_chain\_pt   send\_chain; //赋值见ngx\_event\_accept   ngx\_http\_write\_filter和ngx\_chain\_writer中执行

//这个连接对应的ngx\_listening\_t监听对象,通过listen配置项配置,此连接由listening监听端口的事件建立,赋值在ngx\_event\_process\_init  
//接收到客户端连接后会冲连接池分配一个ngx\_connection\_s结构,其listening成员指向服务器接受该连接的listen信息结构,见ngx\_event\_accept  
ngx\_listening\_t    \*listening; //实际上是从cycle->listening.elts中的一个ngx\_listening\_t   

off\_t               sent;//这个连接上已经发送出去的字节数 //ngx\_linux\_sendfile\_chain和ngx\_writev\_chain没发送多少字节就加多少字节

ngx\_log\_t          \*log;//可以记录日志的ngx\_log\_t对象 其实就是ngx\_listening\_t中获取的log //赋值见ngx\_event\_accept

/\*  
内存池。一般在accept -个新连接时,会创建一个内存池,而在这个连接结束时会销毁内存池。注意,这里所说的连接是指成功建立的  
TCP连接,所有的ngx\_connection\_t结构体都是预分配的。这个内存池的大小将由listening监听对象中的pool\_size成员决定  
 \*/  
ngx\_pool\_t         \*pool; //在accept返回成功后创建poll,见ngx\_event\_accept, 连接上游服务区的时候在ngx\_http\_upstream\_connect创建

struct sockaddr    \*sockaddr; //连接客户端的sockaddr结构体  客户端的,本端的为下面的local\_sockaddr 赋值见ngx\_event\_accept  
socklen\_t           socklen; //sockaddr结构体的长度  //赋值见ngx\_event\_accept  
ngx\_str\_t           addr\_text; //连接客户端字符串形式的IP地址  

ngx\_str\_t           proxy\_protocol\_addr;

#if (NGX_SSL)
ngx_ssl_connection_t *ssl; //赋值见ngx_ssl_create_connection
#endif

//本机的监听端口对应的sockaddr结构体,也就是listening监听对象中的sockaddr成员  
struct sockaddr    \*local\_sockaddr; //赋值见ngx\_event\_accept  
socklen\_t           local\_socklen;

/\*  
用于接收、缓存客户端发来的字符流,每个事件消费模块可自由决定从连接池中分配多大的空间给buffer这个接收缓存字段。  
例如,在HTTP模块中,它的大小决定于client\_header\_buffer\_size配置项  
 \*/  
ngx\_buf\_t          \*buffer; //ngx\_http\_request\_t->header\_in指向该缓冲去

/\*  
该字段用来将当前连接以双向链表元素的形式添加到ngx\_cycle\_t核心结构体的reusable\_connections\_queue双向链表中,表示可以重用的连接  
 \*/  
ngx\_queue\_t         queue;

/\*  
连接使用次数。ngx\_connection t结构体每次建立一条来自客户端的连接,或者用于主动向后端服务器发起连接时(ngx\_peer\_connection\_t也使用它),  
number都会加l  
 \*/  
ngx\_atomic\_uint\_t   number; //这个应该是记录当前连接是整个连接中的第几个连接,见ngx\_event\_accept  ngx\_event\_connect\_peer

ngx\_uint\_t          requests; //处理的请求次数

/\*  
缓存中的业务类型。任何事件消费模块都可以自定义需要的标志位。这个buffered字段有8位,最多可以同时表示8个不同的业务。第三方模  
块在自定义buffered标志位时注意不要与可能使用的模块定义的标志位冲突。目前openssl模块定义了一个标志位:  
    #define NGX\_SSL\_BUFFERED    Ox01

    HTTP官方模块定义了以下标志位:  
    #define NGX HTTP\_LOWLEVEL\_BUFFERED   0xf0  
    #define NGX\_HTTP\_WRITE\_BUFFERED       0x10  
    #define NGX\_HTTP\_GZIP\_BUFFERED        0x20  
    #define NGX\_HTTP\_SSI\_BUFFERED         0x01  
    #define NGX\_HTTP\_SUB\_BUFFERED         0x02  
    #define NGX\_HTTP\_COPY\_BUFFERED        0x04  
    #define NGX\_HTTP\_IMAGE\_BUFFERED       Ox08  
同时,对于HTTP模块而言,buffered的低4位要慎用,在实际发送响应的ngx\_http\_write\_filter\_module过滤模块中,低4位标志位为1则惫味着  
Nginx会一直认为有HTTP模块还需要处理这个请求,必须等待HTTP模块将低4位全置为0才会正常结束请求。检查低4位的宏如下:  
    #define NGX\_LOWLEVEL\_BUFFERED  OxOf  
 \*/  
unsigned            buffered:8; //不为0,表示有数据没有发送完毕,ngx\_http\_request\_t->out中还有未发送的报文

/\*  
 本连接记录日志时的级别,它占用了3位,取值范围是0-7,但实际上目前只定义了5个值,由ngx\_connection\_log\_error\_e枚举表示,如下:  
typedef enum{  
    NGXi ERROR—AIERT=0,  
    NGX' ERROR ERR,  
    NGX  ERROR\_INFO,  
    NGX, ERROR IGNORE ECONNRESET,  
    NGX ERROR—IGNORE EIb:fVAL  
 }  
 ngx\_connection\_log\_error\_e ;  
 \*/  
unsigned            log\_error:3;     /\* ngx\_connection\_log\_error\_e \*/

//标志位,为1时表示不期待字符流结束,目前无意义  
unsigned            unexpected\_eof:1;

//每次处理完一个客户端请求后,都会ngx\_add\_timer(rev, c->listening->post\_accept\_timeout);  
/\*读客户端连接的数据,在ngx\_http\_init\_connection(ngx\_connection\_t \*c)中的ngx\_add\_timer(rev, c->listening->post\_accept\_timeout)把读事件添加到定时器中,如果超时则置1  
  每次ngx\_unix\_recv把内核数据读取完毕后,在重新启动add epoll,等待新的数据到来,同时会启动定时器ngx\_add\_timer(rev, c->listening->post\_accept\_timeout);  
  如果在post\_accept\_timeout这么长事件内没有数据到来则超时,开始处理关闭TCP流程\*/  
  //当ngx\_event\_t->timedout置1的时候,该置也同时会置1,参考ngx\_http\_process\_request\_line  ngx\_http\_process\_request\_headers  
  //在ngx\_http\_free\_request中如果超时则会设置SO\_LINGER来减少time\_wait状态  
unsigned            timedout:1; //标志位,为1时表示连接已经超时,也就是过了post\_accept\_timeout多少秒还没有收到客户端的请求  
unsigned            error:1; //标志位,为1时表示连接处理过程中出现错误

/\*  
 标志位,为1时表示连接已经销毁。这里的连接指是的TCP连接,而不是ngx\_connection t结构体。当destroyed为1时,ngx\_connection\_t结  
 构体仍然存在,但其对应的套接字、内存池等已经不可用  
 \*/  
unsigned            destroyed:1; //ngx\_http\_close\_connection中置1

unsigned            idle:1; //为1时表示连接处于空闲状态,如keepalive请求中丽次请求之间的状态  
unsigned            reusable:1; //为1时表示连接可重用,它与上面的queue字段是对应使用的  
unsigned            close:1; //为1时表示连接关闭  
/\*  
    和后端的ngx\_connection\_t在ngx\_event\_connect\_peer这里置为1,但在ngx\_http\_upstream\_connect中c->sendfile &= r->connection->sendfile;,  
    和客户端浏览器的ngx\_connextion\_t的sendfile需要在ngx\_http\_update\_location\_config中判断,因此最终是由是否在configure的时候是否有加  
    sendfile选项来决定是置1还是置0  
 \*/  
//赋值见ngx\_http\_update\_location\_config  
unsigned            sendfile:1; //标志位,为1时表示正在将文件中的数据发往连接的另一端

/\*  
标志位,如果为1,则表示只有在连接套接字对应的发送缓冲区必须满足最低设置的大小阅值时,事件驱动模块才会分发该事件。这与上文  
介绍过的ngx\_handle\_write\_event方法中的lowat参数是对应的  
 \*/  
unsigned            sndlowat:1; //ngx\_send\_lowat

/\*  
标志位,表示如何使用TCP的nodelay特性。它的取值范围是下面这个枚举类型ngx\_connection\_tcp\_nodelay\_e。  
typedef enum{  
NGX\_TCP\_NODELAY\_UNSET=O,  
NGX\_TCP\_NODELAY\_SET,  
NGX\_TCP\_NODELAY\_DISABLED  
)  ngx\_connection\_tcp\_nodelay\_e;  
 \*/  
unsigned            tcp\_nodelay:2;   /\* ngx\_connection\_tcp\_nodelay\_e \*/ //域套接字默认是disable的,

/\*  
标志位,表示如何使用TCP的nopush特性。它的取值范围是下面这个枚举类型ngx\_connection\_tcp\_nopush\_e:  
typedef enum{  
NGX\_TCP\_NOPUSH\_UNSET=0,  
NGX\_TCP\_NOPUSH\_SET,  
NGX\_TCP\_NOPUSH\_DISABLED  
)  ngx\_connection\_tcp\_nopush\_e  
 \*/ //域套接字默认是disable的,  
unsigned            tcp\_nopush:2;    /\* ngx\_connection\_tcp\_nopush\_e \*/

unsigned            need\_last\_buf:1;

#if (NGX_HAVE_IOCP)
unsigned accept_context_updated:1;
#endif

/*
#if (NGX HAVE AIO- SENDFILE)
//标志位,为1时表示使用异步I/O的方式将磁盘上文件发送给网络连接的另一端
unsigned aio一sendfile:l;
//使用异步I/O方式发送的文件,busy_sendfile缓冲区保存待发送文件的信息
ngx_buf_t *busy_sendf ile;
#endif
*/
#if (NGX_HAVE_AIO_SENDFILE)
unsigned busy_count:2;
#endif

#if (NGX_THREADS)
ngx_thread_task_t *sendfile_task;
#endif
};

ngx_listening_s:

struct ngx_listening_s { //初始化及赋值见ngx_http_add_listening 热升级nginx的时候,继承源master listen fd在ngx_set_inherited_sockets
ngx_socket_t fd; //socket套接字句柄 //赋值见ngx_open_listening_sockets

struct sockaddr    \*sockaddr; //监听sockaddr地址  
socklen\_t           socklen;    /\* size of sockaddr \*/ //sockaddr地址长度

//存储IP地址的字符串addr\_text最大长度,即它指定了addr\_text所分配的内存大小  
size\_t              addr\_text\_max\_len; //  
//如果listen 80;或者listen \*:80;则该地址为0.0.0.0  
ngx\_str\_t           addr\_text;//以字符串形式存储IP地址和端口 例如 A.B.C.D:E     3.3.3.3:23  赋值见ngx\_set\_inherited\_sockets

int                 type;//套接字类型。例如,当type是SOCK\_STREAM时,表示TCP

//TCP实现监听时的backlog队列,它表示允许正在通过三次握手建立TCP连接但还没有任何进程开始处理的连接最大个数,默认NGX\_LISTEN\_BACKLOG  
int                 backlog; //  
int                 rcvbuf;//内核中对于这个套接字的接收缓冲区大小  
int                 sndbuf;//内核中对于这个套接字的发送缓冲区大小  

#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int keepidle;
int keepintvl;
int keepcnt;
#endif

/\* handler of accepted connection \*/  
//当新的TCP accept连接成功建立后的处理方法  ngx\_connection\_handler\_pt粪型的handler成员表示在这个监听端口上成功建立新的TCP连接后,就会回调handler方法  
ngx\_connection\_handler\_pt   handler; //赋值为ngx\_http\_init\_connection,见ngx\_http\_add\_listening。该handler在ngx\_event\_accept中执行  
/\*  
实际上框架并不使用servers指针,它更多是作为一个保留指针,目前主要用于HTTP或者mail等模块,用于保存当前监听端口对应着的所有主机名  
\*/  
void               \*servers;  /\* array of ngx\_http\_in\_addr\_t  ngx\_http\_port\_t, for example \*/ //赋值见ngx\_http\_init\_listening,指向ngx\_http\_port\_t结构

//log和logp都是可用的日志对象的指针  
ngx\_log\_t           log; //见ngx\_http\_add\_listening  
ngx\_log\_t          \*logp;

size\_t              pool\_size;//如果为新的TCP连接创建内存池,则内存池的初始大小应该是pool\_size      见ngx\_http\_add\_listening  
/\* should be here because of the AcceptEx() preread \*/

size\_t              post\_accept\_buffer\_size;  
/\* should be here because of the deferred accept \*/  
/\*  
TCP\_DEFER ACCEPT选项将在建立TCP连接成功且接收到用户的请求数据后,才向对监听套接字感兴趣的进程发送事件通知,而连接建立成功后,  
如果post\_accept\_timeout秒后仍然没有收到的用户数据,则内核直接丢弃连接  
\*/ //ls->post\_accept\_timeout = cscf->client\_header\_timeout;  "client\_header\_timeout"设置  
ngx\_msec\_t          post\_accept\_timeout; //见ngx\_http\_add\_listening

/\* 前一个ngx\_listening\_t结构,多个ngx\_listening\_t结构体之间由previous指针组成单链表 \*/  
ngx\_listening\_t    \*previous; //这个表示在热启动nginx进程的时候,在最新启动前的上一个nginx的所有listen信息  
//当前监听句柄对应着的ngx\_connection\_t结构体  
ngx\_connection\_t   \*connection; 

ngx\_uint\_t          worker;

//下面这些标志位一般在ngx\_init\_cycle中初始化赋值  
/\*  
标志位,为1则表示在当前监听句柄有效,且执行ngx- init—cycle时不关闭监听端口,为0时则正常关闭。该标志位框架代码会自动设置  
\*/  
unsigned            open:1;  
/\*  
标志位,为1表示使用已有的ngx\_cycle\_t来初始化新的ngx\_cycle\_t结构体时,不关闭原先打开的监听端口,这对运行中升级程序很有用,  
remaln为o时,表示正常关闭曾经打开的监听端口。该标志位框架代码会自动设置,参见ngx\_init\_cycle方法  
\*/  
unsigned            remain:1;  
/\*  
标志位,为1时表示跳过设置当前ngx\_listening\_t结构体中的套接字,为o时正常初始化套接字。该标志位框架代码会自动设置  
\*/  
unsigned            ignore:1;

//表示是否已经绑定。实际上目前该标志位没有使用  
unsigned            bound:1;       /\* already bound \*/  
/\* 表示当前监听句柄是否来自前一个进程(如升级Nginx程序),如果为1,则表示来自前一个进程。一般会保留之前已经设置好的套接字,不做改变 \*/  
unsigned            inherited:1;   /\* inherited from previous process \*/ //说明是热升级过程  
unsigned            nonblocking\_accept:1;  //目前未使用  
//lsopt.bind = 1;这里面存的是bind为1的配置才会有创建ngx\_http\_port\_t  
unsigned            listen:1; //标志位,为1时表示当前结构体对应的套接字已经监听  赋值见ngx\_open\_listening\_sockets  
unsigned            nonblocking:1;//表素套接字是否阻塞,目前该标志位没有意义  
unsigned            shared:1;    /\* shared between threads or processes \*/ //目前该标志位没有意义

//标志位,为1时表示Nginx会将网络地址转变为字符串形式的地址  见addr\_text 赋值见ngx\_http\_add\_listening,当在ngx\_create\_listening把listen的IP地址转换为字符串地址后置1  
unsigned            addr\_ntop:1;  

#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
unsigned ipv6only:1;
#endif
#if (NGX_HAVE_REUSEPORT)
//赋值见ngx_http_add_listening
//master进程执行ngx_clone_listening中如果配置了多worker,监听80端口会有worker个listen赋值,master进程在ngx_open_listening_sockets
//中会监听80端口worker次,那么子进程创建起来后,不是每个字进程都关注这worker多个 listen事件了吗?为了避免这个问题,nginx通过
//在子进程运行ngx_event_process_init函数的时候,通过ngx_add_event来控制子进程关注的listen,最终实现只关注master进程中创建的一个listen事件
unsigned reuseport:1;
unsigned add_reuseport:1;
#endif
unsigned keepalive:2;

#if (NGX_HAVE_DEFERRED_ACCEPT)
unsigned deferred_accept:1;//SO_ACCEPTFILTER(freebsd所用)设置 TCP_DEFER_ACCEPT(LINUX系统所用)
unsigned delete_deferred:1;

unsigned            add\_deferred:1; //SO\_ACCEPTFILTER(freebsd所用)设置  TCP\_DEFER\_ACCEPT(LINUX系统所用)  

#ifdef SO_ACCEPTFILTER
char *accept_filter;
#endif
#endif
#if (NGX_HAVE_SETFIB)
int setfib;
#endif

#if (NGX_HAVE_TCP_FASTOPEN)
int fastopen;
#endif

};

ngx_event_t:

struct ngx_event_s {
/*
事件相关的对象。通常data都是指向ngx_connection_t连接对象,见ngx_get_connection。开启文件异步I/O时,它可能会指向ngx_event_aio_t(ngx_file_aio_init)结构体
*/
void *data; //赋值见ngx_get_connection

//标志位,为1时表示事件是可写的。通常情况下,它表示对应的TCP连接目前状态是可写的,也就是连接处于可以发送网络包的状态  
unsigned         write:1; //见ngx\_get\_connection,可写事件ev默认为1  读ev事件应该默认还是0

//标志位,为1时表示为此事件可以建立新的连接。通常情况下,在ngx\_cycle\_t中的listening动态数组中,每一个监听对象ngx\_listening\_t对  
//应的读事件中的accept标志位才会是l  ngx\_event\_process\_init中置1  
unsigned         accept:1;

/\*  
这个标志位用于区分当前事件是否是过期的,它仅仅是给事件驱动模块使用的,而事件消费模块可不用关心。为什么需要这个标志位呢?  
当开始处理一批事件时,处理前面的事件可能会关闭一些连接,而这些连接有可能影响这批事件中还未处理到的后面的事件。这时,  
可通过instance标志位来避免处理后面的已经过期的事件。将详细描述ngx\_epoll\_module是如何使用instance标志位区分  
过期事件的,这是一个巧妙的设计方法

    instance标志位为什么可以判断事件是否过期?instance标志位的使用其实很简单,它利用了指针的最后一位一定  
是0这一特性。既然最后一位始终都是0,那么不如用来表示instance。这样,在使用ngx\_epoll\_add\_event方法向epoll中添加事件时,就把epoll\_event中  
联合成员data的ptr成员指向ngx\_connection\_t连接的地址,同时把最后一位置为这个事件的instance标志。而在ngx\_epoll\_process\_events方法中取出指向连接的  
ptr地址时,先把最后一位instance取出来,再把ptr还原成正常的地址赋给ngx\_connection\_t连接。这样,instance究竟放在何处的问题也就解决了。  
那么,过期事件又是怎么回事呢?举个例子,假设epoll\_wait -次返回3个事件,在第  
    1个事件的处理过程中,由于业务的需要,所以关闭了一个连接,而这个连接恰好对应第3个事件。这样的话,在处理到第3个事件时,这个事件就  
已经是过期辜件了,一旦处理必然出错。既然如此,把关闭的这个连接的fd套接字置为一1能解决问题吗?答案是不能处理所有情况。  
    下面先来看看这种貌似不可能发生的场景到底是怎么发生的:假设第3个事件对应的ngx\_connection\_t连接中的fd套接字原先是50,处理第1个事件  
时把这个连接的套接字关闭了,同时置为一1,并且调用ngx\_free\_connection将该连接归还给连接池。在ngx\_epoll\_process\_events方法的循环中开始处  
理第2个事件,恰好第2个事件是建立新连接事件,调用ngx\_get\_connection从连接池中取出的连接非常可能就是刚刚释放的第3个事件对应的连接。由于套  
接字50刚刚被释放,Linux内核非常有可能把刚刚释放的套接字50又分配给新建立的连接。因此,在循环中处理第3个事件时,这个事件就是过期的了!它对应  
的事件是关闭的连接,而不是新建立的连接。  
    如何解决这个问题?依靠instance标志位。当调用ngx\_get\_connection从连接池中获取一个新连接时,instance标志位就会置反  
 \*/  
/\* used to detect the stale events in kqueue and epoll \*/  
unsigned         instance:1; //ngx\_get\_connection从连接池中获取一个新连接时,instance标志位就会置反  //见ngx\_get\_connection

/\*  
 \* the event was passed or would be passed to a kernel;  
 \* in aio mode - operation was posted.  
 \*/  
/\*  
标志位,为1时表示当前事件是活跃的,为0时表示事件是不活跃的。这个状态对应着事件驱动模块处理方式的不同。例如,在添加事件、  
删除事件和处理事件时,active标志位的不同都会对应着不同的处理方式。在使用事件时,一般不会直接改变active标志位  
 \*/  //ngx\_epoll\_add\_event中也会置1  在调用该函数后,该值一直为1,除非调用ngx\_epoll\_del\_event  
unsigned         active:1; //标记是否已经添加到事件驱动中,避免重复添加  在server端accept成功后,  
//或者在client端connect的时候把active置1,见ngx\_epoll\_add\_connection。第一次添加epoll\_ctl为EPOLL\_CTL\_ADD,如果再次添加发  
//现active为1,则epoll\_ctl为EPOLL\_CTL\_MOD

/\*  
标志位,为1时表示禁用事件,仅在kqueue或者rtsig事件驱动模块中有效,而对于epoll事件驱动模块则无意义,这里不再详述  
 \*/  
unsigned         disabled:1;

/\* the ready event; in aio mode 0 means that no operation can be posted \*/  
/\*  
标志位,为1时表示当前事件已经淮备就绪,也就是说,允许这个事件的消费模块处理这个事件。在  
HTTP框架中,经常会检查事件的ready标志位以确定是否可以接收请求或者发送响应  
ready标志位,如果为1,则表示在与客户端的TCP连接上可以发送数据;如果为0,则表示暂不可发送数据。  
 \*/ //如果来自对端的数据内核缓冲区没有数据(返回NGX\_EAGAIN),或者连接断开置0,见ngx\_unix\_recv  
 //在发送数据的时候,ngx\_unix\_send中的时候,如果希望发送1000字节,但是实际上send只返回了500字节(说明内核协议栈缓冲区满,需要通过epoll再次促发write的时候才能写),或者链接异常,则把ready置0  
unsigned         ready:1; //在ngx\_epoll\_process\_events中置1,读事件触发并读取数据后ngx\_unix\_recv中置0

/\*  
该标志位仅对kqueue,eventport等模块有意义,而对于Linux上的epoll事件驱动模块则是无意叉的,限于篇幅,不再详细说明  
 \*/  
unsigned         oneshot:1;

/\* aio operation is complete \*/  
//aio on | thread\_pool方式下,如果读取数据完成,则在ngx\_epoll\_eventfd\_handler(aio on)或者ngx\_thread\_pool\_handler(aio thread\_pool)中置1  
unsigned         complete:1; //表示读取数据完成,通过epoll机制返回获取 ,见ngx\_epoll\_eventfd\_handler

//标志位,为1时表示当前处理的字符流已经结束  例如内核缓冲区没有数据,你去读,则会返回0  
unsigned         eof:1; //见ngx\_unix\_recv  
//标志位,为1时表示事件在处理过程中出现错误  
unsigned         error:1;

//标志位,为I时表示这个事件已经超时,用以提示事件的消费模块做超时处理  
/\*读客户端连接的数据,在ngx\_http\_init\_connection(ngx\_connection\_t \*c)中的ngx\_add\_timer(rev, c->listening->post\_accept\_timeout)把读事件添加到定时器中,如果超时则置1  
  每次ngx\_unix\_recv把内核数据读取完毕后,在重新启动add epoll,等待新的数据到来,同时会启动定时器ngx\_add\_timer(rev, c->listening->post\_accept\_timeout);  
  如果在post\_accept\_timeout这么长事件内没有数据到来则超时,开始处理关闭TCP流程\*/

/\*  
读超时是指的读取对端数据的超时时间,写超时指的是当数据包很大的时候,write返回NGX\_AGAIN,则会添加write定时器,从而判断是否超时,如果发往  
对端数据长度小,则一般write直接返回成功,则不会添加write超时定时器,也就不会有write超时,写定时器参考函数ngx\_http\_upstream\_send\_request  
 \*/  
unsigned         timedout:1; //定时器超时标记,见ngx\_event\_expire\_timers  
//标志位,为1时表示这个事件存在于定时器中  
unsigned         timer\_set:1; //ngx\_event\_add\_timer ngx\_add\_timer 中置1   ngx\_event\_expire\_timers置0

//标志位,delayed为1时表示需要延迟处理这个事件,它仅用于限速功能  
unsigned         delayed:1; //限速见ngx\_http\_write\_filter  

/\*  
 标志位,为1时表示延迟建立TCP连接,也就是说,经过TCP三次握手后并不建立连接,而是要等到真正收到数据包后才会建立TCP连接  
 \*/  
unsigned         deferred\_accept:1; //通过listen的时候添加 deferred 参数来确定

/\* the pending eof reported by kqueue, epoll or in aio chain operation \*/  
//标志位,为1时表示等待字符流结束,它只与kqueue和aio事件驱动机制有关  
//一般在触发EPOLLRDHUP(当对端已经关闭,本端写数据,会引起该事件)的时候,会置1,见ngx\_epoll\_process\_events  
unsigned         pending\_eof:1; 

/\*  
if (c->read->posted) { //删除post队列的时候需要检查  
    ngx\_delete\_posted\_event(c->read);  
}  
 \*/  
unsigned         posted:1; //表示延迟处理该事件,见ngx\_epoll\_process\_events -> ngx\_post\_event  标记是否在延迟队列里面  
//标志位,为1时表示当前事件已经关闭,epoll模块没有使用它  
unsigned         closed:1; //ngx\_close\_connection中置1

/\* to test on worker exit \*/  
//这两个该标志位目前无实际意义  
unsigned         channel:1;  
unsigned         resolver:1;

unsigned         cancelable:1;

#if (NGX_WIN32)
/* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was successful */
unsigned accept_context_updated:1;
#endif

#if (NGX_HAVE_KQUEUE)
unsigned kq_vnode:1;

/\* the pending errno reported by kqueue \*/  
int              kq\_errno;  

#endif

/\*  
 \* kqueue only:  
 \*   accept:     number of sockets that wait to be accepted  
 \*   read:       bytes to read when event is ready  
 \*               or lowat when event is set with NGX\_LOWAT\_EVENT flag  
 \*   write:      available space in buffer when event is ready  
 \*               or lowat when event is set with NGX\_LOWAT\_EVENT flag  
 \*  
 \* iocp: TODO  
 \*  
 \* otherwise:  
 \*   accept:     1 if accept many, 0 otherwise  
 \*/

//标志住,在epoll事件驱动机制下表示一次尽可能多地建立TCP连接,它与multi_accept配置项对应,实现原理基见9.8.1节
#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
int available;
#else
unsigned available:1; //ngx_event_accept中 ev->available = ecf->multi_accept;
#endif
/*
每一个事件最核心的部分是handler回调方法,它将由每一个事件消费模块实现,以此决定这个事件究竟如何“消费”
*/

/\*  
1.event可以是普通的epoll读写事件(参考ngx\_event\_connect\_peer->ngx\_add\_conn或者ngx\_add\_event),通过读写事件触发

2.也可以是普通定时器事件(参考ngx\_cache\_manager\_process\_handler->ngx\_add\_timer(ngx\_event\_add\_timer)),通过ngx\_process\_events\_and\_timers中的  
epoll\_wait返回,可以是读写事件触发返回,也可能是因为没获取到共享锁,从而等待0.5s返回重新获取锁来跟新事件并执行超时事件来跟新事件并且判断定  
时器链表中的超时事件,超时则执行从而指向event的handler,然后进一步指向对应r或者u的->write\_event\_handler  read\_event\_handler

3.也可以是利用定时器expirt实现的读写事件(参考ngx\_http\_set\_write\_handler->ngx\_add\_timer(ngx\_event\_add\_timer)),触发过程见2,只是在handler中不会执行write\_event\_handler  read\_event\_handler  
\*/

//这个事件发生时的处理方法,每个事件消费模块都会重新实现它  
//ngx\_epoll\_process\_events中执行accept  
/\*  
 赋值为ngx\_http\_process\_request\_line     ngx\_event\_process\_init中初始化为ngx\_event\_accept  如果是文件异步i/o,赋值为ngx\_epoll\_eventfd\_handler  
 //当accept客户端连接后ngx\_http\_init\_connection中赋值为ngx\_http\_wait\_request\_handler来读取客户端数据  
 在解析完客户端发送来的请求的请求行和头部行后,设置handler为ngx\_http\_request\_handler  
 \*/ //一般与客户端的数据读写是 ngx\_http\_request\_handler;  与后端服务器读写为ngx\_http\_upstream\_handler(如fastcgi proxy memcache gwgi等)

/\* ngx\_event\_accept,ngx\_http\_ssl\_handshake ngx\_ssl\_handshake\_handler ngx\_http\_v2\_write\_handler ngx\_http\_v2\_read\_handler  
ngx\_http\_wait\_request\_handler  ngx\_http\_request\_handler,ngx\_http\_upstream\_handler ngx\_file\_aio\_event\_handler \*/  
ngx\_event\_handler\_pt  handler; //由epoll读写事件在ngx\_epoll\_process\_events触发

#if (NGX_HAVE_IOCP)
ngx_event_ovlp_t ovlp;
#endif
//由于epoll事件驱动方式不使用index,所以这里不再说明
ngx_uint_t index;
//可用于记录error_log日志的ngx_log_t对象
ngx_log_t *log; //可以记录日志的ngx_log_t对象 其实就是ngx_listening_t中获取的log //赋值见ngx_event_accept
//定时器节点,用于定时器红黑树中
ngx_rbtree_node_t timer; //见ngx_event_timer_rbtree

/\* the posted queue \*/  
/\*  
post事件将会构成一个队列再统一处理,这个队列以next和prev作为链表指针,以此构成一个简易的双向链表,其中next指向后一个事件的地址,  
prev指向前一个事件的地址  
 \*/  
ngx\_queue\_t      queue;

};

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章