Nginx事件模塊

ngx_event_module_t

      這是事件模塊都必須實現的接口。

typedef struct {
    //事件模塊的名稱
    ngx_str_t       *name;
    //在解析配置項前,用於創建存儲配置項參數的結構體
    void *(*create_conf)(ngx_cycle_t *cycle);
    //在解析配置項完成後,用以綜合處理當前事件模塊感興趣的全部配置項
    char *(*init_conf)(ngx_cycle_t *cycle,void *conf);
    //對於事件機制,每個事件模塊需要實現的10個函數
    ngx_event_actions_t actions;
} ngx_event_module_t;

ngx_event_actions_t

typedef struct {
    //添加事件方法到操作系統提供的事件驅動機制(如epoll、kqueue等
    ngx_int_t   (*add)(ngx_event_t *ev,ngx_int_t event,ngx_uint_t flags);

    //刪除事件
    ngx_int_t (*del)(ngx_event_t *ev,ngx_int_t event,ngx_uint_t flags);

    //啓用1個事件
    ngx_int_t (*enable)(ngx_event_t *ev,ngx_int_t event,ngx_uint_t flags);

    //禁用1個事件
    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_int_t (*process_changes)(ngx_cycle_t *cycle,ngx_uint_t nowait);

    //通過下面的函數來處理事件,它是處理、分發事件的核心
    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_t

typedef struct ngx_event_s ngx_event_t;

struct ngx_event_s {
    //事件相關的對象
    void                *data;

    //1:事件可寫
    unsigned            write:1;

    //1:事件可以建立新的連接
    unsigned            accept:1;

    //區分當前事件是否過期
    unsigned            instance:1;

    //1:事件是活躍的;0:不活躍
    unsigned            active:1;

    //1:禁用事件
    unsigned            disabled:1;

    //僅對kqueue、eventport有意義,對epoll無意義
    unsigned            oneshot:1;

    //用於異步AIO事件的處理
    unsigned            complete:1;

    //1:當前處理的字符流已經結束
    unsigned            eof:1;

    //1:表示事件在處理過程中出現錯誤
    unsigned            error:1;

    //1:事件超時
    unsigned            timeout:1;

    //1:事件處於定時器計時中
    unsigned            time_set:1;

    //delayed=1需要延遲處理這個事件,它僅用於限速功能
    unsigned            delayed:1;

    //該標誌位目前沒有使用
    unsigned            read_discarded:1;

    //未被使用
    unsigned            unexpected_eof:1;

    //1:延遲建立TCP連接,即三次握手後等待數據包到來才建立連接
    unsigned            deferred:accept:1;

    //1:等待字符流結束,只與kqueue和aio事件驅動有關
    unsigned            pending_eof:1;

#if !(NGX_THREADS)
    //1:在處理post事件時,當前事件已經準備就緒
    unsigned            post_ready:1;
#endif

    //1:epoll事件驅動機制下儘可能多地建立TCP連接
    unsigned            avaliable:1;

    //這個事件發生時的處理函數,每個事件消費者模塊都會重新實現!
    ngx_event_handler_pt handler;

#if (NGX_HAVE_AIO)

#if (NGX_HAVE_IOCP)
    //Windows下的一種事件驅動模型
    ngx_event_ovlp_t    ovlp;
#else
    //Linux aio機制中定義的結構體
    ngx_aiocb_t         aiocb;
#endif

#endif

    //epoll中不用
    ngx_uint_t      index;

    //用於記錄error_log日誌
    ngx_log_t       *log;

    //定時器節點,用於定時器紅黑樹
    ngx_rbtree_node_t   timer;

    //1:當前事件已關閉,epoll中未使用
    unsigned            closed:1;

    //無意義
    unsigned            channel:1;

    //無意義
    unsigned            resolver:1;

    //post事件會構成一個雙向鏈表
    ngx_event_t     *next;
    ngx_event_t     **prev;
};

//其中的最核心的是handler成員,它由每一個事件消費者模塊實現,以此決定如何處理這個事件。
typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);

添加事件到epoll的簡單封裝函數

添加讀事件

ngx_int_t ngx_handle_read_event(ngx_event_t *rev,ngx_uint_t falgs);

ngx_handler_read_event函數會將讀事件添加到事件驅動模塊中。
rev:要操作的事件;
flags:對於不同的事件驅動模塊,取值範圍不同。

添加寫事件

ngx_int_t ngx_handle_write_event(ngx_event_t *wev,size_t lowat);

ngx_handle_write_event函數會將寫事件添加到事件驅動模塊中。
wev:是要操作的事件;
lowat:只有當連接對應的套接字緩衝區中有lowat大小的可用空間時,時間收集器才能處理這個事件(lowat=0時不考慮可寫緩衝區大小)。

Nginx連接

被動連接ngx_connection_t

這個連接表示是客戶端主動發起、Nginx服務器被動接受的TCP連接。

typedef struct ngx_connection_s ngx_connection_t;

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;

     //此連接由listening監聽端口的事件建立
    ngx_listening_t    *listening;

     //這個連接上已經發送出去的字節數
    off_t               sent;

     //記錄日誌
    ngx_log_t          *log;

     /* 內存池。一般在accept一個新連接時,會創建一個內存池,連接結束時會銷燬它。內存池大小由listening
     中的pool_size決定 */
    ngx_pool_t         *pool;

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

    ngx_str_t           proxy_protocol_addr;

#if (NGX_SSL)
    ngx_ssl_connection_t  *ssl;
#endif

     //本機監聽端口對應的sockaddr結構體,就是listening中的sockaddr
    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;

     //用於接收、緩存客戶端發來的字符串流。
    ngx_buf_t          *buffer;

     /* 用來將當前連接以雙向鏈表元素的形式添加到ngx_cycle_t核心結構體reusable_connections_queue
     雙向鏈表中,表示可重用的連接 */
    ngx_queue_t         queue;

     //連接使用次數
    ngx_atomic_uint_t   number;

     //處理的請求次數
    ngx_uint_t          requests;

     //緩存中的業務類型
    unsigned            buffered:8;

     //日誌級別
    unsigned            log_error:3;     /* ngx_connection_log_error_e */

     //1:不期待字符流結束
    unsigned            unexpected_eof:1;
    //1:已經超時
    unsigned            timedout:1;
    //1:連接處理過程中出現錯誤
    unsigned            error:1;
    //1:連接已經銷燬(只對應的TCP連接,而非此結構體)
    unsigned            destroyed:1;

     //1:連接處於空閒狀態
    unsigned            idle:1;
    //1:連接可重用
    unsigned            reusable:1;
    //1:連接關閉
    unsigned            close:1;

     //1:正在將文件中的數據發往連接的另一端
    unsigned            sendfile:1;
    //1:只有在連接套接字對應的發送緩衝區必須滿足最低設置的大小閾值時,事件驅動模塊纔會分發該事件
    unsigned            sndlowat:1;

    //表示如何使用TCPnodelay特性
    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
    //表示如何使用TCPnopush特性
    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            aio_sendfile:1;
    unsigned            busy_count:2;
    ngx_buf_t          *busy_sendfile;
#endif

#if (NGX_THREADS)
    ngx_atomic_t        lock;
#endif
};

主動連接ngx_peer_connection_t

typedef struct ngx_peer_connection_s ngx_peer_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結構體
    ngx_connection_t                *connection;

     //遠端服務器的socket地址
    struct sockaddr                 *sockaddr;
    //sockaddr地址的長度
    socklen_t                        socklen;
    //遠端服務器的名稱
    ngx_str_t                       *name;

     //表示連接一個服務器失敗收可以嘗試重連的次數
    ngx_uint_t                       tries;

     //獲取連接的方法
    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

#if (NGX_THREADS)
    ngx_atomic_t                    *lock;
#endif

     //本機地址信息
    ngx_addr_t                      *local;

     //套接字的接收緩衝區大小
    int                              rcvbuf;

     //記錄日誌
    ngx_log_t                       *log;

     //1:表示connection連接已經緩存
    unsigned                         cached:1;

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

ngx_connection_t連接池

連接池的使用

  • 連接池是ngx_connection_t的數組,這個數組在Nginx啓動階段就已經分配好,之後只需要從中獲取即可。
  • ngx_cycle_t中有connections和free_connections這個兩個成員。connection指向整個連接池數組的首部,free_connections指向第一個ngx_connection_t空閒鏈表。
  • 讀事件、寫事件、連接池是3個長度相同的數組組成的,相對應的事件和連接池在各自數組中的下標是一樣的,所以可以直接找到。

連接池的使用

函數名 參數含義 執行意義
ngx_connection_t *ngx_get_connection(ngx_socket_t s,ngx_log_t *log) s是連接的套接字句柄 從連接池獲取一個ngx_connection_t結構體,同時獲取相應的讀/寫事件
void ngx_free_connection(ngx_connection_t *c) c是需要回收的連接 將這個連接回收到連接池中

ngx_events_module核心模塊

      它定義了一類新模塊:事件模塊。
      定義一個Nginx模塊就是實現ngx_module_t結構體

實現ngx_events_module配置項

static ngx_command_t ngx_events_commands[]={
    {   ngx_string("events"),
        NGX_MAIN_CONF|NGX_CONF_BLOCK|_NGX_CONF_NOARGS,
        ngx_events_block,
        0,
        0
        NULL},

        ngx_null_command
};

實現核心模塊共同接口ngx_core_module_t

static ngx_core_module_t ngx_events_module_ctx = {
    ngx_string("events"),
    NULL,                   //create_conf
    NULL                    //init_conf
};

      由於ngx_events_module模塊並不解析配置項的參數,只是在出現events配置項後會調用各事件模塊去解析events{}內的配置項,所以不需要實現create_conf和init_conf函數。

ngx_events_module模塊的定義

ngx_module_t ngx_events_module = {
    NGX_MODULE_V1,
    &ngx_events_module_ctx,         //module contxt
    ngx_events_commands,            //module directives
    NGX_CORE_MODULE,                    //module type
    NULL,                               //init master
    NULL,                               //init module
    NULL,                               //init process
    NULL,                               //init thread
    NULL,                               //init exit
    NULL,                               //init process
    NULL,                               //init master
    NGX_MODULE_V1_PADDING
};

      每個事件模塊都必須遵循ngx_event_modue_t接口。在這個接口中,允許每個事件模塊更具自己的喜好建立自己的配置項結構體,ngx_event_module_t中的create_conf方法是就是用來創建這個結構體的。
      這裏可以來明確一下ngx_cycle_t中用來存放所有配置項的成員conf_ctx:



      從中可以看出void ***conf_ctx的四個意義:conf_ctx指向一個數組,這個數組中的每個元素都是指向由ngx_moudles數組中規定模塊的配置項組成的數組指針,這些指針都指向另外的數組,這些數組中的元素都是指向配置項結構體的指針。
      那麼,從conf_ctx獲取當前事件模塊需配置項結構指針就是在數組中確定下標:

#define ngx_event_get_conf(conf_ctx,module)    \
    (*(ngx_get_conf(conf_ctx,ngx_events_module))) module.ctx_index;

#define ngx_get_conf(conf_ctx,module)    conf_cts[module.index]

      因此,調用時,傳入ngx_cycle_t中的conf_ctx和自己的模塊名即可。從上面的兩個宏中也可以看出,ngx_modules_s中的index成員表示所有模塊在ngx_modules數組中的序號(第一個數組中的下標);ctx_index表明了模塊在同類型模塊中的順序(第二個數組中的下標)。
      從上看到下,其實ngx_events_module根本沒做什麼,最主要的是它的ngx_events_block函數。它做了一下幾點:
1. 初始化所有事件模塊的ctx_index成員;
2. 申請事件模塊的整個數組(事件類型的同類型所有模塊組成的數組);
3. 依次調用所有事件模塊的create_conf函數,將產生的結構體指針保存在上面的數組中;
4. 針對所有事件類型的模塊解析配置項,由每個事件模塊定義的ngx_command_t決定了配置項解析方法。
5. 解析完配置項後,依次調用所有事件模塊通用接口ngx_event_module_t中的init_conf方法。

ngx_event_core_module事件模塊

      該模塊在ngx_modules數組中的順序應該比所有事件模塊都靠後,相反地,這樣能讓執行configure腳本時更爲優先的執行它。

ngx_event_core_module感興趣的配置項

static ngx_command_t  ngx_event_core_commands[] = {

       //連接池的大小,就是每個worker進城中支持的TCP最大連接數
    { ngx_string("worker_connections"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_connections,
      0,   
      0,   
      NULL },

        //連接池大小,與上述的重複,後續版本有取消
    { ngx_string("connections"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_connections,
      0,   
      0,   
      NULL },

        //確定選擇哪一個事件模塊作爲事件驅動機制
    { ngx_string("use"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_use,
      0,
      0,
      NULL },

        //儘可能多地接收連接
    { ngx_string("multi_accept"),
      NGX_EVENT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_event_conf_t, multi_accept),
      NULL },

        //是否使用accept_mutex負載均衡鎖
    { ngx_string("accept_mutex"),
      NGX_EVENT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_event_conf_t, accept_mutex),
      NULL },

        //開啓負載均衡鎖後,延遲accept_mutex_delay毫秒後視圖重新連接事件
    { ngx_string("accept_mutex_delay"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      0,
      offsetof(ngx_event_conf_t, accept_mutex_delay),
      NULL },

        //需要對來自指定IP的TCP連接打印debug級別的調試日誌
    { ngx_string("debug_connection"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_debug_connection,
      0,
      0,
      NULL },

      ngx_null_command
};

存儲配置項的結構體

typedef struct {
    //連接池大小
    ngx_uint_t    connections;
    //選用的事件在所有事件模塊中的序號,就是ngx_module_t中的ctx_index
    ngx_uint_t    use;

    //1:在接收到一個新連接事件時,一次性建立儘可能多的連接
    ngx_flag_t    multi_accept;
    //1:啓用負載均衡
    ngx_flag_t    accept_mutex;

    //在worker進程得不到負載均衡鎖時的延遲建立連接時間
    ngx_msec_t    accept_mutex_delay;

    //所選用的事件模塊名
    u_char       *name;

#if (NGX_DEBUG)
    /* 在--with-debug編譯下,可僅針對某些客戶端建立的連接輸出調試級別的日誌,而debug_connection數組
    用於保存這些客戶端的地址信息 */
    ngx_array_t   debug_connection;
#endif
} ngx_event_conf_t;

ngx_event_core_module實現的ngx_event_module_t接口

static ngx_str_t event_core_name = ngx_string("event_core");

ngx_event_module_t  ngx_event_core_module_ctx = {
    &event_core_name,
    ngx_event_core_create_conf,            /* create configuration */
    ngx_event_core_init_conf,              /* init configuration */

    { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};

      由於它並不真正負責TCP網絡時間的驅動,所以不會實現ngx_event_actions_t中的10個方法。

ngx_event_core_module模塊的定義

ngx_module_t  ngx_event_core_module = {
    NGX_MODULE_V1,
    &ngx_event_core_module_ctx,            /* module context */
    ngx_event_core_commands,               /* module directives */
    NGX_EVENT_MODULE,                      /* module type */
    NULL,                                  /* init master */
    ngx_event_module_init,                 /* init module */
    ngx_event_process_init,                /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};
發佈了88 篇原創文章 · 獲贊 9 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章