1. 程式人生 > >Nginx學習之路(九)Nginx中的事件驅動過程詳解-----connection事件的註冊過程

Nginx學習之路(九)Nginx中的事件驅動過程詳解-----connection事件的註冊過程

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