溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Nginx中怎么啟動并處理http請求

發(fā)布時間:2021-08-10 15:34:00 來源:億速云 閱讀:158 作者:Leah 欄目:大數(shù)據(jù)

這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)Nginx中怎么啟動并處理http請求,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

1. nginx開啟流程

nginx體量很大,想要在較短時間內(nèi)看完所有代碼很難,而且我看得時間也不是很多,所以,這里主要站在宏觀角度,對nginx做個整體剖析。

其實如果直接從main函數(shù)直接開始看,其實也是可以看懂大部分,但是 nginx 回調(diào)函數(shù)太多了,看著看著,突然跑出一個回調(diào)函數(shù),經(jīng)常就懵逼了。

因此,就需要用gdb來定點調(diào)試;

要使用gdb,首先需要在gcc編譯時,加入-g選項,可以如下操作:

  1. 打開nginx目錄/auto/cc/conf文件,然后更改ngx_compile_opt=”-c”選項,添加-g,即為ngx_compile_opt=”-c -g”;

  2. 然后運行./configure和make即可編譯生成可執(zhí)行文件,在文件objs目錄下;

生成可執(zhí)行文件nginx之后,直接在終端運行即可,nginx會加載默認配置文件,以daemon形式運行;

nginx運行之后,即可通過gdb來調(diào)試;

按如下命令開啟gdb

Nginx中怎么啟動并處理http請求

然后,通過pidof命令獲取nginx進程號,即可attach,如下:

Nginx中怎么啟動并處理http請求

nginx默認開啟一個master進程和一個worker進程,因此上述命令會返回兩個進程號,在我主機上8125和8126,較小是master進程,較大的是worker進程;接下來,先看下master進程,

Nginx中怎么啟動并處理http請求

這樣就可以直接調(diào)試nginx的worker進程,用命令bt可以查看master進程的函數(shù)棧

Nginx中怎么啟動并處理http請求

nginx開啟之后,首先啟動的就是master進程,從main函數(shù)開始,

  1. main函數(shù)主要是做一些初始化操作,初始化啟動參數(shù),開啟daemon,新建pid文件等等,然后調(diào)用ngx_master_process_cycle函數(shù);

  2. 在ngx_master_process_cycle函數(shù)中最重要就是開啟子進程,然后調(diào)用sigsuspend函數(shù),master進程則阻塞在在信號中;

因此,master進程任務(wù)就是開啟子進程,然后管理子進程;怎么管理了?

信號,對,就是信號;當(dāng)master進程收到一個信號之后,就把這個信號傳遞給worker進程,worker進程進而根據(jù)不同信號分別處理。

那么問題又來了,master進程是如何把信號傳遞給worker進程的?

管道,對,就是管道。原理和memcache的master線程和worker線程通信機制一樣,即每個worker進程有兩個文件描述符fd[0]和fd[1],一個讀端,一個寫端;

worker進程將讀端加入epoll事件監(jiān)聽,當(dāng)master進程收到一個信號后,在每個worker進程寫端寫入一個flag,然后worker進程觸發(fā)讀事件,讀取flag,并根據(jù)flag做相應(yīng)操作。

因此nginx接收客戶端請求以及處理客戶端請求,主要是在worker進程,我們來看下,worker進程函數(shù)棧

Nginx中怎么啟動并處理http請求

因為 worker 進程是由 master 進程 fork 出來,因此 worker 進程包含 master 進程的函數(shù)棧;我們直接從#5函數(shù)開始看,

  1. ngx_start_worker_processes 函數(shù)調(diào)用ngx_spawn_process開啟子進程,并且設(shè)置master進程和worker進程通信的管道;

  2. ngx_spawn_process函數(shù)主要是設(shè)置master進程和worker進程間通信管道,例如非阻塞等等,然后通過fork函數(shù)正式開啟子進程;
    子進程調(diào)用通過參數(shù)傳遞進來的回調(diào)函數(shù)ngx_worker_process_cycle正式切入子進程部分,父進程則接著設(shè)置worker進程相關(guān)屬性;

  3. ngx_worker_process_cycle 一開始調(diào)用 ngx_worker_process_init 函數(shù)對worker 進程做些初始化設(shè)置,包括設(shè)置進程優(yōu)先級,worker進程允許打開的最大文件描述符,對阻塞信號的設(shè)置,初始化所有模塊,將master進程和worker進程間通信管道添加到監(jiān)聽可讀事件等等;
    然后在一個無限循環(huán)中,函數(shù)ngx_worker_process_cycle接著調(diào)用ngx_process_events_and_timers,開啟事件監(jiān)聽循環(huán);

  4. 在ngx_process_events_and_timers 函數(shù)中,先是獲取鎖,如果獲取到鎖,listenfd 即可接收客戶端,否則 listenfd 不可接收客戶端事件;
    然后調(diào)用ngx_process_events函數(shù),這個函數(shù)也就是ngx_epoll_process_events函數(shù),開啟開啟事件監(jiān)聽;

ok,worker 進程此時已就緒,等待客戶端連接以及請求數(shù)據(jù)。

為了避免驚群現(xiàn)象以及實現(xiàn)worker進程負載均衡,每次有客戶端連接時,所有worker進程會先爭搶鎖,如果某個worker進程獲取到鎖,即可執(zhí)行接收客戶端和客戶端請求事件;

如果worker進程沒有爭搶到鎖,只執(zhí)行客戶端請求事件。

2. 重要回調(diào)函數(shù)設(shè)置

當(dāng)nginx的master進程和worker進程開啟之后,客戶端即可發(fā)送請求;接下來,就看看nginx是如何處理請求的;

當(dāng)客戶端發(fā)送請求之后,首先是通過tcp三次握手建立連接;當(dāng)連接建立成功之后,即執(zhí)行l(wèi)istenfd的回調(diào)函數(shù),但是listenfd的回調(diào)函數(shù)是哪個了?這對于新手來說,其實是很難發(fā)現(xiàn)listenfd回調(diào)函數(shù)。

下面分析下:

像listenfd的回調(diào)函數(shù)以及模塊間是如何拼湊在一起,這些幾乎都是在模塊初始化時完成的。
對于listenfd的回調(diào)函數(shù)即是在event模塊初始化時或者調(diào)用event模塊一些設(shè)置函數(shù)時設(shè)置;
客戶端連接上服務(wù)器之后,服務(wù)器收到請求之后的回調(diào)函數(shù)也是在http模塊初始化時或者調(diào)用模http模塊一些設(shè)置函數(shù)時設(shè)置的。

在event模塊初始化時,調(diào)用的是ngx_event_process_init函數(shù),下面列出這個函數(shù)最重要的代碼:

Nginx中怎么啟動并處理http請求

在for循環(huán)中,迭代每個監(jiān)聽套接字,recv為listenfd連接對象的讀事件,這里設(shè)置listenfd讀事件的回調(diào)函數(shù)為ngx_event_accept函數(shù),然后將每個listenfd添加到事件監(jiān)聽中,并設(shè)置為可讀事件。

ok,當(dāng)我們?nèi)タ磏gx_add_conn和ngx_add_event的定義時,如下:

Nginx中怎么啟動并處理http請求

說明 ngx_add_conn 和 ngx_add_event 都是結(jié)構(gòu)體 ngx_event_actions 結(jié)構(gòu)體中設(shè)置的函數(shù)指針;

其實這個ngx_event_actions就是nginx跨平臺的關(guān)鍵,因為不同平臺使用的事件監(jiān)聽器是不一樣的,導(dǎo)致ngx_event_actions也就不一樣。

例如linux使用的是epoll,因此ngx_event_actions結(jié)構(gòu)體就是在epoll模塊加載時設(shè)置,在上述代碼前半部分。我們來看下epoll模塊actions.init函數(shù):

Nginx中怎么啟動并處理http請求

從代碼可以看出,ngx_event_actions被設(shè)置為ngx_epoll_module_ctx.actions,接著看下這個結(jié)構(gòu)體:

Nginx中怎么啟動并處理http請求

因此,當(dāng)調(diào)用ngx_add_conn和ngx_add_event時,分別調(diào)用的是ngx_epoll_add_connection和ngx_epoll_add_event;

如此一來,如果此時是mac平臺,那么使用的事件監(jiān)聽器是kqueue,那么當(dāng)調(diào)用ngx_add_event時,調(diào)用的就是ngx_kqueue_add_event。

如果使用的poll監(jiān)聽器,那么調(diào)用將是ngx_poll_add_event等等。

接下來,再分析一個很重要的回調(diào)函數(shù),即客戶端連上客戶端之后,發(fā)送請求時的回調(diào)函數(shù),先來看下,listenfd回調(diào)函數(shù)

Nginx中怎么啟動并處理http請求

當(dāng)客戶端連接服務(wù)器時,首先listenfd回調(diào)函數(shù)先是調(diào)用accept函數(shù)接收客戶端請求,然后從對象池中獲取一個封裝客戶端socket連接對象。

如果目前使用的是epoll事件監(jiān)聽器,則調(diào)用ngx_add_conn(c)放入事件監(jiān)聽,最后調(diào)用ngx_listening_t的回調(diào)函數(shù),對客戶端連接進一步操作;

ok,這個ls->handler(c)是個啥?我在第一次看代碼時,一臉懵逼!?。?/strong>

還記得之前說的嗎?模塊之間的銜接,幾乎都是在模塊初始化或者調(diào)用模塊一些設(shè)置函數(shù)時設(shè)置的,因此接下來,就來看看http模塊初始化時做了什么。

http模塊并沒有在模塊初始化函數(shù)中設(shè)置 ls->handler(c),而是在當(dāng)讀取到”http”命令時,執(zhí)行命令函數(shù) ngx_http_block 中設(shè)置;

Nginx中怎么啟動并處理http請求

真是藏的夠深,經(jīng)歷了四個函數(shù),終于看到ls-handler設(shè)置函數(shù)了,即為ngx_http_init_connection函數(shù),而這個函數(shù)在http模塊,為客戶端http請求處理的入口函數(shù);

到此為止,我們可以知道服務(wù)器在接收到客戶端之后,首先將客戶端封裝成ngx_connection_t結(jié)構(gòu)體,然后交給http模塊執(zhí)行http請求。

3. nginx 處理 http 請求

nginx處理http的請求是nginx最重要的職能,也是最復(fù)雜的一部分。可以大概說下執(zhí)行流程:

  1. 讀取解析請求行;

  2. 讀取解析請求頭;

  3. 開始最重要的部分,即多階段處理;
    nginx把請求處理劃分成了11個階段,也就是說當(dāng)nginx讀取了請求行和請求頭之后,將請求封裝了結(jié)構(gòu)體ngx_http_request_t,然后每個階段的handler都會根據(jù)這個ngx_http_request_t,對請求進行處理,例如重寫uri,權(quán)限控制,路徑查找,生成內(nèi)容以及記錄日志等等;

  4. 將結(jié)果返回給客戶端;

多階段處理是nginx模塊最重要的部分,因為第三方模塊也是注冊在這;例如有人寫了一個利用nginx和memcache做頁面緩存的第三方模塊,也可以把memcache換成redis集群等等;

而且nginx多階段處理有點類似python和golang web框架的中間件,后者主要是用裝飾器模式,對handler一層一層封裝,而nginx是用數(shù)組(鏈表)形式組合多階段handler,然后按handler鏈表執(zhí)行即可;

因為多階段這塊內(nèi)容還沒完全看懂,所以跟著網(wǎng)上教程,寫了個最簡單的第三方模塊,用于設(shè)置定點調(diào)試,觀察http階段函數(shù)執(zhí)行過程,步驟如下:

  1. 在nginx目錄下新建一個目錄thm(third mudole),在新建一個foo目錄(foo模塊),然后在foo目錄下新建ngx_http_foo_module.c

Nginx中怎么啟動并處理http請求

Nginx中怎么啟動并處理http請求

然后同樣是在foo目錄下新建一個配置文件config

Nginx中怎么啟動并處理http請求

這樣,一個最簡單的第三方模塊就編寫完成。

上述兩個函數(shù)很好理解,一個是初始化函數(shù),將這個模塊的 handler 注冊到某個階段中。

這個例子是在階段NGX_HTTP_CONTENT_PHASE,然后當(dāng)程序執(zhí)行到上述階段時,即可執(zhí)行foo模塊;最后重新編譯生成可執(zhí)行文件即可。

接下來,利用gdb來看下http執(zhí)行過程,把定點設(shè)置在

Nginx中怎么啟動并處理http請求

簡要說明下上述函數(shù),我閱讀的版本和運行版本不一樣,因此上述僅供參考:

  1. 當(dāng)有客戶端發(fā)送tcp連接請求時,ngx_epoll_process_events返回listenfd可讀事件,調(diào)用ngx_event_accept函數(shù)接收客戶端請求,然后將請求封裝成ngx_connection_t結(jié)構(gòu)體,最后調(diào)用ngx_http_init_connection函數(shù)進入http處理;

  2. 在新版nginx中,并沒有看到ngx_http_wait_request_handler,而是改成了ngx_http_init_connection(ngx_connection_t *c)函數(shù),然后在這個函數(shù)內(nèi)部調(diào)用ngx_http_init_request函數(shù)初始化請求結(jié)構(gòu)體ngx_http_request_t以及調(diào)用ngx_http_process_request_line函數(shù);

  3. ngx_http_process_request_line函數(shù)內(nèi)部先是調(diào)用ngx_http_read_request_header函數(shù)將請求行讀取到緩存中,然后調(diào)用ngx_http_parse_request_line函數(shù)解析出請求行信息,最后調(diào)用ngx_http_process_request_header處理請求頭;

  4. 在函數(shù) ngx_http_process_request_header 內(nèi)部先是調(diào)用函數(shù)ngx_http_read_request_header 讀取請求頭,然后調(diào)用 ngx_http_parse_header_line 函數(shù)解析出請求頭,接著調(diào)用 ngx_http_process_request_header 函數(shù)對請求頭進行必要的驗證,最后調(diào)用ngx_http_process_request 函數(shù)處理請求;

  5. 在ngx_http_process_request 函數(shù)內(nèi)部調(diào)用 ngx_http_handler(ngx_http_request_t r) 函數(shù),而在ngx_http_handler(ngx_http_request_t r) 函數(shù)內(nèi)部調(diào)用 函數(shù)ngx_http_core_run_phases進行多階段處理;

  6. 我們來看下多階段處理函數(shù)ngx_http_core_run_phases 

Nginx中怎么啟動并處理http請求

  1. http 多階段處理,每個階段可能對應(yīng)一個 handler,也可能對應(yīng)多個 handler,而每個階段對應(yīng)同一個checker。

因此上述while循環(huán)中,迭代所有http模塊handler,然后在handler函數(shù)中根據(jù)請求結(jié)構(gòu)體ngx_http_request_t做出相應(yīng)的處理;

上述gdb調(diào)試結(jié)果,可以看出NGX_HTTP_CONTENT_PHASE 階段的 checker函數(shù)為 ngx_http_core_content_phase,然后再在這個 checker 函數(shù)內(nèi)部執(zhí)行foo 模塊的 handler(ngx_http_foo_handler)。

等到多階段處理結(jié)束之后,最后再將 response 返回給客戶端。

上述就是小編為大家分享的Nginx中怎么啟動并處理http請求了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI