1. 程式人生 > >四:深入Nginx之事件和連線 (之一)

四:深入Nginx之事件和連線 (之一)

Nginx 本質上是基於事件驅動的Web伺服器,事件 處理框架所要解決的問題就是如何收集、管理、分發事件。
事件主要 以

  • 網路事件(TCP網路事件為主 )
  • 定時器事件

Nginx 定義一個 核心模組ngx_event_module,參考部落格一深入理解Nginx的 模組化 ,全域性觀,Nginx在啟動時會呼叫ngx_init_cycle方法 解析配置檔案時,一旦 在nginx.conf中找到感興趣的是”events {}”配置項,ngx_event_module模組開始工作了。在 核心模組ngx_event_module中的ngx_event_core_module模組定義了這個模組會使用哪種事件驅動機制以及如何管理 事件。

事件 模組是 一種新的模組型別,nginx_module_t表示Nginx模組介面,而針對每一種 不同型別的模組,都有一個結構體來描述這一類模組的通用介面,這個介面儲存在ngx_module_t結構體的 ctx成員中。核心 模組的 通用介面是ngx_core_module_t,事件通用 介面 是 ngx_event_module_t結構 體:

typedef struct {
//位於檔案 ngx_event.h
   //事件模組 的名字
    ngx_str_t              *name;
    //在解析 配置前,這個回撥 方法用於建立儲存配置選項引數的結構體
    void
*(*create_conf)(ngx_cycle_t *cycle); // 在解析配置項完成 之後,init_conf方法會被呼叫,用以綜合處理當前事件模組感興趣的全部配置項 char *(*init_conf)(ngx_cycle_t *cycle, void *conf); // 對於事件驅動機制,每個事件模組需要實現10個抽象方法 ngx_event_actions_t actions; } ngx_event_module_t;

ngx_event_module_t中的actions

成員是定義事件驅動模組的核心方法。

typedef struct {
    /* 新增事件方法,它將負責把1個感興趣 事件新增到操作 系統提供的事件驅動機制 */
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*刪除事件方法,它將1個已經存在於事件驅動機制中的事件移除 */
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*啟用 1個 事件,目前事件框架不會呼叫 這個 方法,大部分事件驅動模組對於該方法的實現都是與上面的add方法完全一致的*/
    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*禁用1個事件,目前事件框架不會 呼叫這個方法 ,大部分事件驅動器對於這個方法的實現與上面的del方法完全一致*/
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    /*向事件驅動機制中新增一個新的連線,這意味著連線上的讀寫事件都新增到事件驅動機制中*/
    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    /*從事件驅動機制中移除一個連線的讀寫事件*/
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
    /*僅在多執行緒環境下會被呼叫,目前Nginx在產品環境下還不會以多執行緒方式執行*/
    ngx_int_t  (*notify)(ngx_event_handler_pt handler);
    /*在正常的工作迴圈中,將呼叫process_events方法來 處理事件*/
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                                 ngx_uint_t flags);
   /*初始化事件驅動模組的方法*/
    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    // 退出事件驅動模組前呼叫的方法
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;

ngx_event_core_module 和9個事件驅動模組都必須ngx_module_t結構體的ctx成員 實現ngx_event_module_t 介面

struct ngx_event_s {
    /*事件相關的物件,通常data指向ngx_connection_t連線物件。開啟檔案非同步I/O 時,它可能會指向*/
    void            *data;
     /* 標誌位,標識事件可寫,意味著對應的TCP連線可寫,也即連線處於傳送網路包狀態 */ 
    unsigned         write:1;
     /* 標誌位,標識可建立新的連線,一般是在ngx_listening_t對應的讀事件中標記 */
    unsigned         accept:1;
     /*檢測當前事件是否是過期的,它僅僅是給驅動模組使用的,而事件消費模組可以不用關心 */
    /* used to detect the stale events in kqueue and epoll */
    unsigned         instance:1;

    /*
     * the event was passed or would be passed to a kernel;
     * in aio mode - operation was posted.
     */
    unsigned         active:1;

    unsigned         disabled:1;

    /* the ready event; in aio mode 0 means that no operation can be posted */
    unsigned         ready:1;

    unsigned         oneshot:1;

    /* aio operation is complete */
    unsigned         complete:1;

    unsigned         eof:1;
    unsigned         error:1;

    unsigned         timedout:1;
    unsigned         timer_set:1;

    unsigned         delayed:1;

    unsigned         deferred_accept:1;

    /* the pending eof reported by kqueue, epoll or in aio chain operation */
    unsigned         pending_eof:1;

    unsigned         posted:1;

    unsigned         closed:1;

    /* to test on worker exit */
    unsigned         channel:1;
    unsigned         resolver:1;

    unsigned         cancelable:1;

#if (NGX_WIN32)
    /* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was successful */
    unsigned         accept_context_updated:1;
#endif

#if (NGX_HAVE_KQUEUE)
    unsigned         kq_vnode:1;

    /* the pending errno reported by kqueue */
    int              kq_errno;
#endif

    /*
     * kqueue only:
     *   accept:     number of sockets that wait to be accepted
     *   read:       bytes to read when event is ready
     *               or lowat when event is set with NGX_LOWAT_EVENT flag
     *   write:      available space in buffer when event is ready
     *               or lowat when event is set with NGX_LOWAT_EVENT flag
     *
     * epoll with EPOLLRDHUP:
     *   accept:     1 if accept many, 0 otherwise
     *   read:       1 if there can be data to read, 0 otherwise
     *
     * iocp: TODO
     *
     * otherwise:
     *   accept:     1 if accept many, 0 otherwise
     */

#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
    int              available;
#else
    unsigned         available:1;
#endif

    ngx_event_handler_pt  handler;


#if (NGX_HAVE_IOCP)
    ngx_event_ovlp_t ovlp;
#endif

    ngx_uint_t       index;

    ngx_log_t       *log;

    ngx_rbtree_node_t   timer;

    /* the posted queue */
    ngx_queue_t      queue;

#if 0

    /* the threads support */

    /*
     * the event thread context, we store it here
     * if $(CC) does not understand __thread declaration
     * and pthread_getspecific() is too costly
     */

    void            *thr_ctx;

#if (NGX_EVENT_T_PADDING)

    /* event should not cross cache line in SMP */

    uint32_t         padding[NGX_EVENT_T_PADDING];
#endif
#endif
};


#if (NGX_HAVE_FILE_AIO)

struct ngx_event_aio_s {
    void                      *data;
    ngx_event_handler_pt       handler;
    ngx_file_t                *file;

#if (NGX_HAVE_AIO_SENDFILE)
    ssize_t                  (*preload_handler)(ngx_buf_t *file);
#endif

    ngx_fd_t                   fd;

#if (NGX_HAVE_EVENTFD)
    int64_t                    res;
#endif

#if !(NGX_HAVE_EVENTFD) || (NGX_TEST_BUILD_EPOLL)
    ngx_err_t                  err;
    size_t                     nbytes;
#endif

    ngx_aiocb_t                aiocb;
    ngx_event_t                event;
};

#endif

Nginx的連線

 被動連線

這種連線是指 客戶端發起的,伺服器被動接受的連線

//在 檔案ngx_connection.h中
struct ngx_connection_s {
  /*  連線未使用時,data成員用於充當連線池中空閒連結串列中的next指標。當連線被使用時,data的意義由使用它的Nginx模組而定。在HTTP模組中,data指向ngx_http_request_t請求*/
    void               *data;
    // 連線對應的讀事件 
    ngx_event_t        *read;
    // 連線對應的寫事件 
    ngx_event_t        *write;
    // 套接字控制代碼 
    ngx_socket_t        fd;
    //直接接收網路字元流的方法
    ngx_recv_pt         recv;
    // 直接傳送網路字元流的辦法
    ngx_send_pt         send;
    // 以ngx_chain_t連結串列為 引數來 接收 網路 字元流的方法 
    ngx_recv_chain_pt   recv_chain;
    // 以ngx_chain_t連結串列為 引數來 傳送 網路 字元流的方法 
    ngx_send_chain_pt   send_chain;
    /*這個連線對應的ngx_listening_t監聽物件,此連線由listening監聽埠的事件建立*/
    ngx_listening_t    *listening;
    //這個連線上已經發送出去的位元組數
    off_t               sent;
    // 可以記錄日誌的ngx_log_t物件
    ngx_log_t          *log;
    /* 記憶體池。一般在accept一個新連線時,會建立一個 記憶體池,而在這個 連線結束時會銷燬記憶體池。所有的ngx_connectionn_t結構 體都是預分配,這個記憶體池的大小將由上面的listening 監聽物件中的 pool_size成員決定*/
    ngx_pool_t         *pool;

    int                 type;
    // 連線客戶端的sockaddr結構體
    struct sockaddr    *sockaddr;
    // 連線 sockaddr結構體的 長度
    socklen_t           socklen;
    // 連線客戶端字串形式的IP地址
    ngx_str_t           addr_text;

    ngx_str_t           proxy_protocol_addr;

    in_port_t           proxy_protocol_port;

#if (NGX_SSL)
    ngx_ssl_connection_t  *ssl;
#endif
      /*本機監聽埠 對應 的sockaddr結構 體 ,也就是listening監聽物件中的sock
    addr成員*/
    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;
     /*用於接收、快取客戶端 發來的位元組流,每個事件消費模組可自由決定從連線池中分配多大空間給 buffer這個 快取欄位*/
    ngx_buf_t          *buffer;

    ngx_queue_t         queue;
    // 連線使用次數。ngx_connection_t結構體每次建立一條來自客戶端的連線,或者主動向後端伺服器發起連線時,number都會加1*/
    ngx_atomic_uint_t   number;
    // 處理 請求次數
    ngx_uint_t          requests;

    unsigned            buffered:8;

    unsigned            log_error:3;     /* ngx_connection_log_error_e */

    unsigned            timedout:1;
    unsigned            error:1;
    unsigned            destroyed:1;

    unsigned            idle:1;
    unsigned            reusable:1;
    unsigned            close:1;
    unsigned            shared:1;

    unsigned            sendfile:1;
    unsigned            sndlowat:1;
    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */

    unsigned            need_last_buf:1;

#if (NGX_HAVE_IOCP)
    unsigned            accept_context_updated:1;
#endif

#if (NGX_HAVE_AIO_SENDFILE)
    unsigned            busy_count:2;
#endif

#if (NGX_THREADS)
    ngx_thread_task_t  *sendfile_task;
#endif
};

主動連線

作為Web伺服器,Nginx也需要向其他伺服器發起連線,使用ngx_peer_connection_t結構體來表示 主動連線,一個待處理連線的許多特性在 被動連線ngx_connection_t中都被定義過,因此ngx_peer_connection_t結構體中引用了ngx_connection_t這個結構體。

// 當使用長連線與上游伺服器通訊時,可通過該方法由連線池獲取 一個新的連線
typedef ngx_int_t (*ngx_event_get_peer_pt)(ngx_peer_connection_t *pc,
    void *data);
// 當 使用長連線與上游伺服器通訊時,通過該方法 將使用完畢 的連線釋放給連線池
typedef void (*ngx_event_free_peer_pt)(ngx_peer_connection_t *pc, void *data,
    ngx_uint_t state);
struct ngx_peer_connection_s {
    /*一個主動連線實際 上也需要ngx_connection_t結構體的大部分成員,並且處於重用的考慮 而定義 了connecion*/
    ngx_connection_t                *connection;
    // 遠端伺服器的socketaddr
    struct sockaddr                 *sockaddr;
    // sockaddr地址長度
    socklen_t                        socklen;
    // 遠端伺服器的名稱 
    ngx_str_t                       *name;
    // 表示在連線 一個 遠端伺服器,當前連接出現 異常失敗後可以重試的次數,也就是允許的最多失敗的次數
    ngx_uint_t                       tries;
    ngx_msec_t                       start_time;

    ngx_event_get_peer_pt            get;
    ngx_event_free_peer_pt           free;
    void                            *data;

#if (NGX_SSL)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif
   // 本機地址資訊 
    ngx_addr_t                      *local;

    int                              type;
    // 套接字的接收緩衝區大小
    int                              rcvbuf;
    // 記錄日誌的ngx_log_t物件
    ngx_log_t                       *log;

    unsigned                         cached:1;
#if (NGX_HAVE_TRANSPARENT_PROXY)
    unsigned                         transparent:1;
#endif

                                     /* ngx_connection_log_error_e */
    unsigned                         log_error:2;
};