雖然代碼理解起來比較混亂,但是使用還是比較簡單的,常用的有創建 hash 和在 hash 中進行查找兩個操作,對于創建hash的操作,過程一般為:
- 構造一個 ngx_hash_key_t 為成員的數組, 包含 key, value 和 使用key計算出的一個hash值
- 構建一個 ngx_hash_init_t結構體的變量, 其中包含了ngx_hash_t 的成員, 為hash的結構體, 還包括一些其他初始設置,如bucket的大小,內存池等
- 調用 ngx_hash_init 傳入 ngx_hash_init_t 結構, ngx_hash_key_t 的數組,和數組的長度, 進行初始化,這樣 ngx_hash_init_t的hash成員就是我們要的hash結構
查找的過程很簡單
- 計算 key 的hash值
- 使用 ngx_hash_find 進行查找,需要同時傳入 hash值和key ,返回的就是value的指針
?
需要注意的是,nginx 的 hash 在查找時使用的是分桶后線性查找法,因此當分桶數確定時查找效率同其中的總 key-val 對數量成反比。
下面是一些demo代碼(可以從svn中找到)
#include <stdio.h> #include "ngx_config.h" #include "ngx_conf_file.h" #include "nginx.h" #include "ngx_core.h" #include "ngx_string.h" #include "ngx_palloc.h" #include "ngx_array.h" #include "ngx_hash.h" volatile ngx_cycle_t ? * ngx_cycle ; void ngx_log_error_core ( ngx_uint_t level , ngx_log_t * log , ngx_err_t err , const char * fmt , ...) { } static ngx_str_t names [] = { ngx_string ( "rainx" ), ? ? ? ? ? ? ? ? ? ? ? ? ? ? ngx_string ( "xiaozhe" ), ? ? ? ? ? ? ? ? ? ? ? ? ? ? ngx_string ( "zhoujian" )}; static char * descs [] = { "rainx's id is 1" , "xiaozhe's id is 2" , "zhoujian's id is 3" }; // hash table的一些基本操作 int main () { ? ? ngx_uint_t ? ? ? ? ?k ; //, p, h; ? ? ngx_pool_t * ? ? ? ? pool ; ? ? ngx_hash_init_t ? ? hash_init ; ? ? ngx_hash_t * ? ? ? ? hash ; ? ? ngx_array_t * ? ? ? ?elements ; ? ? ngx_hash_key_t * ? ? arr_node ; ? ? char * ? ? ? ? ? ? ? find ; ? ? int ? ? ? ? ? ? ? ? i ; ? ? ngx_cacheline_size = 32 ; ? ? // hash key cal start ? ? ngx_str_t str ? ? ? = ngx_string ( "hello, world" ); ? ? k ? ? ? ? ? ? ? ? ? = ngx_hash_key_lc ( str . data , str . len ); ? ? pool ? ? ? ? ? ? ? ? = ngx_create_pool ( 1024 * 10 , NULL ); ? ? printf ( "caculated key is %u \n" , k ); ? ? // hask key cal end ? ? // ? ? hash = ( ngx_hash_t *) ngx_pcalloc ( pool , sizeof ( hash )); ? ? hash_init . hash ? ? ? = hash ; ? ? ? ? ? ? ? ? ? ? ? // hash結構 ? ? hash_init . key ? ? ? = & ngx_hash_key_lc ; ? ? ? ? ? // hash算法函數 ? ? hash_init . max_size ? = 1024 * 10 ; ? ? ? ? ? ? ? ? ? // max_size ? ? hash_init . bucket_size = 64 ; // ngx_align(64, ngx_cacheline_size); ? ? hash_init . name ? ? ? = "yahoo_guy_hash" ; ? ? ? ? ? // 在log里會用到 ? ? hash_init . pool ? ? ? ? ? = pool ; ? ? ? ? ? ? ? ? // 內存池 ? ? hash_init . temp_pool ? ? ? = NULL ; ? ? // 創建數組 ? ? elements = ngx_array_create ( pool , 32 , sizeof ( ngx_hash_key_t )); ? ? for ( i = 0 ; i < 3 ; i ++) { ? ? ? ? arr_node ? ? ? ? ? ? = ( ngx_hash_key_t *) ngx_array_push ( elements ); ? ? ? ? arr_node -> key ? ? ? = ( names [ i ]); ? ? ? ? arr_node -> key_hash ? = ngx_hash_key_lc ( arr_node -> key . data , arr_node -> key . len ); ? ? ? ? arr_node -> value ? ? = ( void *) descs [ i ]; ? ? ? ? // ? ? ? ? printf ( "key: %s , key_hash: %u\n" , arr_node -> key . data , arr_node -> key_hash ); ? ? } ? ? if ( ngx_hash_init (& hash_init , ( ngx_hash_key_t *) elements -> elts , elements -> nelts )!= NGX_OK ){ ? ? ? ? return 1 ; ? ? } ? ? // 查找 ? ? k ? ? = ngx_hash_key_lc ( names [ 0 ]. data , names [ 0 ]. len ); ? ? printf ( "%s key is %d\n" , names [ 0 ]. data , k ); ? ? find = ( char *) ? ? ? ? ngx_hash_find ( hash , k , ( u_char *) names [ 0 ]. data , names [ 0 ]. len ); ? ? if ( find ) { ? ? ? ? printf ( "get desc of rainx: %s\n" , ( char *) find ); ? ? } ? ? ngx_array_destroy ( elements ); ? ? ngx_destroy_pool ( pool ); ? ? return 0 ; }
運行結果
rainx@rainx - laptop :~/ land / nginxsrp / src / demo / basic_types$ ./ hash_op caculated key is 3654358412 key : rainx , key_hash : 108275556 key : xiaozhe , key_hash : 2225329080 key : zhoujian , key_hash : 3269715264 rainx key is 108275556 get desc of rainx : rainx 's id is 1
ngx_list
ngx_list 的結構并不復雜,ngx為我們封裝了ngx_list_create, ngx_list_init, 和 ngx_list_push等(建立,初始化,添加)操作, 但是對于我們來說最常用的是遍歷操作, 下面是nginx的注釋里面提到的遍歷的例子
? ?part = & list . part ; ? ?data = part -> elts ; ? ? ? for ( i = 0 ;; i ++) { ? ? ? ? ? if ( i >= part -> nelts ) { ? ? ? ? ? ? if ( part -> next == NULL ) { ? ? ? ? ? ? ? ? break ; ? ? ? ? ? ? } ? ? ? ? ? ? ?part = part -> next ; ? ? ? ? ? ?data = part -> elts ; ? ? ? ? ? ?i = 0 ; ? ? ? ? } ? ? ? ? ? ... ?data [ i ] ... ? ? ? }
了解nginx的core module 的結構和運行機制
參考資料
在開始這個task的學習的時候,經過搜索發現了langwan同學之前對nginx的源代碼研究資料,很有參考意義,所以大量節省了我們的工作,我覺得對于本章的進行比較有用的是,下面這幾個文章
- nginx源代碼分析? http://hi.baidu.com/langwan/blog/item/6b18ef24cd859e064c088d28.html
- nginx 緩沖區構造? http://hi.baidu.com/langwan/blog/item/822b758d5d1d9a1ab31bbaf8.html
- Nginx源代碼分析 - 日志處理? http://hi.baidu.com/langwan/blog/item/7e7db51978e04e4d43a9ad32.html
Debug信息的輸出
為了方便研究,將nginx的debug 信息打開,重新編譯
rainx@rainx - laptop :~/ land / nginx - 0.7 . 61 $ ./ configure -- prefix = /home/ rainx / land / test -- with - debug
然后修改nginx.conf
worker_processes ? 2 ; error_log ?logs / error . log ?debug ;
打開debug信息的支持,并使用2個worker進程,通過查看 log 信息來了解 nginx 運行的情況
基于上面的配置信息,結合一個簡單的http訪問操作,我這里記錄了一個? log日志的例子
ngx_init_cycle
其中一個比較重要的函數調用是, ngx_init_cycle, 這個是使用kscope輸出的他的調用關系,他被main, ngx_master_process_cycle,ngx_single_process_cycle 調用, 其中后兩者是在reconfigure的時候被調用的
他主要做了如下幾件事情:
初始化 cycle 是基于舊有的 cycle 進行的,比如這里的 init_cycle ,會繼承 old cycle 的很多屬性, 比如 log 等, 但是同時會對很多資源重新分配,比如 pool , shared mem , file handler , listening socket 等,同時清除舊有的 cycle 的資源
另外,ngx_master/single_process_cycle 里面會對init_process進行調用, 并且循環調用 ngx_process_events_and_timers , 其中里面會調用ngx_process_events(cycle, timer, flags); 對事件循環進行polliing 時間一般默認為 500 ms
了解nginx的http core module 的結構和運行機制
HTTP相關的Module都在 src/http 目錄和其子目錄下, 其中 src/http 下的文件為http模塊的核心文件, src/http/modules 下的文件為http模塊的擴展模塊。
其中:
ngx_http.[c|h]
ngx_http.c 中,注冊了 http 這個指令的處理模塊,對應ngx_http_block函數
static ngx_command_t ?ngx_http_commands [] = { ? ? { ngx_string ( "http" ), ? ? ? NGX_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_NOARGS , ? ? ? ngx_http_block , ? ? ? 0 , ? ? ? 0 , ? ? ? NULL }, ? ? ? ngx_null_command };
這個函數里面會進行一些conf資源分配/Merge,配置文件解析等工作。 這里面有個一比較重要的工作是注冊了nginx http 的 phase handler
? ? if ( ngx_http_init_phase_handlers ( cf , cmcf ) != NGX_OK ) { ? ? ? ? return NGX_CONF_ERROR ; ? ? }
phase handler的類型在 ngx_http_core_module 這里定義:
typedef enum { ? ? NGX_HTTP_POST_READ_PHASE = 0 , ? ? NGX_HTTP_SERVER_REWRITE_PHASE , ? ? NGX_HTTP_FIND_CONFIG_PHASE , ? ? NGX_HTTP_REWRITE_PHASE , ? ? NGX_HTTP_POST_REWRITE_PHASE , ? ? NGX_HTTP_PREACCESS_PHASE , ? ? NGX_HTTP_ACCESS_PHASE , ? ? NGX_HTTP_POST_ACCESS_PHASE , ? ? NGX_HTTP_TRY_FILES_PHASE , ? ? NGX_HTTP_CONTENT_PHASE , ? ? NGX_HTTP_LOG_PHASE } ngx_http_phases ;
每一個phase的handlers 都是一個數組,里面可以包含多個元素,通過 ngx_array_push 添加新的handler
其中每個phase的處理大都包含了對ngx_request_t 的 write 或者 read event的改寫,其中
在 ngx_http_core_content_phase 里面, 有對location handler的調用, 其中的 r->content_handler 就是運行時刻從location handler中注冊的,
? ? if ( r -> content_handler ) { ? ? ? ? r -> write_event_handler = ngx_http_request_empty_handler ; ? ? ? ? ngx_http_finalize_request ( r , r -> content_handler ( r )); /*實際的請求發送處理*/ ? ? ? ? return NGX_OK ; ? ? }
其中, 在各個phase的結束階段,一般都是調用
? ? r -> phase_handler ++; ? ? return NGX_AGAIN ;
移動request 中 phase_handler的指針,并且示意主程序繼續進行。
這里,無論是phase handler,還是 location handler,我們都是可以在程序里進行注冊的。
另外, ngx_http_block 里面調用了 ngx_http_optimize_servers ,這個函數對listening和connection相關的變量進行了初始化和調優,并最終在 ngx_http_add_listening (被ngx_http_add_listening調用) 中注冊了listening 的 handler 為 ngx_http_init_connection
? ? ls -> handler = ngx_http_init_connection ;
ngx_http_init_connection 在 ngx_http_request.c中定義,后續會進行詳細的介紹
ngx_http_request.[c|h]
這里面,ngx_http_init_connection 注冊了connection事件的讀操作的回叫函數, 并將寫操作設置為空函數
? ? rev = c -> read ; ? ? rev -> handler = ngx_http_init_request ; ? ? c -> write -> handler = ngx_http_empty_handler ;
當新的連接進入的時候,就執行到 ngx_http_init_request, 開始對后面的流程進行處理,主要是將rev的handler 設置為ngx_http_process_request_line , 然后ngx_http_process_request_line 會先后有調度到 ngx_http_process_request_headers 和 ngx_http_process_request 函數對讀取過來的event進行處理,其中, ngx_http_process_request_headers 里面會對http的請求頭進行解析,ngx_http_process_request 設置event handler 到ngx_http_request_handler ,ngx_http_request_handler 中會根據事件可能是讀取還是寫入的操作分別調用 request 的 read_event_handler 和 write_event_handler ,所以后續程序對 request 的 read/write event_handler 調整 本質上類似對 rev 和 wev的handler的調整,只是回叫函數的參數變更為了 ngx_request_t 而不是之前的ngx_event_t
? ? c -> read -> handler = ngx_http_request_handler ; ? ? c -> write -> handler = ngx_http_request_handler ; ? ? r -> read_event_handler = ngx_http_block_reading ;
根據上面代碼可以看出, 模塊開始使用 ngx_http_block_reading 這個handler對后續的讀請求進行處理
在注冊完事件后, ngx_http_process_request 會分別調用下面的兩個函數
? ? ngx_http_handler ( r ); ? ? ngx_http_run_posted_requests ( c );
其中, ngx_http_handler 在ngx_http_core_module中定義,處理程序的主請求, ngx_http_run_posted_requests 在ngx_http_request.c 里定義,處理所有提交的子請求數據的輸出。
ngx_http_core_module.[c|h]
對于 ngx_http_core_module 是http 模塊中比較重要的模塊, 他本身是一個 NGX_HTTP_MODULE (不同于ngx_http_module, ngx_http_module本質上是一個 NGX_CORE_MODULE
這里面對http block下面的一些指令進行了處理, 比如 server, location 等, 同時, 上面提到的 ngx_http_handler 也在這里面
ngx_http_handler 所作的最核心的工作就是在最后調用 并將 write event 設置為 ngx_http_core_run_phases, 開始依次處理各個階段的 handler
當handler處理完成后,http的處理流程也就基本上完成了..
? ? while ( ph [ r -> phase_handler ]. checker ) { ? ? ? ? rc = ph [ r -> phase_handler ]. checker ( r , & ph [ r -> phase_handler ]); ? ? ? ? if ( rc == NGX_OK ) { ? ? ? ? ? ? return ; ? ? ? ? } ? ? }
run_phases 的過程實際上非常簡單, 一次的運行每一個handler, 當任意一個handler返回ok或者所有handler執行完成后,整個流程結束。
這里需要注意的是, ph的下標變化是根據 r->phase_handler 變量決定的, 所以在每個handler內部,如果想要讓主程序繼續處理下一個 handler,需要手動的 r->phase_handler++ ,將phase handler數組的下標轉移到下一個成員。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
