在上一篇文章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中過程就完成了