Nginx學習之路(九)Nginx中的事件驅動過程詳解-----connection事件的註冊過程
阿新 • • 發佈:2018-12-25
在上一篇文章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中過程就完成了