nginx的請求接收流程(一)

2021-06-05 18:31:23 字數 4156 閱讀 7328

今年我們組計畫寫一本nginx模組開發以及原理解析方面的書,整本書是以open book的形式在網上會定時的更新,**為本書分析的nginx原始碼版本為1.2.0,環境為linux,事件處理模型為epoll,大部分分析流程都基於以上假設。我會負責其中一些章節的編寫,所以打算在這裡寫一系列我負責章節內容相關的文章(主要包括nginx各phase模組的開發,nginx請求的處理流程等)。本篇文章主要會介紹nginx中請求的接收流程,包括請求頭的解析和請求體的讀取流程。

首先介紹一下rfc2616中定義的http請求基本格式:

request = request-line 

*(( general-header

| request-header

| entity-header ) crlf)

crlf

[ message-body ]

第一行是請求行(request line),用來說明請求方法,要訪問的資源以及所使用的http版本:

請求方法(method)的定義如下,其中最常用的是get,post方法:

method = "options" 

| "get"

| "head"

| "post"

| "put"

| "delete"

| "trace"

| "connect"

| extension-method

extension-method = token

要訪問的資源由統一資源地位符uri(uniform resource identifier)確定,它的乙個比較通用的組成格式(rfc2396)如下:

://?

一般來說根據請求方法(method)的不同,請求uri的格式會有所不同,通常只需寫出path和query部分。

http版本(version)定義如下,現在用的一般為1.0和1.1版本:

請求行的下一行則是請求頭,rfc2616中定義了3種不同型別的請求頭,分別為general-header,request-header和entity-header,每種型別rfc中都定義了一些通用的頭,其中entity-header型別可以包含自定義的頭。

現在開始介紹nginx中請求頭的解析,nginx的請求處理流程中,會涉及到2個非常重要的資料結構,ngx_connection_t和ngx_http_request_t,分別用來表示連線和請求,這2個資料結構在本書的前篇中已經做了比較詳細的介紹,沒有印象的讀者可以翻回去複習一下,整個請求處理流程從頭到尾,對應著這2個資料結構的分配,初始化,使用,重用和銷毀。

nginx在初始化階段,具體是在init process階段的ngx_event_process_init函式中會為每乙個監聽套接字分配乙個連線結構(ngx_connection_t),並將該連線結構的讀事件成員(read)的事件處理函式設定為ngx_event_accept,並且如果沒有使用accept互斥鎖的話,在這個函式中會將該讀事件掛載到nginx的事件處理模型上(poll或者epoll等),反之則會等到init process階段結束,在工作程序的事件處理迴圈中,某個程序搶到了accept鎖才能掛載該讀事件。

static ngx_int_t

ngx_event_process_init(ngx_cycle_t *cycle)

/* 初始化事件模型 */

for (m = 0; ngx_modules[m]; m++)

if (ngx_modules[m]->ctx_index != ecf->use)

module = ngx_modules[m]->ctx;

if (module->actions.init(cycle, ngx_timer_resolution) != ngx_ok)

break;

}...

/* for each listening socket */

/* 為每個監聽套接字分配乙個連線結構 */

ls = cycle->listening.elts;

for (i = 0; i < cycle->listening.nelts; i++)

c->log = &ls[i].log;

c->listening = &ls[i];

ls[i].connection = c;

rev = c->read;

rev->log = c->log;

/* 標識此讀事件為新請求連線事件 */

rev->accept = 1;

...#if (ngx_win32)

/* windows環境下不做分析,但原理類似 */

#else

/* 將讀事件結構的處理函式設定為ngx_event_accept */

rev->handler = ngx_event_accept;

/* 如果使用accept鎖的話,要在後面搶到鎖才能將監聽控制代碼掛載上事件處理模型上 */

if (ngx_use_accept_mutex)

/* 否則,將該監聽控制代碼直接掛載上事件處理模型 */

if (ngx_event_flags & ngx_use_rtsig_event)

} else

}#endif

}return ngx_ok;

}

當乙個工作程序在某個時刻將監聽事件掛載上事件處理模型之後,nginx就可以正式的接收並處理客戶端過來的請求了。這時如果有乙個使用者在瀏覽器的位址列內輸入乙個網域名稱,並且網域名稱解析伺服器將該網域名稱解析到一台由nginx監聽的伺服器上,nginx的事件處理模型接收到這個讀事件之後,會速度交由之前註冊好的事件處理函式ngx_event_accept來處理。

在ngx_event_accept函式中,nginx呼叫accept函式,從已連線佇列得到乙個連線以及對應的套接字,接著分配乙個連線結構(ngx_connection_t),並將新得到的套接字儲存在該連線結構中,這裡還會做一些基本的連線初始化工作:

首先給該連線分配乙個記憶體池,初始大小預設為256位元組,可通過connection_pool_size指令設定;

分配日誌結構,並儲存在其中,以便後續的日誌系統使用;

初始化連線相應的io收發函式,具體的io收發函式和使用的事件模型及作業系統相關;

分配乙個套介面位址(sockaddr),並將accept得到的對端位址拷貝在其中,儲存在sockaddr欄位;

將本地套介面位址儲存在local_sockaddr欄位,因為這個值是從監聽結構ngx_listening_t中可得,而監聽結構中儲存的只是配置檔案中設定的監聽位址,但是配置的監聽位址可能是萬用字元*,即監聽在所有的位址上,所以連線中儲存的這個值最終可能還會變動,會被確定為真正的接收位址;

將連線的寫事件設定為已就緒,即設定ready為1,nginx預設連線第一次為可寫;

如果監聽套接字設定了tcp_defer_accept屬性,則表示該連線上已經有資料報過來,於是設定讀事件為就緒;

將sockaddr欄位儲存的對端位址格式化為可讀字串,並儲存在addr_text欄位;

最後呼叫ngx_http_init_connection函式初始化該連線結構的其他部分。

ngx_http_init_connection函式最重要的工作是初始化讀寫事件的處理函式:將該連線結構的寫事件的處理函式設定為ngx_http_empty_handler,這個事件處理函式不會做任何操作,實際上nginx預設連線第一次可寫,不會掛載寫事件,如果有資料需要傳送,nginx會直接寫到這個連線,只有在發生一次寫不完的情況下,才會掛載寫事件到事件模型上,並設定真正的寫事件處理函式,這裡後面的章節還會做詳細介紹;讀事件的處理函式設定為ngx_http_init_request,此時如果該連線上已經有資料過來(設定了deferred accept),則會直接呼叫ngx_http_init_request函式來處理該請求,反之則設定乙個定時器並在事件處理模型上掛載乙個讀事件,等待資料到來或者超時。當然這裡不管是已經有資料到來,或者需要等待資料到來,又或者等待超時,最終都會進入讀事件的處理函式-ngx_http_init_request。

Nginx處理請求的流程

nginx處理請求過程 nginx使用乙個多程序模型來對外提供服務,乙個master程序和多個worker程序,master程序負責管理nginx本身和其他worker程序。所有實際上的業務處理邏輯都在worker程序。worker程序中有乙個函式,執行無限迴圈,不斷處理收到的來自客戶端的請求,並進...

Nginx請求處理流程

因為 nginx 執行在企業內網的最外層也就是邊緣節點,那麼他處理的的流量是其他應用伺服器處理流量的數倍,甚至幾個數量級,我們知道任何一種問題在不同的數量級下,他的解決方案是完全不同的,所以在 nginx 它所處理的應用場景中,所有的問題都會被放大,所以我們必須要去理解,為什麼 nginx 採用 m...

接收請求處理流程 介面測試流程及用例設計

介面測試是整專案測試過程中非常重要的一環,測試的物件是介面,所以可以很早的介入測試,對 邏輯進行全面驗證,更早的發現程式的問題,比ui測試效率更高,並且更容易驗證極端和異常的情況。類似於功能測試流程,乙個完整的介面測試流程如下 分析介面文件和需求文件 編寫介面測試計畫 編寫介面測試用例 介面測試執行...