本节主要对最近学习的nginx事件模块做一下总结。通过对nginx整体的学习,觉得nginx的事件模块最主要的两个结构 ngx_connection_t,ngx_event_t,ngx_connection_t结构主要存储tcp连接结构, 对应ngx_event_t结构则存储对应位置上连接的读写事件,nginx的事件模块可以配置,linux下默认用内核提供的epoll机制,本节暂不细讨论epoll机制原理。
对上述两个结构进行了简化,如下:
struct ngx_cycle_s {
……
ngx_connection_t *free_connections;
ngx_uint_t free_connection_n;
ngx_connection_t *connections;
ngx_event_t *read_events;
ngx_event_t *write_events;
……
}
struct ngx_connection_s {
void *data; // 空连接指向 next ;非空不同模块指的结构不同
ngx_event_t *read;
ngx_event_t *write;
ngx_socket_t fd;
ngx_recv_pt recv;
ngx_send_pt send;
……
ngx_listening_t *listening;
……
};
struct ngx_event_s {
// 执行该事件所属的连接结构
void *data;
……
};
结构体之间的大致关系如下图:
每次调用 ngx_get_connection 获取获取新连接时,该连接对应的读、写事件的data指针指向该连接。主要代码如下:
ngx_connection_t * ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
{
……
c = ngx_cycle->free_connections;
……
rev = c->read;
wev = c->write;
……
// 该链接的读、写事件的data指针指向该连接
rev->data = c;
wev->data = c;
return c;
}
ngx_event_process_init函数主要代码如下:
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
……
// 申请连接对象存储
cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
……
// 申请连接对象中读事件存储
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
// 申请连接对象中写事件存储
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
……
// 将上述申请的三个变量关联
do {
i--;
// 连接数组中的每个(void *)data指针指向数组后一个ngx_connection_t结构的地址
c[i].data = next;
// 连接数组中的每个(ngx_event_t *)read、write指针指向 cycle->read_events、 cycle->write_events 数组对应位置的结构
c[i].read = &cycle->read_events[i];
c[i].write = &cycle->write_events[i];
c[i].fd = (ngx_socket_t) -1;
next = &c[i];
} while (i);
// 由于刚开始申请的连接全部还未被用,都为free状态
cycle->free_connections = next;
cycle->free_connection_n = cycle->connection_n;
// 遍历全部的监听socket
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
……
// 为其获取connection连接
c = ngx_get_connection(ls[i].fd, cycle->log);
……
rev = c->read;
……
// 并为其读事件添加回调函数, tcp 为 ngx_event_accept
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg;
……
// 将监听套接字的读事件调用事件添加函数,添加至epoll中
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
}
上述ngx_event_process_init函数调用栈为:main() -> ngx_master_process_cycle() -> ngx_start_worker_processes() -> ngx_worker_process_cycle() -> ngx_worker_process_init();在ngx_worker_process_init函数中会调用所有模块的 init_process 函数指针,主要代码如下:
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
……
for (i = 0; cycle->modules[i]; i++)
{
if (cycle->modules[i]->init_process)
{
if (cycle->modules[i]->init_process(cycle) == NGX_ERROR)
{
/* fatal */
exit(2);
}
}
}
……
}
上述了解到监听套接字的连接(ngx_connection_t)处理及回调函数(ngx_event_accept)的设置。
当进程启动进程后,客户端的http请求nginx监听的端口时会触发监听套接字连接(ngx_connection_t)的读事件,进而调用读事件的handler处理函数(ngx_event_accept函数)去处理。函数主要代码如下:
void ngx_event_accept(ngx_event_t *ev)
{
……
lc = ev->data;
ls = lc->listening;
……
do {
// 调用accept接收新连接
s = accept(lc->fd, &sa.sockaddr, &socklen);
// 新连接申请connection结构
c = ngx_get_connection(s, ev->log);
……
// 定义处理接收、发送数据的回调函数
c->recv = ngx_recv;
c->send = ngx_send;
……
// 调用连接结构connection中listening结构的handler回调方法
ls->handler(c);
……
} while (ev->available);
}
由上述代码简单可知,当 ngx_event_accept 收到新连接时,最终会调用监听connection中listening结构的handler回调函数。该回调函数其实是在解析nginx配置文件时已被初始化;函数调用栈为: main() -> ngx_init_cycle() -> ngx_conf_parse() -> ngx_conf_handler();主要代码如下:
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
……
// 调用对应命令的set函数
rv = cmd->set(cf, cmd, conf);
……
}
当解析到“http”关键字时,会调用对应的set回调函数:ngx_http_block。后续的函数栈调用为:ngx_http_block() -> ngx_http_optimize_servers() -> ngx_http_init_listening() -> ngx_http_add_listening。主要代码如下:
static ngx_listening_t * ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
……
ls = ngx_create_listening(cf, addr->opt.sockaddr, addr->opt.socklen);
……
// 初始化 listen 结构的 handler 指针
ls->handler = ngx_http_init_connection;
……
}
因此,当有客户端的新连接时会调用listen结构handler函数指针指向的函数: ngx_http_init_connection,该函数主要代码如下:
void ngx_http_init_connection(ngx_connection_t *c)
{
// 申请 ngx_http_connection_t 结构
hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
c->data = hc;
……
// 设置读、写事件的回调函数
rev = c->read;
rev->handler = ngx_http_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
……
}
ngx_http_init_connection() 函数主要设置了连接的读写事件的回调函数,当在该连接上接收或者发送数据时会调用回调。该函数主要代码如下:
static void ngx_http_wait_request_handler(ngx_event_t *rev)
{
……
c = rev->data;
……
// 调用c连接的revc回调函数接收数据,c->recv函数指针在ngx_event_accept函数值已被初始化
// 根据系统不同调用不同函数,linux系统会调用ngx_unix_recv函数(封装了recv函数)
n = c->recv(c, b->last, size);
// 后续可以处理接收到的数据
……
}
当一个客户端连接成功后,发送请求数据时,nginx会调用ngx_http_wait_request_handler() 函数接收数据。
而epoll机制是当内核发现有事件发生时调用对应事件的handler回调函数;主要实现函数ngx_epoll_process_events 的代码如下:
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
……
events = epoll_wait(ep, event_list, (int) nevents, timer);
……
// 遍历触发的事件
for (i = 0; i < events; i++) {
c = event_list[i].data.ptr;
……
rev = c->read;
if ((revents & EPOLLIN) && rev->active) {
……
// 调用事件的handler回调函数
rev->handler(rev);
……
}
……
}
}
总结:nginx的ngx_connection_t*连接结构里面有两类连接,一类是:监听套接字的连接,该连接的回调函数为ngx_event_accept(); 另一类是:处理客户端数据的连接,该连接的回调函数为:ngx_http_wait_request_handler()。linux系统epoll机制监听所有的连接;当连接上有事件触发时调用该事件对应的回调函数。