在上一篇文章Nginx学习之路(八)Nginx中的事件驱动过程详解-----以listenfd注册过程为例中举了listenfd的注册过程来说明事件驱动中的事件注册过程,这是一个简单的过程,今天来说明下当浏览器发起一个http请求时,nginx是如何将这个事件注册到epoll中并处理的:
还记得上一篇文件说明了注册listenfd时,传入参数中的那个rev吧,今天再来详细的说明一下这个rev,在上篇文章中我们提到了rev中最关键的地方就是它的handler,这个handler的作用就是当一个event触发的时候,就会去调用这个handler上注册的回调函数,那么rev上注册的函数是什么呢?看看下面:
rev->handler = ngx_event_accept;
也就是说,当listenfd就绪的时候,也就是有browser发起tcp请求并完成3次握手后,在listen()的连接队列里了,这时,就会调用ngx_event_accept函数,我们来看看这个函数,这个函数很长,我删除一些不重要的部分,只给个缩略班:
void
ngx_event_accept(ngx_event_t *ev)
{
//处理定时器超时,关于定时器的问题后面会细讲
if (ev->timedout) {
if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
return;
}
ev->timedout = 0;
}
lc = ev->data;
ls = lc->listening;
ev->ready = 0;
//关键函数,将listen()就绪队列里的fd,accept出来
s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
if (s == (ngx_socket_t) -1) {
err = ngx_socket_errno;
if (err == NGX_EAGAIN) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
"accept() not ready");
return;
}
level = NGX_LOG_ALERT;
if (err == NGX_ECONNABORTED) {
level = NGX_LOG_ERR;
} else if (err == NGX_EMFILE || err == NGX_ENFILE) {
level = NGX_LOG_CRIT;
}
if (err == NGX_ECONNABORTED) {
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
ev->available--;
}
if (ev->available) {
continue;
}
}
//简单的负载均衡,之前有讲到过
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
//从连接池里获取连接
c = ngx_get_connection(s, ev->log);
//分配内存池
c->pool = ngx_create_pool(ls->pool_size, ev->log);
if (c->pool == NULL) {
ngx_close_accepted_connection(c);
return;
}
//设置IO复用非阻塞
if (ngx_inherited_nonblocking) {
if (ngx_event_flags & NGX_USE_AIO_EVENT) {
if (ngx_blocking(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
ngx_blocking_n " failed");
ngx_close_accepted_connection(c);
return;
}
}
} else {
if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) {
if (ngx_nonblocking(s) == -1) {
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
ngx_nonblocking_n " failed");
ngx_close_accepted_connection(c);
return;
}
}
}
*log = ls->log;
//连接的参数
c->recv = ngx_recv;
c->send = ngx_send;
c->recv_chain = ngx_recv_chain;
c->send_chain = ngx_send_chain;
c->log = log;
c->pool->log = log;
c->socklen = socklen;
c->listening = ls;
c->local_sockaddr = ls->sockaddr;
c->local_socklen = ls->socklen;
c->unexpected_eof = 1;
//关键的部分来了,设置连接的读写事件,读事件就是browser有请求来,accept下来的connfd就绪了,调用的事件,写事件就是nginx这边把数据处理好,要发送给browser时调用的事件
rev = c->read;
wev = c->write;
wev->ready = 1;
if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) {
/* rtsig, aio, iocp */
rev->ready = 1;
}
if (ev->deferred_accept) {
rev->ready = 1;
#if (NGX_HAVE_KQUEUE)
rev->available = 1;
#endif
}
rev->log = log;
wev->log = log;
c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
.
.
.
//关键的又来了,把这个连接注册到epoll中去,就完成了监听
if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
if (ngx_add_conn(c) == NGX_ERROR) {
ngx_close_accepted_connection(c);
return;
}
}
} while (ev->available);
}
这个函数完成的主要功能如下:accept一个连接,调用ngx_add_conn(这个回调的本身是ngx_epoll_module.c中的ngx_epoll_add_connection(ngx_connection_t *c))把连接注册到epoll中去,其余还做了一些负载均衡,filter等操作,这里先不关心它。至此,一起browser端的请求注册到epoll中过程就完成了