摘自《Nginx模塊開發與架構解析》node
Nginx框架是圍繞着ngx_cycle_t結構體來控制進程運行。nginx
//咱們來看一看這神奇的ngx_cycle_s結構體吧,看一看廬山真面目. struct ngx_cycle_s { /* 保存着全部模塊配置項的結構體指針p,它首先是一個數組, 該數組每一個成員又是一個指針,這個指針又指向了存儲着指針的數組.*/ void **** conf_ctx ; //內存池 ngx_pool_t * pool ; /*日誌模塊中提供了生成基本ngx_log_t日誌對象的功能, 這裏的log其實是在在沒有執行ngx_init_cycle方法前, 也就是尚未解析配置項前,若是有信息須要輸出到日誌, 就會暫時使用log對象,它會輸出到屏幕.在執行ngx_init_cycle方法後, 會根據nginx.conf配置文件中的配置項,構造出正確的日誌文件, 此時會對log從新賦值. */ ngx_log_t * log ; /* 由nginx.conf配置文件讀取到日誌路徑後,將開始初始化error_log日誌文件, 因爲log對象還在用於輸出日誌到屏幕,這時候new_log將暫時替代log, 待初始化成功後,會用new_log的地址覆蓋上面的指針.*/ ngx_log_t new_log ; //下面files數組裏元素的總數 ngx_uint_t files_n ; /* 對於epoll,rtsig這樣的事件模塊,會以有效文件句柄樹來預先創建 這些ngx_connection_t結構體,以加速事件的收集,分發.這時files就會 保存全部ngx_connection_t的指針組成的數組,而文件句柄的值用來訪問 files數組成員. */ ngx_connection_t ** files ; //可用鏈接池,與free_connection_n配合使用 ngx_connection_t * free_connections ; //鏈接池的個數 ngx_uint_t free_connection_n ; //可重用的鏈接隊列 ngx_queue_t reusable_connections_queue ; //動態數組,每一個成員存儲ngx_listening_t成員,表示監聽端口以及相關的參數 ngx_array_t listening ; /* 動態數組,保存着nginx全部要操做的目錄,若是目錄不在,將建立, 若是建立失敗(例如權限不夠),nginx將啓動失敗.*/ ngx_array_t paths ; /* 單鏈表容器,元素類型是ngx_open_file_t結構體,他表示nginx已經打開的 全部文件.事實上nginx框架不會向open_files鏈表中添加文件, 而是由對此感興趣的模塊向其中添加文件路徑名, nginx會在ngx_init_cycle方法中打開這些文件.*/ ngx_list_t open_files ; /* 單鏈表容器,每個元素表示一塊共享內存*/ ngx_list_t shared_memory ; //當前進程中全部鏈接對象的總數,於下面的connections成員配合使用. ngx_uint_t connection_n ; //指向當前進程中多有鏈接對象 ngx_connection_t * connections ; //指向當前進程中全部讀事件 ngx_event_t * read_events ; //指向當前進程中全部寫事件 ngx_event_t * write_events ; /* 舊的ngx_cycle_t對象,用於引用上一個ngx_cycle_t對象中的成員. 例如 ngx_init_cycle方法在啓動初期,須要創建一個臨時ngx_cycle_t 對象來保存一些變量,再調用ngx_init_cycle方法時,就能夠把 舊的ngx_cycle_t對象傳進去,而這時,這個old_cycle指針 就會保存這個前期的ngx_cycle_t對象 */ ngx_cycle_t * old_cycle ; //配置文件相對於安裝目錄的路徑 ngx_str_t conf_file ; /* nginx處理配置文件時須要特殊處理的在命令行攜帶的參數, 通常是 -g選項攜帶的參數 */ ngx_str_t conf_param ; //nginx配置文件路徑 ngx_str_t conf_prefix ; //nginx安裝路徑 ngx_str_t prefix ; //用於進程間同步的文件所名稱 ngx_str_t lock_file ; //使用gethostname系統調用獲得的主機名 ngx_str_t hostname ; };
ngx_cycle_t對象中有一個動態數組成員叫作listening,它的每一個數組元素都是ngx_listening_t結構體,而每一個ngx_listening_t結構體又表明着Nginx服務器監聽的一個端口。後端
struct ngx_listening_s { ngx_socket_t fd;//套接字句柄 struct sockaddr *sockaddr;//監聽sockaddr地址 socklen_t socklen; /*sockaddr地址長度 size of sockaddr */ size_t addr_text_max_len;//存儲ip地址的字符串addr_text最大長度 ngx_str_t addr_text;//以字符串形式存儲ip地址 int type; //套接字類型。types是SOCK_STREAM時,表示是tcp int backlog; //TCP實現監聽時的backlog隊列,它表示容許正在經過三次握手創建tcp鏈接但尚未任何進程開始處理的鏈接最大個數 int rcvbuf;//套接字接收緩衝區大小 int sndbuf;//套接字發送緩衝區大小 /* handler of accepted connection */ ngx_connection_handler_pt handler;//當新的tcp鏈接成功創建後的處理方法 void *servers; //實際上框架並不使用servers指針,它更可能是做爲一個保留指針,目前主要用於HTTP或者mail等模塊,用於保存當前監聽端口對應着的全部主機名 ngx_log_t log;//日誌 ngx_log_t *logp;//日誌指針 size_t pool_size;//若是爲新的tcp鏈接建立內存池,則內存池的初始大小應該是pool_size。 /* should be here because of the AcceptEx() preread */ size_t post_accept_buffer_size; /* should be here because of the deferred accept */ ngx_msec_t post_accept_timeout;//鏈接創建成功後,若是 post_accept_timeout秒後仍然沒有收到用戶的數據,就丟棄該鏈接 ngx_listening_t *previous; //前一個ngx_listening_t結構,多個ngx_listening_t結構體之間由previous指針組成單鏈表 ngx_connection_t *connection;//當前監聽句柄對應的ngx_connection_t結構體 unsigned open:1;//爲1表示監聽句柄有效,爲0表示正常關閉 unsigned remain:1;//爲1表示不關閉原先打開的監聽端口,爲0表示關閉曾經打開的監聽端口 unsigned ignore:1;//爲1表示跳過設置當前ngx_listening_t結構體中的套接字,爲0時正常初始化套接字 unsigned bound:1; /* already bound */ unsigned inherited:1; /* inherited from previous process */ unsigned nonblocking_accept:1; unsigned listen:1;//爲1表示當前結構體對應的套接字已經監聽 unsigned nonblocking:1; //目前該標誌位沒有意義 unsigned shared:1; /*目前該標誌位沒有意義, shared between threads or processes */ unsigned addr_ntop:1;//爲1表示將網絡地址轉變爲字符串形式的地址 #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) unsigned ipv6only:2; #endif #if (NGX_HAVE_DEFERRED_ACCEPT) unsigned deferred_accept:1; unsigned delete_deferred:1; unsigned add_deferred:1; #ifdef SO_ACCEPTFILTER char *accept_filter; #endif #endif #if (NGX_HAVE_SETFIB) int setfib; #endif };
Nginx定義了基本的數據結構ngx_connection_t來表示鏈接。由客戶端主動發起、Nginx服務器被動接收的TCP鏈接,這類能夠稱爲被動鏈接。還有一類鏈接,在某些請求的處理過程當中,Nginx會試圖主動向其餘上游服務器創建鏈接,並以此鏈接與上游服務器通訊,Nginx定義ngx_peer_connection_t結構來表示,這類能夠稱爲主動鏈接。本質上來講,主動鏈接是以ngx_connection_t結構體爲基礎實現的。數組
struct ngx_connection_s { //鏈接未使用時,data用於充當鏈接池中空閒鏈表中的next指針。鏈接使用時由模塊而定,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_recv_chain_pt recv_chain;//以鏈表來接收網絡字符流的方法 ngx_send_chain_pt send_chain;//以鏈表來發送網絡字符流的方法 //這個鏈接對應的ngx_listening_t監聽對象,此鏈接由listening監聽端口的事件創建 ngx_listening_t *listening; off_t sent;//這個鏈接上已發送的字節數 ngx_log_t *log;//日誌對象 /*內存池。通常在accept一個新的鏈接時,會建立一個內存池,而在這個鏈接結束時會銷燬內存池。內存池大小是由上面listening成員的pool_size決定的*/ ngx_pool_t *pool; struct sockaddr *sockaddr;//鏈接客戶端的sockaddr socklen_t socklen;//sockaddr結構體的長度 ngx_str_t addr_text;//鏈接客戶段字符串形式的IP地址 #if (NGX_SSL) ngx_ssl_connection_t *ssl; #endif //本機監聽端口對應的sockaddr結構體,實際上就是listening監聽對象的sockaddr成員 struct sockaddr *local_sockaddr; ngx_buf_t *buffer;//用戶接受、緩存客戶端發來的字符流,buffer是由鏈接內存池分配的,大小自由決定 /*用來將當前鏈接以雙向鏈表元素的形式添加到ngx_cycle_t核心結構體的reuseable_connection_queue雙向鏈表中,表示能夠重用的鏈接*/ ngx_queue_t queue; /*鏈接使用次數。ngx_connection_t結構體每次創建一條來自客戶端的鏈接,或者主動向後端服務器發起鏈接時,number都會加1*/ ngx_atomic_uint_t number; ngx_uint_t requests;//處理的請求次數 //緩存中的業務類型。 unsigned buffered:8; //本鏈接的日誌級別,佔用3位,取值範圍爲0~7,但實際只定義了5個值,由ngx_connection_log_error_e枚舉表示。 unsigned log_error:3; /* ngx_connection_log_error_e */ unsigned single_connection:1;//爲1時表示獨立的鏈接,爲0表示依靠其餘鏈接行爲而創建起來的非獨立鏈接 unsigned unexpected_eof:1;//爲1表示不期待字符流結束 unsigned timedout:1;//爲1表示鏈接已經超時 unsigned error:1;//爲1表示鏈接處理過程當中出現錯誤 unsigned destroyed:1;//爲1表示鏈接已經銷燬 unsigned idle:1;//爲1表示鏈接處於空閒狀態,如keepalive兩次請求中間的狀態 unsigned reusable:1;//爲1表示鏈接可重用,與上面的queue字段對應使用 unsigned close:1;//爲1表示鏈接關閉 unsigned sendfile:1;//爲1表示正在將文件中的數據發往鏈接的另外一端 /*爲1表示只有鏈接套接字對應的發送緩衝區必須知足最低設置的大小閥值時,事件驅動模塊纔會分發該事件。這與ngx_handle_write_event方法中的lowat參數是對應的*/ unsigned sndlowat:1; unsigned tcp_nodelay:2; /* ngx_connection_tcp_nodelay_e */ unsigned tcp_nopush:2; /* ngx_connection_tcp_nopush_e */ #if (NGX_HAVE_IOCP) unsigned accept_context_updated:1; #endif #if (NGX_HAVE_AIO_SENDFILE) unsigned aio_sendfile:1; ngx_buf_t *busy_sendfile; #endif #if (NGX_THREADS) ngx_atomic_t lock; #endif };
Nginx主動想後端服務器發起的鏈接稱做主動鏈接,用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 *connection; /* 遠端服務器socket信息 */ struct 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; /* 該data成員做爲上面方法的傳遞參數 */ void *data; /* 本地地址信息 */ ngx_addr_t *local; /* 接收緩衝區大小 */ int rcvbuf; ngx_log_t *log; /* 標識該鏈接已經緩存 */ unsigned cached:1; /* ngx_connection_log_error_e */ unsigned log_error:2; };
Nginx在接受客戶端的鏈接所使用的ngx_connection_t結構都是在啓動階段就預分配好的,使用時只需從鏈接池中獲取。書中圖示以下:服務器
ngx_cycle_t中的connections和free_connections倆成員構成一個鏈接池。其中,connections指向整個鏈接池數組首部,free_connections指向第一個ngx_connection_t空閒鏈接。全部空閒鏈接ngx_connection_t都以data成員做爲next指針串聯成一個單鏈表。這樣,一旦有用戶發起鏈接就從free_connections指向的鏈表頭獲取一個空閒鏈接。在鏈接釋放時,只需把該鏈接再插入到free_connections鏈表頭便可。
同時,ngx_cycle_t核心結構還有一套事件池,即Nginx認爲每個鏈接至少須要一個讀事件和一個寫事件,有多少個鏈接就分配多少個讀、寫事件。這裏,讀、寫、鏈接池都是由三個大小相同的數組組成,根據數組下標就可將每個鏈接、讀、寫事件對應起來。這三個數組的大小都由nginx.conf的connections配置項決定。網絡