前言
本小节将分析事件模块中事件的定义以及连接池等相关定义,方便我们后面对事件模块中的模块进行分析。
事件的定义
每个事件由ngx_event_s
表示:
struct ngx_event_s {
/* 跟事件相关的对象。通常指向ngx_connection_t连接
* 当开启了文件异步I/O之后,它可能会指向ngx_event_aio_t结构体
*/
void *data;
/* 这种结构体成员中冒号的作用是位压缩
* 这是为了节约空间
* unsigned write:1代表该变量只占1位的空间
* 而不是占unsigned int大小
*/
/* 读事件标识位
* 为1表示事件可写
* 即此时可以发送数据
*/
unsigned write:1;
/* 可建立新连接标识位 */
unsigned accept:1;
/* used to detect the stale events in kqueue, rtsig, and epoll */
/* 用于区分过期事件 */
unsigned instance:1;
/*
* the event was passed or would be passed to a kernel;
* in aio mode - operation was posted.
*/
/* 表示当前事件是否活跃 */
unsigned active:1;
/* 为1表示禁用该事件,在epoll模块中无用 */
unsigned disabled:1;
/* the ready event; in aio mode 0 means that no operation can be posted */
/* 为1表示当前事件已经准备就绪
* 即该事件可以被处理
*/
unsigned ready:1;
/* 在epoll模块中无用 */
unsigned oneshot:1;
/* aio operation is complete */
/* 该标识位用于异步AIO事件的处理 */
unsigned complete:1;
/* 为1表示事件当前处理的字符流已经结束 */
unsigned eof:1;
/* 为1表示事件在处理过程中出现错误 */
unsigned error:1;
/* 为1表示该事件已经超时 */
unsigned timedout:1;
/* 为1表示该事件存在定时器中 */
unsigned timer_set:1;
/* 为1表示该事件会被延迟处理 */
unsigned delayed:1;
/* 该标识位目前未使用 */
unsigned read_discarded:1;
/* 该标识位目前未使用 */
unsigned unexpected_eof:1;
/* 为1表示延迟建立tcp连接
* 即就算三次握手之后也不立即建立连接
* 而是等到有数据包发送时才建立
*/
unsigned deferred_accept:1;
/* the pending eof reported by kqueue or in aio chain operation */
/* 在epoll模块无用 */
unsigned pending_eof:1;
#if !(NGX_THREADS)
/* 为1表示在处理post事件时,当前事件已经准备就绪 */
unsigned posted_ready:1;
#endif
#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
*/
#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
int available;
#else
/* 为1表示在epoll事件驱动机制下,一次尽可能多建立tcp连接 */
unsigned available:1;
#endif
/* 该事件发生时的处理方法 */
ngx_event_handler_pt handler;
#if (NGX_HAVE_AIO)
#if (NGX_HAVE_IOCP)
ngx_event_ovlp_t ovlp;
#else
/* linux aio机制中定义的结构体 */
struct aiocb aiocb;
#endif
#endif
/* epoll模块中未用 */
ngx_uint_t index;
/* 用于记录error_log日志的ngx_log_t对象 */
ngx_log_t *log;
/* 定时器节点 */
ngx_rbtree_node_t timer;
/* 为1表示当前事件已经关闭,epoll模块中未用 */
unsigned closed:1;
/* to test on worker exit */
/* 当前并没有使用 */
unsigned channel:1;
unsigned resolver:1;
/* 并没有使用多线程
* 因此这几个标识位跳过
*/
#if (NGX_THREADS)
unsigned locked:1;
unsigned posted_ready:1;
unsigned posted_timedout:1;
unsigned posted_eof:1;
#if (NGX_HAVE_KQUEUE)
/* the pending errno reported by kqueue */
int posted_errno;
#endif
#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
int posted_available;
#else
unsigned posted_available:1;
#endif
ngx_atomic_t *lock;
ngx_atomic_t *own_lock;
#endif
/* the links of the posted queue */
/* post事件会形成一个队列再一次进行处理
* 这是一个双向链表
* next指向下一个事件的地址
* prev指向上一个事件的地址
*/
ngx_event_t *next;
ngx_event_t **prev;
#if 0
/* the threads support */
/*
* the event thread context, we store it here
* if $(CC) does not understand __thread declaration
* and pthread_getspecific() is too costly
*/
void *thr_ctx;
#if (NGX_EVENT_T_PADDING)
/* event should not cross cache line in SMP */
uint32_t padding[NGX_EVENT_T_PADDING];
#endif
#endif
};
以上便是关于事件的定义,nginx的事件不需要创建。
在初始化过程中会读取nginx.conf文件中”events {}”中的worker_connections
配置(我默认的nginx.conf中worker_connections的值是1024),即指读/写事件的最大值为1024(同时也代表每个worker进程的最大连接数是1024),然后在ngx_event_process_init
中申请读事件和写事件的空间,并初始化读/写事件,完成预分配。
每一个读和写事件都对应了一个连接,每个连接也是预分配好的,还记得ngx_cycle_t
中的*connections
、*read_events
、*write_events
吗,connections
即存储所有连接的数组,read_events
即存储读事件的数组,write_events
即存储写事件的数组。
从main
起始的调用的流程图:
当需要向I/O多路复用机制中注册事件的时候,我们只需要从连接池中取一个空闲的连接出来,该连接对应了读和写事件,而ngx_cycle_t
中的*free_connections
指向连接池中空闲连接的起始处。将读/写事件添加到I/O多路复用机制中采用的方法是:
ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
ngx_int_t ngx_handler_write_event(ngx_event_t *wev, size_t lowat)
rev
参数代表需要进行操作的事件,flags
参数指定事件的驱动方式;
wev
参数代表需要进行操作的事件,lowat
表示只有当连接对应的套接字缓冲区必须至少有lowat大小的可用空间时,该事件才能被epoll处理(lowat
为0时不考虑可写缓冲区的大小)
连接的定义
接下来,介绍一下nginx中关于连接的定义。nginx中的连接主要分别主动连接和被动连接两种,被动连接是指客户端发起,而服务端被动接受的连接;主动连接则是指nginx主动发起连接,而其他服务器被动接受的连接。
定义被动连接的结构体在core/ngx_connection.h
中
- 被动连接:
typedef struct ngx_connection_s ngx_connection_t;
struct ngx_connection_s {
//当连接未使用时,data成员用于充当连接池中空闲连接链表中的next指针
//当该连接被使用时,具体的作用视具体的模块而定
void *data;
//该连接对应的读事件
ngx_event_t *read;
//该连接对应的写事件
ngx_event_t *write;
//套接字句柄
ngx_socket_t fd;
//接受字符流的方法
ngx_recv_pt recv;
//发送字符流的方法
ngx_send_pt send;
//以ngx_chain_t链表为参数来接受字符流的方法
ngx_recv_chain_pt recv_chain;
//以ngx_chain_t链表为参数来发送字符流的方法
ngx_send_chain_pt send_chain;
//该连接对应的ngx_listening_t监听对象
//该连接是由listening监听端口的事件所建立的
ngx_listening_t *listening;
//已经发送的字节数
off_t sent;
//记录日志的ngx_log_t对象
ngx_log_t *log;
//内存池,每一个连接在建立之后,都会创建一个内存池(这里的建立是指成功建立tcp连接之后,而不是预分配的时候就建立内存池)
//在该连接结束时,该内存池会被销毁
//内存池的大小由listening成员监听对象中的pool_size决定
ngx_pool_t *pool;
//连接客户端的sockaddr结构体
struct sockaddr *sockaddr;
//sockaddr结构体的长度
socklen_t socklen;
//连接客户端的ip地址(以字符串的形式表示)
ngx_str_t addr_text;
#if (NGX_SSL)
ngx_ssl_connection_t *ssl;
#endif
//本机上监听端口对应的sockaddr结构体
//其实也就是指listening监听对象中的sockaddr成员
struct sockaddr *local_sockaddr;
//接受字符流的缓存
ngx_buf_t *buffer;
//该成员将当前连接以双向链表的节点的形式添加到ngx_cycle_t核心结构体的reusable_connections_queue双向链表中
//表示该连接可重用
ngx_queue_t queue;
//该连接使用的次数(不论是被动连接还是主动连接,只要使用了该连接,number都会加1)
ngx_atomic_uint_t number;
//处理的请求次数
ngx_uint_t requests;
//缓存中的业务类型。这里目前略过。
unsigned buffered:8;
/* 表示该连接记录日志时的级别
* 它只占用了3位,目前所支持的级别如下:
* typedef enum {
* NGX_ERROR_ALERT = 0,
* NGX_ERROR_ERR,
* NGX_ERROR_INFO,
* NGX_ERROR_IGNORE_ECONNRESET,
* NGX_ERROR_IGNORE_EINVAL
* } ngx_connections_log_error_e;
*/
unsigned log_error:3; /* ngx_connection_log_error_e */
//为1时表示为独立的连接,比如从客户端发生的连接
unsigned single_connection:1;
//为1表示不期待字符流结束,目前无意义
unsigned unexpected_eof:1;
//为1表示连接超时
unsigned timedout:1;
//为1表示连接处理中发生错误
unsigned error:1;
//为1表示对应的tcp连接已经销毁
unsigned destroyed:1;
//为1表示连接处于空闲状态
unsigned idle:1;
//为1表示连接可重用
unsigned reusable:1;
//为1表示连接关闭
unsigned close:1;
//为1表示正在把文件中的数据发往另一端
unsigned sendfile:1;
//为1表示只有当连接套接字对应的发送缓冲区满足最低设置的大小阀值时,该事件才可能被触发
unsigned sndlowat:1;
//表示如何使用tcp的nodelay特性
unsigned tcp_nodelay:2; /* ngx_connection_tcp_nodelay_e */
//表示如何使用tcp的nopush特性
unsigned tcp_nopush:2; /* ngx_connection_tcp_nopush_e */
#if (NGX_HAVE_IOCP)
unsigned accept_context_updated:1;
#endif
#if (NGX_HAVE_AIO_SENDFILE)
//为1时表示使用异步I/O的方式将磁盘上的文件发送给连接的另一端
unsigned aio_sendfile:1;
//使用异步I/O方式发送的文件,该缓冲区保存待发送文件的信息
ngx_buf_t *busy_sendfile;
#endif
#if (NGX_THREADS)
ngx_atomic_t lock;
#endif
};
以上便是被动连接的表示,接下来是主动连接(event/ngx_event_connec.h):
- 主动连接
typedef struct ngx_peer_connection_s ngx_peer_connection_t;
struct ngx_peer_connection_s {
//主动连接也需要被动连接ngx_connection_t结构体的大部分成员
ngx_connection_t *connection;
//远端服务器的socket地址
struct sockaddr *sockaddr;
//sockaddr地址的长度
socklen_t socklen;
//远端服务器的名称
ngx_str_t *name;
//连接异常时,允许的重新尝试连接的次数
ngx_uint_t tries;
//获取连接的方法
ngx_event_get_peer_pt get;
//与get对应的释放连接的方法
ngx_event_free_peer_pt free;
//配合get还有free传递参数
void *data;
#if (NGX_SSL)
ngx_event_set_peer_session_pt set_session;
ngx_event_save_peer_session_pt save_session;
#endif
#if (NGX_THREADS)
ngx_atomic_t *lock;
#endif
//本机地址信息
ngx_addr_t *local;
//接受缓冲区大小
int rcvbuf;
//记录日志的ngx_log_t对象
ngx_log_t *log;
//为1表示connection连接已经缓存
unsigned cached:1;
/* ngx_connection_log_error_e */
unsigned log_error:2;
};
关于连接池
前面提到过,nginx会预先分配好connections
、read_events
、write_events
这几个数组,并且会预先初始化好。对于连接池来说,当需要连接的时候,分配出去即可,剩下的则是空闲连接,因此ngx_cycle_t
核心结构体中有一个free_connections
成员专门指向空闲连接的首部,在连接是空闲的时候,使用ngx_connection_t
里的data
成员将空闲连接连接起来。
下面根据代码来说明(event/ngx_event.c):
......
//给connections数组申请空间
cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
if(cycle->connections == NULL) {
return NGX_ERROR;
}
c = cycle->connections;
//给读事件数组申请空间
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
if (cycle->read_events == NULL) {
return NGX_ERROR;
}
//初始化读事件
rev = cycle->read_events;
for (i = 0; i < cycle->connection_n; i++) {
rev[i].closed = 1;
rev[i].instance = 1;
#if (NGX_THREADS)
rev[i].lock = &c[i].lock;
rev[i].own_lock = &c[i].lock;
#endif
}
//给写事件数组申请空间
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log);
if (cycle->write_events == NULL) {
return NGX_ERROR;
}
//初始化写事件
wev = cycle->write_events;
for (i = 0; i < cycle->connection_n; i++) {
wev[i].closed = 1;
#if (NGX_THREADS)
wev[i].lock = &c[i].lock;
wev[i].own_lock = &c[i].lock;
#endif
}
i = cycle->connection_n;
next = NULL;
/* 初始化connections数组 */
do {
i--;
/* 在初始化阶段,连接数组中所有连接都是空闲的
* 因此使用data将所有空闲连接连接起来
*/
c[i].data = next;
/* read对应read_events中的其中一个读事件 */
c[i].read = &cycle->read_events[i];
/* write对应write_events中的其中一个写事件 */
c[i].write = &cycle->write_events[i];
//将fd置为-1
c[i].fd = (ngx_socket_t) -1;
//由于遍历的顺序是从后往前,因此将next指向当前的connection元素
next = &c[i];
#if (NGX_THREADS)
c[i].lock = 0;
#endif
} while (i);
//free_connections指向第一个空闲连接
cycle->free_connections = next;
//free_connection_n即空闲连接的总数
cycle->free_connection_n = cycle->connection_n;
如何将连接、读事件、写事件一一对应起来,这点其实在代码中就已经表达的很清楚了,即通过简单的下标就可以完成一一对应。
这里引用一下《深入理解Nginx》书中的图帮助理解:
小结
nginx中关于事件还有连接的定义在本小节中预览了一下,它们有很多状态位,不可能一次就全部记住,在后面的代码分析中再进行理解学习。nginx的连接都是预分配好了的,需要连接时,直接从connections
数组中获取即可,用完了之后再归还就行了。并且每个连接都对应了读、写这两种事件,取得连接之后,相应的读、写事件也会被注册到I/O多路复用机制中。
关于connections
的元素个数,是由nginx.conf中的connections配置项决定的,我的配置文件中是1024。
下一小节,我们就可以正式进入到ngx_event_core_module模块中了。