四:深入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;
};