鍍金池/ 教程/ Linux/ 基本數(shù)據(jù)結(jié)構(gòu)
示例: hello handler 模塊
什么是 Nginx
handler 模塊的掛載
Nginx 特點(diǎn)
handler 模塊簡(jiǎn)介
初探 Nginx 架構(gòu)
Nginx 的模塊化體系結(jié)構(gòu)
更多 handler 模塊示例分析
Nginx 基礎(chǔ)概念
upstream 模塊簡(jiǎn)介
Nginx 的請(qǐng)求處理
過(guò)濾模塊簡(jiǎn)介
基本數(shù)據(jù)結(jié)構(gòu)
模塊的基本結(jié)構(gòu)
負(fù)載均衡模塊
過(guò)濾模塊的分析
core 模塊
handler 模塊的基本結(jié)構(gòu)
Nginx 的配置系統(tǒng)
handler 的編寫(xiě)步驟
handler 模塊的編譯和使用
event 模塊

基本數(shù)據(jù)結(jié)構(gòu)

Nginx 的作者為追求極致的高效,自己實(shí)現(xiàn)了很多頗具特色的 Nginx 風(fēng)格的數(shù)據(jù)結(jié)構(gòu)以及公共函數(shù)。比如,Nginx 提供了帶長(zhǎng)度的字符串,根據(jù)編譯器選項(xiàng)優(yōu)化過(guò)的字符串拷貝函數(shù) ngx_copy 等。所以,在我們寫(xiě) Nginx 模塊時(shí),應(yīng)該盡量調(diào)用 Nginx 提供的 api,盡管有些 api 只是對(duì) glibc 的宏定義。本節(jié),我們介紹 string、list、buffer、chain 等一系列最基本的數(shù)據(jù)結(jié)構(gòu)及相關(guān)api的使用技巧以及注意事項(xiàng)。

ngx_str_t

在 Nginx 源碼目錄的 src/core 下面的 ngx_string.h|c 里面,包含了字符串的封裝以及字符串相關(guān)操作的 api。Nginx 提供了一個(gè)帶長(zhǎng)度的字符串結(jié)構(gòu) ngx_str_t,它的原型如下:

    typedef struct {
        size_t      len;
        u_char     *data;
    } ngx_str_t;

在結(jié)構(gòu)體當(dāng)中,data 指向字符串?dāng)?shù)據(jù)的第一個(gè)字符,字符串的結(jié)束用長(zhǎng)度來(lái)表示,而不是由'\\0'來(lái)表示結(jié)束。所以,在寫(xiě) Nginx 代碼時(shí),處理字符串的方法跟我們平時(shí)使用有很大的不一樣,但要時(shí)刻記住,字符串不以'\\0'結(jié)束,盡量使用 Nginx 提供的字符串操作的 api 來(lái)操作字符串。

那么,Nginx 這樣做有什么好處呢?首先,通過(guò)長(zhǎng)度來(lái)表示字符串長(zhǎng)度,減少計(jì)算字符串長(zhǎng)度的次數(shù)。其次,Nginx 可以重復(fù)引用一段字符串內(nèi)存,data 可以指向任意內(nèi)存,長(zhǎng)度表示結(jié)束,而不用去 copy 一份自己的字符串(因?yàn)槿绻?code>'\\0'結(jié)束,而不能更改原字符串,所以勢(shì)必要 copy 一段字符串)。我們?cè)?ngx_http_request_t 結(jié)構(gòu)體的成員中,可以找到很多字符串引用一段內(nèi)存的例子,比如 request_line、uri、args 等等,這些字符串的 data 部分,都是指向在接收數(shù)據(jù)時(shí)創(chuàng)建 buffer 所指向的內(nèi)存中,uri,args 就沒(méi)有必要 copy 一份出來(lái)。這樣的話,減少了很多不必要的內(nèi)存分配與拷貝。

正是基于此特性,在 Nginx 中,必須謹(jǐn)慎的去修改一個(gè)字符串。在修改字符串時(shí)需要認(rèn)真的去考慮:是否可以修改該字符串;字符串修改后,是否會(huì)對(duì)其它的引用造成影響。在后面介紹 ngx_unescape_uri 函數(shù)的時(shí)候,就會(huì)看到這一點(diǎn)。但是,使用 Nginx 的字符串會(huì)產(chǎn)生一些問(wèn)題,glibc 提供的很多系統(tǒng) api 函數(shù)大多是通過(guò)'\\0'來(lái)表示字符串的結(jié)束,所以我們?cè)谡{(diào)用系統(tǒng) api 時(shí),就不能直接傳入 str->data 了。此時(shí),通常的做法是創(chuàng)建一段 str->len + 1 大小的內(nèi)存,然后 copy 字符串,最后一個(gè)字節(jié)置為'\\0'。比較 hack 的做法是,將字符串最后一個(gè)字符的后一個(gè)字符 backup 一個(gè),然后設(shè)置為'\\0',在做完調(diào)用后,再由 backup 改回來(lái),但前提條件是,你得確定這個(gè)字符是可以修改的,而且是有內(nèi)存分配,不會(huì)越界,但一般不建議這么做。 接下來(lái),看看 Nginx 提供的操作字符串相關(guān)的 api。

    #define ngx_string(str)     { sizeof(str) - 1, (u_char *) str }

ngx_string(str) 是一個(gè)宏,它通過(guò)一個(gè)以'\\0'結(jié)尾的普通字符串 str 構(gòu)造一個(gè) Nginx 的字符串,鑒于其中采用 sizeof 操作符計(jì)算字符串長(zhǎng)度,因此參數(shù)必須是一個(gè)常量字符串。

    #define ngx_null_string     { 0, NULL }

定義變量時(shí),使用 ngx_null_string 初始化字符串為空字符串,符串的長(zhǎng)度為 0,data 為 NULL。

    #define ngx_str_set(str, text)                                               \
        (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text

ngx_str_set 用于設(shè)置字符串 str 為 text,由于使用 sizeof 計(jì)算長(zhǎng)度,故 text 必須為常量字符串。

    #define ngx_str_null(str)   (str)->len = 0; (str)->data = NULL

ngx_str_null 用于設(shè)置字符串 str 為空串,長(zhǎng)度為 0,data 為 NULL。

上面這四個(gè)函數(shù),使用時(shí)一定要小心,ngx_string 與 ngx_null_string 是“{,}”格式的,故只能用于賦值時(shí)初始化,如:

    ngx_str_t str = ngx_string("hello world");
    ngx_str_t str1 = ngx_null_string;

如果向下面這樣使用,就會(huì)有問(wèn)題,這里涉及到c語(yǔ)言中對(duì)結(jié)構(gòu)體變量賦值操作的語(yǔ)法規(guī)則,在此不做介紹。

    ngx_str_t str, str1;
    str = ngx_string("hello world");    // 編譯出錯(cuò)
    str1 = ngx_null_string;                // 編譯出錯(cuò)

這種情況,可以調(diào)用 ngx_str_set 與 ngx_str_null 這兩個(gè)函數(shù)來(lái)做:

    ngx_str_t str, str1;
    ngx_str_set(&str, "hello world");    
    ngx_str_null(&str1);

按照 C99 標(biāo)準(zhǔn),您也可以這么做:

    ngx_str_t str, str1;
    str  = (ngx_str_t) ngx_string("hello world");
    str1 = (ngx_str_t) ngx_null_string;

另外要注意的是,ngx_string 與 ngx_str_set 在調(diào)用時(shí),傳進(jìn)去的字符串一定是常量字符串,否則會(huì)得到意想不到的錯(cuò)誤(因?yàn)?ngx_str_set 內(nèi)部使用了 sizeof(),如果傳入的是 u_char*,那么計(jì)算的是這個(gè)指針的長(zhǎng)度,而不是字符串的長(zhǎng)度)。如:

   ngx_str_t str;
   u_char *a = "hello world";
   ngx_str_set(&str, a);    // 問(wèn)題產(chǎn)生

此外,值得注意的是,由于 ngx_str_set 與 ngx_str_null 實(shí)際上是兩行語(yǔ)句,故在 if/for/while 等語(yǔ)句中單獨(dú)使用需要用花括號(hào)括起來(lái),例如:

   ngx_str_t str;
   if (cond)
      ngx_str_set(&str, "true");     // 問(wèn)題產(chǎn)生
   else
      ngx_str_set(&str, "false");    // 問(wèn)題產(chǎn)生
   void ngx_strlow(u_char *dst, u_char *src, size_t n);

將 src 的前 n 個(gè)字符轉(zhuǎn)換成小寫(xiě)存放在 dst 字符串當(dāng)中,調(diào)用者需要保證 dst 指向的空間大于等于n,且指向的空間必須可寫(xiě)。操作不會(huì)對(duì)原字符串產(chǎn)生變動(dòng)。如要更改原字符串,可以:

    ngx_strlow(str->data, str->data, str->len);
    ngx_strncmp(s1, s2, n)

區(qū)分大小寫(xiě)的字符串比較,只比較前n個(gè)字符。

    ngx_strcmp(s1, s2)

區(qū)分大小寫(xiě)的不帶長(zhǎng)度的字符串比較。

    ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2);

不區(qū)分大小寫(xiě)的不帶長(zhǎng)度的字符串比較。

    ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);

不區(qū)分大小寫(xiě)的帶長(zhǎng)度的字符串比較,只比較前 n 個(gè)字符。

u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);
u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);

上面這三個(gè)函數(shù)用于字符串格式化,ngx_snprintf 的第二個(gè)參數(shù) max 指明 buf 的空間大小,ngx_slprintf 則通過(guò) last 來(lái)指明 buf 空間的大小。推薦使用第二個(gè)或第三個(gè)函數(shù)來(lái)格式化字符串,ngx_sprintf 函數(shù)還是比較危險(xiǎn)的,容易產(chǎn)生緩沖區(qū)溢出漏洞。在這一系列函數(shù)中,Nginx 在兼容 glibc 中格式化字符串的形式之外,還添加了一些方便格式化 Nginx 類型的一些轉(zhuǎn)義字符,比如%V用于格式化 ngx_str_t 結(jié)構(gòu)。在 Nginx 源文件的 ngx_string.c 中有說(shuō)明:

    /*
     * supported formats:
     *    %[0][width][x][X]O        off_t
     *    %[0][width]T              time_t
     *    %[0][width][u][x|X]z      ssize_t/size_t
     *    %[0][width][u][x|X]d      int/u_int
     *    %[0][width][u][x|X]l      long
     *    %[0][width|m][u][x|X]i    ngx_int_t/ngx_uint_t
     *    %[0][width][u][x|X]D      int32_t/uint32_t
     *    %[0][width][u][x|X]L      int64_t/uint64_t
     *    %[0][width|m][u][x|X]A    ngx_atomic_int_t/ngx_atomic_uint_t
     *    %[0][width][.width]f      double, max valid number fits to %18.15f
     *    %P                        ngx_pid_t
     *    %M                        ngx_msec_t
     *    %r                        rlim_t
     *    %p                        void *
     *    %V                        ngx_str_t *
     *    %v                        ngx_variable_value_t *
     *    %s                        null-terminated string
     *    %*s                       length and string
     *    %Z                        '\0'
     *    %N                        '\n'
     *    %c                        char
     *    %%                        %
     *
     *  reserved:
     *    %t                        ptrdiff_t
     *    %S                        null-terminated wchar string
     *    %C                        wchar
     */

這里特別要提醒的是,我們最常用于格式化 ngx_str_t 結(jié)構(gòu),其對(duì)應(yīng)的轉(zhuǎn)義符是%V,傳給函數(shù)的一定要是指針類型,否則程序就會(huì) coredump 掉。這也是我們最容易犯的錯(cuò)。比如:

    ngx_str_t str = ngx_string("hello world");
    u_char buffer[1024];
    ngx_snprintf(buffer, 1024, "%V", &str);    // 注意,str取地址
    void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);
    ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);

這兩個(gè)函數(shù)用于對(duì) str 進(jìn)行 base64 編碼與解碼,調(diào)用前,需要保證 dst 中有足夠的空間來(lái)存放結(jié)果,如果不知道具體大小,可先調(diào)用 ngx_base64_encoded_length 與 ngx_base64_decoded_length 來(lái)預(yù)估最大占用空間。

    uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size,
        ngx_uint_t type);

對(duì) src 進(jìn)行編碼,根據(jù) type 來(lái)按不同的方式進(jìn)行編碼,如果 dst 為 NULL,則返回需要轉(zhuǎn)義的字符的數(shù)量,由此可得到需要的空間大小。type 的類型可以是:

    #define NGX_ESCAPE_URI         0
    #define NGX_ESCAPE_ARGS        1
    #define NGX_ESCAPE_HTML        2
    #define NGX_ESCAPE_REFRESH     3
    #define NGX_ESCAPE_MEMCACHED   4
    #define NGX_ESCAPE_MAIL_AUTH   5
    void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);

對(duì) src 進(jìn)行反編碼,type 可以是 0、NGX_UNESCAPE_URI、NGX_UNESCAPE_REDIRECT 這三個(gè)值。如果是 0,則表示 src 中的所有字符都要進(jìn)行轉(zhuǎn)碼。如果是 NGX_UNESCAPE_URI 與 NGX_UNESCAPE_REDIRECT,則遇到'?'后就結(jié)束了,后面的字符就不管了。而 NGX_UNESCAPE_URI 與 NGX_UNESCAPE_REDIRECT 之間的區(qū)別是 NGX_UNESCAPE_URI 對(duì)于遇到的需要轉(zhuǎn)碼的字符,都會(huì)轉(zhuǎn)碼,而 NGX_UNESCAPE_REDIRECT 則只會(huì)對(duì)非可見(jiàn)字符進(jìn)行轉(zhuǎn)碼。

    uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size);

對(duì) html 標(biāo)簽進(jìn)行編碼。

當(dāng)然,我這里只介紹了一些常用的 api 的使用,大家可以先熟悉一下,在實(shí)際使用過(guò)程中,遇到不明白的,最快最直接的方法就是去看源碼,看 api 的實(shí)現(xiàn)或看 Nginx 自身調(diào)用 api 的地方是怎么做的,代碼就是最好的文檔。

ngx_pool_t

ngx_pool_t是一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu),在很多重要的場(chǎng)合都有使用,很多重要的數(shù)據(jù)結(jié)構(gòu)也都在使用它。那么它究竟是一個(gè)什么東西呢?簡(jiǎn)單的說(shuō),它提供了一種機(jī)制,幫助管理一系列的資源(如內(nèi)存,文件等),使得對(duì)這些資源的使用和釋放統(tǒng)一進(jìn)行,免除了使用過(guò)程中考慮到對(duì)各種各樣資源的什么時(shí)候釋放,是否遺漏了釋放的擔(dān)心。

例如對(duì)于內(nèi)存的管理,如果我們需要使用內(nèi)存,那么總是從一個(gè) ngx_pool_t 的對(duì)象中獲取內(nèi)存,在最終的某個(gè)時(shí)刻,我們銷毀這個(gè) ngx_pool_t 對(duì)象,所有這些內(nèi)存都被釋放了。這樣我們就不必要對(duì)對(duì)這些內(nèi)存進(jìn)行 malloc 和 free 的操作,不用擔(dān)心是否某塊被malloc出來(lái)的內(nèi)存沒(méi)有被釋放。因?yàn)楫?dāng) ngx_pool_t 對(duì)象被銷毀的時(shí)候,所有從這個(gè)對(duì)象中分配出來(lái)的內(nèi)存都會(huì)被統(tǒng)一釋放掉。

再比如我們要使用一系列的文件,但是我們打開(kāi)以后,最終需要都關(guān)閉,那么我們就把這些文件統(tǒng)一登記到一個(gè) ngx_pool_t 對(duì)象中,當(dāng)這個(gè) ngx_pool_t 對(duì)象被銷毀的時(shí)候,所有這些文件都將會(huì)被關(guān)閉。

從上面舉的兩個(gè)例子中我們可以看出,使用 ngx_pool_t 這個(gè)數(shù)據(jù)結(jié)構(gòu)的時(shí)候,所有的資源的釋放都在這個(gè)對(duì)象被銷毀的時(shí)刻,統(tǒng)一進(jìn)行了釋放,那么就會(huì)帶來(lái)一個(gè)問(wèn)題,就是這些資源的生存周期(或者說(shuō)被占用的時(shí)間)是跟 ngx_pool_t 的生存周期基本一致(ngx_pool_t 也提供了少量操作可以提前釋放資源)。從最高效的角度來(lái)說(shuō),這并不是最好的。比如,我們需要依次使用 A,B,C 三個(gè)資源,且使用完 B 的時(shí)候,A 就不會(huì)再被使用了,使用C的時(shí)候 A 和 B 都不會(huì)被使用到。如果不使用 ngx_pool_t 來(lái)管理這三個(gè)資源,那我們可能從系統(tǒng)里面申請(qǐng) A,使用 A,然后在釋放 A。接著申請(qǐng) B,使用 B,再釋放 B。最后申請(qǐng) C,使用 C,然后釋放 C。但是當(dāng)我們使用一個(gè) ngx_pool_t 對(duì)象來(lái)管理這三個(gè)資源的時(shí)候,A,B 和 C 的釋放是在最后一起發(fā)生的,也就是在使用完 C 以后。誠(chéng)然,這在客觀上增加了程序在一段時(shí)間的資源使用量。但是這也減輕了程序員分別管理三個(gè)資源的生命周期的工作。這也就是有所得,必有所失的道理。實(shí)際上是一個(gè)取舍的問(wèn)題,要看在具體的情況下,你更在乎的是哪個(gè)。

可以看一下在 Nginx 里面一個(gè)典型的使用 ngx_pool_t 的場(chǎng)景,對(duì)于 Nginx 處理的每個(gè) http request, Nginx 會(huì)生成一個(gè) ngx_pool_t 對(duì)象與這個(gè) http request 關(guān)聯(lián),所有處理過(guò)程中需要申請(qǐng)的資源都從這個(gè) ngx_pool_t 對(duì)象中獲取,當(dāng)這個(gè) http request 處理完成以后,所有在處理過(guò)程中申請(qǐng)的資源,都將隨著這個(gè)關(guān)聯(lián)的 ngx_pool_t 對(duì)象的銷毀而釋放。

ngx_pool_t 相關(guān)結(jié)構(gòu)及操作被定義在文件src/core/ngx_palloc.h|c中。

    typedef struct ngx_pool_s        ngx_pool_t; 

    struct ngx_pool_s {
        ngx_pool_data_t       d;
        size_t                max;
        ngx_pool_t           *current;
        ngx_chain_t          *chain;
        ngx_pool_large_t     *large;
        ngx_pool_cleanup_t   *cleanup;
        ngx_log_t            *log;
    };

從 ngx_pool_t 的一般使用者的角度來(lái)說(shuō),可不用關(guān)注 ngx_pool_t 結(jié)構(gòu)中各字段作用。所以這里也不會(huì)進(jìn)行詳細(xì)的解釋,當(dāng)然在說(shuō)明某些操作函數(shù)的使用的時(shí)候,如有必要,會(huì)進(jìn)行說(shuō)明。

下面我們來(lái)分別解釋下 ngx_pool_t 的相關(guān)操作。

    ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);

創(chuàng)建一個(gè)初始節(jié)點(diǎn)大小為 size 的 pool,log 為后續(xù)在該 pool 上進(jìn)行操作時(shí)輸出日志的對(duì)象。 需要說(shuō)明的是 size 的選擇,size 的大小必須小于等于 NGX_MAX_ALLOC_FROM_POOL,且必須大于 sizeof(ngx_pool_t)。

選擇大于 NGX_MAX_ALLOC_FROM_POOL 的值會(huì)造成浪費(fèi),因?yàn)榇笥谠撓拗频目臻g不會(huì)被用到(只是說(shuō)在第一個(gè)由 ngx_pool_t 對(duì)象管理的內(nèi)存塊上的內(nèi)存,后續(xù)的分配如果第一個(gè)內(nèi)存塊上的空閑部分已用完,會(huì)再分配的)。

選擇小于 sizeof(ngx_pool_t)的值會(huì)造成程序崩潰。由于初始大小的內(nèi)存塊中要用一部分來(lái)存儲(chǔ) ngx_pool_t 這個(gè)信息本身。

當(dāng)一個(gè) ngx_pool_t 對(duì)象被創(chuàng)建以后,該對(duì)象的 max 字段被賦值為 size-sizeof(ngx_pool_t)和 NGX_MAX_ALLOC_FROM_POOL 這兩者中比較小的。后續(xù)的從這個(gè) pool 中分配的內(nèi)存塊,在第一塊內(nèi)存使用完成以后,如果要繼續(xù)分配的話,就需要繼續(xù)從操作系統(tǒng)申請(qǐng)內(nèi)存。當(dāng)內(nèi)存的大小小于等于 max 字段的時(shí)候,則分配新的內(nèi)存塊,鏈接在 d 這個(gè)字段(實(shí)際上是 d.next 字段)管理的一條鏈表上。當(dāng)要分配的內(nèi)存塊是比 max 大的,那么從系統(tǒng)中申請(qǐng)的內(nèi)存是被掛接在 large 字段管理的一條鏈表上。我們暫且把這個(gè)稱之為大塊內(nèi)存鏈和小塊內(nèi)存鏈。

    void *ngx_palloc(ngx_pool_t *pool, size_t size); 

從這個(gè) pool 中分配一塊為 size 大小的內(nèi)存。注意,此函數(shù)分配的內(nèi)存的起始地址按照 NGX_ALIGNMENT 進(jìn)行了對(duì)齊。對(duì)齊操作會(huì)提高系統(tǒng)處理的速度,但會(huì)造成少量?jī)?nèi)存的浪費(fèi)。


    void *ngx_pnalloc(ngx_pool_t *pool, size_t size); 

從這個(gè) pool 中分配一塊為 size 大小的內(nèi)存。但是此函數(shù)分配的內(nèi)存并沒(méi)有像上面的函數(shù)那樣進(jìn)行過(guò)對(duì)齊。

.. code:: c

void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

該函數(shù)也是分配size大小的內(nèi)存,并且對(duì)分配的內(nèi)存塊進(jìn)行了清零。內(nèi)部實(shí)際上是轉(zhuǎn)調(diào)用ngx_palloc實(shí)現(xiàn)的。


    void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

按照指定對(duì)齊大小 alignment 來(lái)申請(qǐng)一塊大小為 size 的內(nèi)存。此處獲取的內(nèi)存不管大小都將被置于大內(nèi)存塊鏈中管理。

    ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);

對(duì)于被置于大塊內(nèi)存鏈,也就是被 large 字段管理的一列內(nèi)存中的某塊進(jìn)行釋放。該函數(shù)的實(shí)現(xiàn)是順序遍歷 large 管理的大塊內(nèi)存鏈表。所以效率比較低下。如果在這個(gè)鏈表中找到了這塊內(nèi)存,則釋放,并返回 NGX_OK。否則返回 NGX_DECLINED。

由于這個(gè)操作效率比較低下,除非必要,也就是說(shuō)這塊內(nèi)存非常大,確應(yīng)及時(shí)釋放,否則一般不需要調(diào)用。反正內(nèi)存在這個(gè) pool 被銷毀的時(shí)候,總歸會(huì)都釋放掉的嘛!

    ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size); 

ngx_pool_t 中的 cleanup 字段管理著一個(gè)特殊的鏈表,該鏈表的每一項(xiàng)都記錄著一個(gè)特殊的需要釋放的資源。對(duì)于這個(gè)鏈表中每個(gè)節(jié)點(diǎn)所包含的資源如何去釋放,是自說(shuō)明的。這也就提供了非常大的靈活性。意味著,ngx_pool_t 不僅僅可以管理內(nèi)存,通過(guò)這個(gè)機(jī)制,也可以管理任何需要釋放的資源,例如,關(guān)閉文件,或者刪除文件等等。下面我們看一下這個(gè)鏈表每個(gè)節(jié)點(diǎn)的類型:

    typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
    typedef void (*ngx_pool_cleanup_pt)(void *data);

    struct ngx_pool_cleanup_s {
        ngx_pool_cleanup_pt   handler;
        void                 *data;
        ngx_pool_cleanup_t   *next;
    };
  • data: 指明了該節(jié)點(diǎn)所對(duì)應(yīng)的資源。

  • handler: 是一個(gè)函數(shù)指針,指向一個(gè)可以釋放 data 所對(duì)應(yīng)資源的函數(shù)。該函數(shù)只有一個(gè)參數(shù),就是 data。

  • next: 指向該鏈表中下一個(gè)元素。

看到這里,ngx_pool_cleanup_add 這個(gè)函數(shù)的用法,我相信大家都應(yīng)該有一些明白了。但是這個(gè)參數(shù) size 是起什么作用的呢?這個(gè) size 就是要存儲(chǔ)這個(gè) data 字段所指向的資源的大小,該函數(shù)會(huì)為 data 分配 size 大小的空間。

比如我們需要最后刪除一個(gè)文件。那我們?cè)谡{(diào)用這個(gè)函數(shù)的時(shí)候,把 size 指定為存儲(chǔ)文件名的字符串的大小,然后調(diào)用這個(gè)函數(shù)給 cleanup 鏈表中增加一項(xiàng)。該函數(shù)會(huì)返回新添加的這個(gè)節(jié)點(diǎn)。我們?nèi)缓蟀堰@個(gè)節(jié)點(diǎn)中的 data 字段拷貝為文件名。把 hander 字段賦值為一個(gè)刪除文件的函數(shù)(當(dāng)然該函數(shù)的原型要按照 void (\*ngx_pool_cleanup_pt)(void \*data))。

    void ngx_destroy_pool(ngx_pool_t *pool);

該函數(shù)就是釋放 pool 中持有的所有內(nèi)存,以及依次調(diào)用 cleanup 字段所管理的鏈表中每個(gè)元素的 handler 字段所指向的函數(shù),來(lái)釋放掉所有該 pool 管理的資源。并且把 pool 指向的 ngx_pool_t 也釋放掉了,完全不可用了。

    void ngx_reset_pool(ngx_pool_t *pool);

該函數(shù)釋放 pool 中所有大塊內(nèi)存鏈表上的內(nèi)存,小塊內(nèi)存鏈上的內(nèi)存塊都修改為可用。但是不會(huì)去處理 cleanup鏈表上的項(xiàng)目。

ngx_array_t

ngx_array_t 是 Nginx 內(nèi)部使用的數(shù)組結(jié)構(gòu)。Nginx 的數(shù)組結(jié)構(gòu)在存儲(chǔ)上與大家認(rèn)知的 C 語(yǔ)言內(nèi)置的數(shù)組有相似性,比如實(shí)際上存儲(chǔ)數(shù)據(jù)的區(qū)域也是一大塊連續(xù)的內(nèi)存。但是數(shù)組除了存儲(chǔ)數(shù)據(jù)的內(nèi)存以外還包含一些元信息來(lái)描述相關(guān)的一些信息。下面我們從數(shù)組的定義上來(lái)詳細(xì)的了解一下。ngx_array_t 的定義位于src/core/ngx_array.c|h里面。

    typedef struct ngx_array_s       ngx_array_t;
    struct ngx_array_s {
        void        *elts;
        ngx_uint_t   nelts;
        size_t       size;
        ngx_uint_t   nalloc;
        ngx_pool_t  *pool;
    };
  • elts: 指向?qū)嶋H的數(shù)據(jù)存儲(chǔ)區(qū)域。

  • nelts: 數(shù)組實(shí)際元素個(gè)數(shù)。

  • size: 數(shù)組單個(gè)元素的大小,單位是字節(jié)。

  • nalloc: 數(shù)組的容量。表示該數(shù)組在不引發(fā)擴(kuò)容的前提下,可以最多存儲(chǔ)的元素的個(gè)數(shù)。當(dāng) nelts 增長(zhǎng)到達(dá) nalloc 時(shí),如果再往此數(shù)組中存儲(chǔ)元素,則會(huì)引發(fā)數(shù)組的擴(kuò)容。數(shù)組的容量將會(huì)擴(kuò)展到原有容量的 2 倍大小。實(shí)際上是分配新的一塊內(nèi)存,新的一塊內(nèi)存的大小是原有內(nèi)存大小的 2 倍。原有的數(shù)據(jù)會(huì)被拷貝到新的一塊內(nèi)存中。

  • pool: 該數(shù)組用來(lái)分配內(nèi)存的內(nèi)存池。

下面介紹 ngx_array_t 相關(guān)操作函數(shù)。

    ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);

創(chuàng)建一個(gè)新的數(shù)組對(duì)象,并返回這個(gè)對(duì)象。

  • p: 數(shù)組分配內(nèi)存使用的內(nèi)存池;
  • n: 數(shù)組的初始容量大小,即在不擴(kuò)容的情況下最多可以容納的元素個(gè)數(shù)。
  • size: 單個(gè)元素的大小,單位是字節(jié)。
    void ngx_array_destroy(ngx_array_t *a);

銷毀該數(shù)組對(duì)象,并釋放其分配的內(nèi)存回內(nèi)存池。

    void *ngx_array_push(ngx_array_t *a);

在數(shù)組 a 上新追加一個(gè)元素,并返回指向新元素的指針。需要把返回的指針使用類型轉(zhuǎn)換,轉(zhuǎn)換為具體的類型,然后再給新元素本身或者是各字段(如果數(shù)組的元素是復(fù)雜類型)賦值。

    void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);

在數(shù)組 a 上追加 n 個(gè)元素,并返回指向這些追加元素的首個(gè)元素的位置的指針。

    static ngx_inline ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size);

如果一個(gè)數(shù)組對(duì)象是被分配在堆上的,那么當(dāng)調(diào)用 ngx_array_destroy 銷毀以后,如果想再次使用,就可以調(diào)用此函數(shù)。

如果一個(gè)數(shù)組對(duì)象是被分配在棧上的,那么就需要調(diào)用此函數(shù),進(jìn)行初始化的工作以后,才可以使用。

注意事項(xiàng) 由于使用 ngx_palloc 分配內(nèi)存,數(shù)組在擴(kuò)容時(shí),舊的內(nèi)存不會(huì)被釋放,會(huì)造成內(nèi)存的浪費(fèi)。因此,最好能提前規(guī)劃好數(shù)組的容量,在創(chuàng)建或者初始化的時(shí)候一次搞定,避免多次擴(kuò)容,造成內(nèi)存浪費(fèi)。

ngx_hash_t

ngx_hash_t 是 Nginx 自己的 hash 表的實(shí)現(xiàn)。定義和實(shí)現(xiàn)位于src/core/ngx_hash.h|c中。ngx_hash_t 的實(shí)現(xiàn)也與數(shù)據(jù)結(jié)構(gòu)教科書(shū)上所描述的 hash 表的實(shí)現(xiàn)是大同小異。對(duì)于常用的解決沖突的方法有線性探測(cè),二次探測(cè)和開(kāi)鏈法等。ngx_hash_t 使用的是最常用的一種,也就是開(kāi)鏈法,這也是 STL 中的 hash 表使用的方法。

但是 ngx_hash_t 的實(shí)現(xiàn)又有其幾個(gè)顯著的特點(diǎn):

  1. ngx_hash_t 不像其他的 hash 表的實(shí)現(xiàn),可以插入刪除元素,它只能一次初始化,就構(gòu)建起整個(gè) hash 表以后,既不能再刪除,也不能在插入元素了。
  2. ngx_hash_t 的開(kāi)鏈并不是真的開(kāi)了一個(gè)鏈表,實(shí)際上是開(kāi)了一段連續(xù)的存儲(chǔ)空間,幾乎可以看做是一個(gè)數(shù)組。這是因?yàn)?ngx_hash_t 在初始化的時(shí)候,會(huì)經(jīng)歷一次預(yù)計(jì)算的過(guò)程,提前把每個(gè)桶里面會(huì)有多少元素放進(jìn)去給計(jì)算出來(lái),這樣就提前知道每個(gè)桶的大小了。那么就不需要使用鏈表,一段連續(xù)的存儲(chǔ)空間就足夠了。這也從一定程度上節(jié)省了內(nèi)存的使用。

從上面的描述,我們可以看出來(lái),這個(gè)值越大,越造成內(nèi)存的浪費(fèi)。就兩步,首先是初始化,然后就可以在里面進(jìn)行查找了。下面我們?cè)敿?xì)來(lái)看一下。

ngx_hash_t 的初始化。

 ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
 ngx_uint_t nelts);

首先我們來(lái)看一下初始化函數(shù)。該函數(shù)的第一個(gè)參數(shù) hinit 是初始化的一些參數(shù)的一個(gè)集合。 names 是初始化一個(gè) ngx_hash_t 所需要的所有 key 的一個(gè)數(shù)組。而 nelts 就是 key 的個(gè)數(shù)。下面先看一下 ngx_hash_init_t 類型,該類型提供了初始化一個(gè) hash 表所需要的一些基本信息。

    typedef struct {
        ngx_hash_t       *hash;
        ngx_hash_key_pt   key;

        ngx_uint_t        max_size;
        ngx_uint_t        bucket_size;

        char             *name;
        ngx_pool_t       *pool;
        ngx_pool_t       *temp_pool;
    } ngx_hash_init_t;
  • hash: 該字段如果為 NULL,那么調(diào)用完初始化函數(shù)后,該字段指向新創(chuàng)建出來(lái)的 hash 表。如果該字段不為 NULL,那么在初始的時(shí)候,所有的數(shù)據(jù)被插入了這個(gè)字段所指的 hash 表中。

  • key: 指向從字符串生成 hash 值的 hash 函數(shù)。Nginx 的源代碼中提供了默認(rèn)的實(shí)現(xiàn)函數(shù) ngx_hash_key_lc。

  • max_size: hash 表中的桶的個(gè)數(shù)。該字段越大,元素存儲(chǔ)時(shí)沖突的可能性越小,每個(gè)桶中存儲(chǔ)的元素會(huì)更少,則查詢起來(lái)的速度更快。當(dāng)然,這個(gè)值越大,越造成內(nèi)存的浪費(fèi)也越大,(實(shí)際上也浪費(fèi)不了多少)。
:bucket_size: 每個(gè)桶的最大限制大小,單位是字節(jié)。如果在初始化一個(gè) hash 表的時(shí)候,發(fā)現(xiàn)某個(gè)桶里面無(wú)法存的下所有屬于該桶的元素,則 hash 表初始化失敗。

name: 該 hash 表的名字。

pool: 該 hash 表分配內(nèi)存使用的 pool。

temp_pool: 該 hash 表使用的臨時(shí) pool,在初始化完成以后,該 pool 可以被釋放和銷毀掉。

下面來(lái)看一下存儲(chǔ) hash 表 key 的數(shù)組的結(jié)構(gòu)。

    typedef struct {
        ngx_str_t         key;
        ngx_uint_t        key_hash;
        void             *value;
    } ngx_hash_key_t;

key 和 value 的含義顯而易見(jiàn),就不用解釋了。key_hash 是對(duì) key 使用 hash 函數(shù)計(jì)算出來(lái)的值。

對(duì)這兩個(gè)結(jié)構(gòu)分析完成以后,我想大家應(yīng)該都已經(jīng)明白這個(gè)函數(shù)應(yīng)該是如何使用了吧。該函數(shù)成功初始化一個(gè) hash 表以后,返回 NGX_OK,否則返回 NGX_ERROR。

    void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len);

在 hash 里面查找 key 對(duì)應(yīng)的 value。實(shí)際上這里的 key 是對(duì)真正的 key(也就是 name)計(jì)算出的 hash 值。len 是 name 的長(zhǎng)度。

如果查找成功,則返回指向 value 的指針,否則返回 NULL。

ngx_hash_wildcard_t

Nginx 為了處理帶有通配符的域名的匹配問(wèn)題,實(shí)現(xiàn)了 ngx_hash_wildcard_t 這樣的 hash 表。他可以支持兩種類型的帶有通配符的域名。一種是通配符在前的,例如:\*.abc.com,也可以省略掉星號(hào),直接寫(xiě)成.abc.com。這樣的 key,可以匹配 www.abc.com,qqq.www.abc.com 之類的。另外一種是通配符在末尾的,例如:mail.xxx.\*,請(qǐng)?zhí)貏e注意通配符在末尾的不像位于開(kāi)始的通配符可以被省略掉。這樣的通配符,可以匹配 mail.xxx.com、mail.xxx.com.cn、mail.xxx.net 之類的域名。

有一點(diǎn)必須說(shuō)明,就是一個(gè) ngx_hash_wildcard_t 類型的 hash 表只能包含通配符在前的key或者是通配符在后的key。不能同時(shí)包含兩種類型的通配符的 key。ngx_hash_wildcard_t 類型變量的構(gòu)建是通過(guò)函數(shù) ngx_hash_wildcard_init 完成的,而查詢是通過(guò)函數(shù) ngx_hash_find_wc_head 或者 ngx_hash_find_wc_tail 來(lái)做的。ngx_hash_find_wc_head 查詢包含通配符在前的 key 的 hash 表的,而 ngx_hash_find_wc_tail 是查詢包含通配符在后的 key 的 hash 表的。

下面詳細(xì)說(shuō)明這幾個(gè)函數(shù)的用法。

    ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names,
        ngx_uint_t nelts);

該函數(shù)用來(lái)構(gòu)建一個(gè)可以包含通配符 key 的 hash 表。

  • hinit: 構(gòu)造一個(gè)通配符 hash 表的一些參數(shù)的一個(gè)集合。關(guān)于該參數(shù)對(duì)應(yīng)的類型的說(shuō)明,請(qǐng)參見(jiàn) ngx_hash_t 類型中 ngx_hash_init 函數(shù)的說(shuō)明。

  • names: 構(gòu)造此 hash 表的所有的通配符 key 的數(shù)組。特別要注意的是這里的 key 已經(jīng)都是被預(yù)處理過(guò)的。例如:\*.abc.com或者.abc.com被預(yù)處理完成以后,變成了com.abc.。而mail.xxx.\*則被預(yù)處理為mail.xxx.。為什么會(huì)被處理這樣?這里不得不簡(jiǎn)單地描述一下通配符 hash 表的實(shí)現(xiàn)原理。當(dāng)構(gòu)造此類型的 hash 表的時(shí)候,實(shí)際上是構(gòu)造了一個(gè) hash 表的一個(gè)“鏈表”,是通過(guò) hash 表中的 key “鏈接”起來(lái)的。比如:對(duì)于\*.abc.com將會(huì)構(gòu)造出 2 個(gè) hash 表,第一個(gè) hash 表中有一個(gè) key 為 com 的表項(xiàng),該表項(xiàng)的 value 包含有指向第二個(gè) hash 表的指針,而第二個(gè) hash 表中有一個(gè)表項(xiàng) abc,該表項(xiàng)的 value 包含有指向\*.abc.com對(duì)應(yīng)的 value 的指針。那么查詢的時(shí)候,比如查詢 www.abc.com 的時(shí)候,先查 com,通過(guò)查 com 可以找到第二級(jí)的 hash 表,在第二級(jí) hash 表中,再查找 abc,依次類推,直到在某一級(jí)的 hash 表中查到的表項(xiàng)對(duì)應(yīng)的 value 對(duì)應(yīng)一個(gè)真正的值而非一個(gè)指向下一級(jí) hash 表的指針的時(shí)候,查詢過(guò)程結(jié)束。這里有一點(diǎn)需要特別注意的,就是 names 數(shù)組中元素的 value 值低兩位 bit 必須為 0(有特殊用途)。如果不滿足這個(gè)條件,這個(gè) hash 表查詢不出正確結(jié)果。

  • nelts: names 數(shù)組元素的個(gè)數(shù)。

該函數(shù)執(zhí)行成功返回 NGX_OK,否則 NGX_ERROR。

    void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);

該函數(shù)查詢包含通配符在前的 key 的 hash 表的。

  • hwc: hash 表對(duì)象的指針。
  • name: 需要查詢的域名,例如: www.abc.com。
  • len: name 的長(zhǎng)度。

該函數(shù)返回匹配的通配符對(duì)應(yīng) value。如果沒(méi)有查到,返回 NULL。

    void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);

該函數(shù)查詢包含通配符在末尾的 key 的 hash 表的。

參數(shù)及返回值請(qǐng)參加上個(gè)函數(shù)的說(shuō)明。

ngx_hash_combined_t

組合類型 hash 表,該 hash 表的定義如下:

    typedef struct {
        ngx_hash_t            hash;
        ngx_hash_wildcard_t  *wc_head;
        ngx_hash_wildcard_t  *wc_tail;
    } ngx_hash_combined_t;

從其定義顯見(jiàn),該類型實(shí)際上包含了三個(gè) hash 表,一個(gè)普通 hash 表,一個(gè)包含前向通配符的 hash 表和一個(gè)包含后向通配符的 hash 表。

Nginx 提供該類型的作用,在于提供一個(gè)方便的容器包含三個(gè)類型的 hash 表,當(dāng)有包含通配符的和不包含通配符的一組 key 構(gòu)建 hash 表以后,以一種方便的方式來(lái)查詢,你不需要再考慮一個(gè) key 到底是應(yīng)該到哪個(gè)類型的 hash 表里去查了。

構(gòu)造這樣一組合 hash 表的時(shí)候,首先定義一個(gè)該類型的變量,再分別構(gòu)造其包含的三個(gè)子 hash 表即可。

對(duì)于該類型 hash 表的查詢,Nginx 提供了一個(gè)方便的函數(shù) ngx_hash_find_combined。

    void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key,
    u_char *name, size_t len);

該函數(shù)在此組合 hash 表中,依次查詢其三個(gè)子 hash 表,看是否匹配,一旦找到,立即返回查找結(jié)果,也就是說(shuō)如果有多個(gè)可能匹配,則只返回第一個(gè)匹配的結(jié)果。

  • hash: 此組合 hash 表對(duì)象。
  • key: 根據(jù) name 計(jì)算出的 hash 值。
  • name: key 的具體內(nèi)容。
  • len: name 的長(zhǎng)度。

返回查詢的結(jié)果,未查到則返回 NULL。

ngx_hash_keys_arrays_t

大家看到在構(gòu)建一個(gè) ngx_hash_wildcard_t 的時(shí)候,需要對(duì)通配符的哪些 key 進(jìn)行預(yù)處理。這個(gè)處理起來(lái)比較麻煩。而當(dāng)有一組 key,這些里面既有無(wú)通配符的 key,也有包含通配符的 key 的時(shí)候。我們就需要構(gòu)建三個(gè) hash 表,一個(gè)包含普通的 key 的 hash 表,一個(gè)包含前向通配符的 hash 表,一個(gè)包含后向通配符的 hash 表(或者也可以把這三個(gè) hash 表組合成一個(gè) ngx_hash_combined_t)。在這種情況下,為了讓大家方便的構(gòu)造這些 hash 表,Nginx 提供給了此輔助類型。

該類型以及相關(guān)的操作函數(shù)也定義在src/core/ngx_hash.h|c里。我們先來(lái)看一下該類型的定義。

    typedef struct {
        ngx_uint_t        hsize;

        ngx_pool_t       *pool;
        ngx_pool_t       *temp_pool;

        ngx_array_t       keys;
        ngx_array_t      *keys_hash;

        ngx_array_t       dns_wc_head;
        ngx_array_t      *dns_wc_head_hash;

        ngx_array_t       dns_wc_tail;
        ngx_array_t      *dns_wc_tail_hash;
    } ngx_hash_keys_arrays_t;
  • hsize: 將要構(gòu)建的 hash 表的桶的個(gè)數(shù)。對(duì)于使用這個(gè)結(jié)構(gòu)中包含的信息構(gòu)建的三種類型的 hash 表都會(huì)使用此參數(shù)。

  • pool: 構(gòu)建這些 hash 表使用的 pool。

  • temp_pool: 在構(gòu)建這個(gè)類型以及最終的三個(gè) hash 表過(guò)程中可能用到臨時(shí) pool。該 temp_pool 可以在構(gòu)建完成以后,被銷毀掉。這里只是存放臨時(shí)的一些內(nèi)存消耗。

  • keys: 存放所有非通配符 key 的數(shù)組。

  • keys_hash: 這是個(gè)二維數(shù)組,第一個(gè)維度代表的是 bucket 的編號(hào),那么 keys_hash[i] 中存放的是所有的 key 算出來(lái)的 hash 值對(duì) hsize 取模以后的值為 i 的 key。假設(shè)有 3 個(gè) key,分別是 key1,key2 和 key3 假設(shè) hash 值算出來(lái)以后對(duì) hsize 取模的值都是 i,那么這三個(gè) key 的值就順序存放在keys_hash[i][0],keys_hash[i][1], keys_hash[i][2]。該值在調(diào)用的過(guò)程中用來(lái)保存和檢測(cè)是否有沖突的 key 值,也就是是否有重復(fù)。

  • dns_wc_head: 放前向通配符 key 被處理完成以后的值。比如:\*.abc.com 被處理完成以后,變成 “com.abc.” 被存放在此數(shù)組中。

  • dns_wc_tail: 存放后向通配符 key 被處理完成以后的值。比如:mail.xxx.\* 被處理完成以后,變成 “mail.xxx.” 被存放在此數(shù)組中。

  • dns_wc_head_hash: 該值在調(diào)用的過(guò)程中用來(lái)保存和檢測(cè)是否有沖突的前向通配符的 key 值,也就是是否有重復(fù)。

  • dns_wc_tail_hash: 該值在調(diào)用的過(guò)程中用來(lái)保存和檢測(cè)是否有沖突的后向通配符的 key 值,也就是是否有重復(fù)。

在定義一個(gè)這個(gè)類型的變量,并對(duì)字段 pool 和 temp_pool 賦值以后,就可以調(diào)用函數(shù) ngx_hash_add_key 把所有的 key 加入到這個(gè)結(jié)構(gòu)中了,該函數(shù)會(huì)自動(dòng)實(shí)現(xiàn)普通 key,帶前向通配符的 key 和帶后向通配符的 key 的分類和檢查,并將這個(gè)些值存放到對(duì)應(yīng)的字段中去,然后就可以通過(guò)檢查這個(gè)結(jié)構(gòu)體中的 keys、dns_wc_head、dns_wc_tail 三個(gè)數(shù)組是否為空,來(lái)決定是否構(gòu)建普通 hash 表,前向通配符 hash 表和后向通配符 hash 表了(在構(gòu)建這三個(gè)類型的 hash 表的時(shí)候,可以分別使用 keys、dns_wc_head、dns_wc_tail三個(gè)數(shù)組)。

構(gòu)建出這三個(gè) hash 表以后,可以組合在一個(gè) ngx_hash_combined_t 對(duì)象中,使用 ngx_hash_find_combined 進(jìn)行查找。或者是仍然保持三個(gè)獨(dú)立的變量對(duì)應(yīng)這三個(gè) hash 表,自己決定何時(shí)以及在哪個(gè) hash 表中進(jìn)行查詢。

    ngx_int_t ngx_hash_keys_array_init(ngx_hash_keys_arrays_t *ha, ngx_uint_t type);

初始化這個(gè)結(jié)構(gòu),主要是對(duì)這個(gè)結(jié)構(gòu)中的 ngx_array_t 類型的字段進(jìn)行初始化,成功返回 NGX_OK。

  • ha: 該結(jié)構(gòu)的對(duì)象指針。

  • type: 該字段有 2 個(gè)值可選擇,即 NGX_HASH_SMALL 和 NGX_HASH_LARGE。用來(lái)指明將要建立的 hash 表的類型,如果是 NGX_HASH_SMALL,則有比較小的桶的個(gè)數(shù)和數(shù)組元素大小。NGX_HASH_LARGE 則相反。
    ngx_int_t ngx_hash_add_key(ngx_hash_keys_arrays_t *ha, ngx_str_t *key,
    void *value, ngx_uint_t flags);

一般是循環(huán)調(diào)用這個(gè)函數(shù),把一組鍵值對(duì)加入到這個(gè)結(jié)構(gòu)體中。返回 NGX_OK 是加入成功。返回 NGX_BUSY 意味著key值重復(fù)。

  • ha: 該結(jié)構(gòu)的對(duì)象指針。

  • key: 參數(shù)名自解釋了。

  • value: 參數(shù)名自解釋了。

  • flags: 有兩個(gè)標(biāo)志位可以設(shè)置,NGX_HASH_WILDCARD_KEY 和 NGX_HASH_READONLY_KEY。同時(shí)要設(shè)置的使用邏輯與操作符就可以了。NGX_HASH_READONLY_KEY 被設(shè)置的時(shí)候,在計(jì)算 hash 值的時(shí)候,key 的值不會(huì)被轉(zhuǎn)成小寫(xiě)字符,否則會(huì)。NGX_HASH_WILDCARD_KEY 被設(shè)置的時(shí)候,說(shuō)明 key 里面可能含有通配符,會(huì)進(jìn)行相應(yīng)的處理。如果兩個(gè)標(biāo)志位都不設(shè)置,傳 0。

有關(guān)于這個(gè)數(shù)據(jù)結(jié)構(gòu)的使用,可以參考src/http/ngx_http.c中的 ngx_http_server_names 函數(shù)。

ngx_chain_t

Nginx 的 filter 模塊在處理從別的 filter 模塊或者是 handler 模塊傳遞過(guò)來(lái)的數(shù)據(jù)(實(shí)際上就是需要發(fā)送給客戶端的 http response)。這個(gè)傳遞過(guò)來(lái)的數(shù)據(jù)是以一個(gè)鏈表的形式(ngx_chain_t)。而且數(shù)據(jù)可能被分多次傳遞過(guò)來(lái)。也就是多次調(diào)用 filter 的處理函數(shù),以不同的 ngx_chain_t。

該結(jié)構(gòu)被定義在src/core/ngx_buf.h|c。下面我們來(lái)看一下 ngx_chain_t 的定義。

    typedef struct ngx_chain_s       ngx_chain_t;

    struct ngx_chain_s {
        ngx_buf_t    *buf;
        ngx_chain_t  *next;
    };

就 2 個(gè)字段,next 指向這個(gè)鏈表的下個(gè)節(jié)點(diǎn)。buf 指向?qū)嶋H的數(shù)據(jù)。所以在這個(gè)鏈表上追加節(jié)點(diǎn)也是非常容易,只要把末尾元素的 next 指針指向新的節(jié)點(diǎn),把新節(jié)點(diǎn)的 next 賦值為 NULL 即可。

    ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);

該函數(shù)創(chuàng)建一個(gè) ngx_chain_t 的對(duì)象,并返回指向?qū)ο蟮闹羔槪》祷?NULL。

    #define ngx_free_chain(pool, cl)                                             \
        cl->next = pool->chain;                                                  \
    pool->chain = cl

該宏釋放一個(gè) ngx_chain_t 類型的對(duì)象。如果要釋放整個(gè) chain,則迭代此鏈表,對(duì)每個(gè)節(jié)點(diǎn)使用此宏即可。

注意: 對(duì) ngx_chaint_t 類型的釋放,并不是真的釋放了內(nèi)存,而僅僅是把這個(gè)對(duì)象掛在了這個(gè) pool 對(duì)象的一個(gè)叫做 chain 的字段對(duì)應(yīng)的 chain 上,以供下次從這個(gè) pool 上分配 ngx_chain_t 類型對(duì)象的時(shí)候,快速的從這個(gè) pool->chain上 取下鏈?zhǔn)自鼐头祷亓?,?dāng)然,如果這個(gè)鏈?zhǔn)强盏模艜?huì)真的在這個(gè) pool 上使用 ngx_palloc 函數(shù)進(jìn)行分配。

ngx_buf_t

這個(gè) ngx_buf_t 就是這個(gè) ngx_chain_t 鏈表的每個(gè)節(jié)點(diǎn)的實(shí)際數(shù)據(jù)。該結(jié)構(gòu)實(shí)際上是一種抽象的數(shù)據(jù)結(jié)構(gòu),它代表某種具體的數(shù)據(jù)。這個(gè)數(shù)據(jù)可能是指向內(nèi)存中的某個(gè)緩沖區(qū),也可能指向一個(gè)文件的某一部分,也可能是一些純?cè)獢?shù)據(jù)(元數(shù)據(jù)的作用在于指示這個(gè)鏈表的讀取者對(duì)讀取的數(shù)據(jù)進(jìn)行不同的處理)。

該數(shù)據(jù)結(jié)構(gòu)位于src/core/ngx_buf.h|c文件中。我們來(lái)看一下它的定義。

    struct ngx_buf_s {
        u_char          *pos;
        u_char          *last;
        off_t            file_pos;
        off_t            file_last;

        u_char          *start;         /* start of buffer */
        u_char          *end;           /* end of buffer */
        ngx_buf_tag_t    tag;
        ngx_file_t      *file;
        ngx_buf_t       *shadow;

        /* the buf's content could be changed */
        unsigned         temporary:1;

        /*
         * the buf's content is in a memory cache or in a read only memory
         * and must not be changed
         */
        unsigned         memory:1;

        /* the buf's content is mmap()ed and must not be changed */
        unsigned         mmap:1;

        unsigned         recycled:1;
        unsigned         in_file:1;
        unsigned         flush:1;
        unsigned         sync:1;
        unsigned         last_buf:1;
        unsigned         last_in_chain:1;

        unsigned         last_shadow:1;
        unsigned         temp_file:1;

        /* STUB */ int   num;
    };
  • pos:當(dāng) buf 所指向的數(shù)據(jù)在內(nèi)存里的時(shí)候,pos 指向的是這段數(shù)據(jù)開(kāi)始的位置。

  • last:當(dāng) buf 所指向的數(shù)據(jù)在內(nèi)存里的時(shí)候,last 指向的是這段數(shù)據(jù)結(jié)束的位置。

  • file_pos:當(dāng) buf 所指向的數(shù)據(jù)是在文件里的時(shí)候,file_pos 指向的是這段數(shù)據(jù)的開(kāi)始位置在文件中的偏移量。

  • file_last:當(dāng) buf 所指向的數(shù)據(jù)是在文件里的時(shí)候,file_last 指向的是這段數(shù)據(jù)的結(jié)束位置在文件中的偏移量。

  • start:當(dāng) buf 所指向的數(shù)據(jù)在內(nèi)存里的時(shí)候,這一整塊內(nèi)存包含的內(nèi)容可能被包含在多個(gè) buf 中(比如在某段數(shù)據(jù)中間插入了其他的數(shù)據(jù),這一塊數(shù)據(jù)就需要被拆分開(kāi))。那么這些 buf 中的 start 和 end 都指向這一塊內(nèi)存的開(kāi)始地址和結(jié)束地址。而 pos 和 last 指向本 buf 所實(shí)際包含的數(shù)據(jù)的開(kāi)始和結(jié)尾。

  • end:解釋參見(jiàn) start。

  • tag:實(shí)際上是一個(gè)void *類型的指針,使用者可以關(guān)聯(lián)任意的對(duì)象上去,只要對(duì)使用者有意義。

  • file:當(dāng) buf 所包含的內(nèi)容在文件中時(shí),file字段指向?qū)?yīng)的文件對(duì)象。

  • shadow:當(dāng)這個(gè) buf 完整 copy 了另外一個(gè) buf 的所有字段的時(shí)候,那么這兩個(gè) buf 指向的實(shí)際上是同一塊內(nèi)存,或者是同一個(gè)文件的同一部分,此時(shí)這兩個(gè) buf 的 shadow 字段都是指向?qū)Ψ降?。那么?duì)于這樣的兩個(gè) buf,在釋放的時(shí)候,就需要使用者特別小心,具體是由哪里釋放,要提前考慮好,如果造成資源的多次釋放,可能會(huì)造成程序崩潰!

  • temporary:為 1 時(shí)表示該 buf 所包含的內(nèi)容是在一個(gè)用戶創(chuàng)建的內(nèi)存塊中,并且可以被在 filter 處理的過(guò)程中進(jìn)行變更,而不會(huì)造成問(wèn)題。

  • memory:為 1 時(shí)表示該 buf 所包含的內(nèi)容是在內(nèi)存中,但是這些內(nèi)容卻不能被進(jìn)行處理的 filter 進(jìn)行變更。

  • mmap:為 1 時(shí)表示該 buf 所包含的內(nèi)容是在內(nèi)存中, 是通過(guò) mmap 使用內(nèi)存映射從文件中映射到內(nèi)存中的,這些內(nèi)容卻不能被進(jìn)行處理的 filter 進(jìn)行變更。

  • recycled:可以回收的。也就是這個(gè) buf 是可以被釋放的。這個(gè)字段通常是配合 shadow 字段一起使用的,對(duì)于使用 ngx_create_temp_buf 函數(shù)創(chuàng)建的 buf,并且是另外一個(gè) buf 的 shadow,那么可以使用這個(gè)字段來(lái)標(biāo)示這個(gè)buf是可以被釋放的。

  • in_file:為 1 時(shí)表示該 buf 所包含的內(nèi)容是在文件中。

  • flush:遇到有 flush 字段被設(shè)置為 1 的 buf 的 chain,則該 chain 的數(shù)據(jù)即便不是最后結(jié)束的數(shù)據(jù)(last_buf被設(shè)置,標(biāo)志所有要輸出的內(nèi)容都完了),也會(huì)進(jìn)行輸出,不會(huì)受 postpone_output 配置的限制,但是會(huì)受到發(fā)送速率等其他條件的限制。

  • last_buf:數(shù)據(jù)被以多個(gè) chain 傳遞給了過(guò)濾器,此字段為 1 表明這是最后一個(gè) buf。

  • last_in_chain:在當(dāng)前的 chain 里面,此 buf 是最后一個(gè)。特別要注意的是 last_in_chain 的 buf 不一定是last_buf,但是 last_buf 的 buf 一定是 last_in_chain 的。這是因?yàn)閿?shù)據(jù)會(huì)被以多個(gè) chain 傳遞給某 個(gè)filter 模塊。

  • last_shadow:在創(chuàng)建一個(gè) buf 的 shadow 的時(shí)候,通常將新創(chuàng)建的一個(gè) buf 的 last_shadow 置為 1。

  • temp_file:由于受到內(nèi)存使用的限制,有時(shí)候一些 buf 的內(nèi)容需要被寫(xiě)到磁盤(pán)上的臨時(shí)文件中去,那么這時(shí),就設(shè)置此標(biāo)志。

對(duì)于此對(duì)象的創(chuàng)建,可以直接在某個(gè) ngx_pool_t 上分配,然后根據(jù)需要,給對(duì)應(yīng)的字段賦值。也可以使用定義好的 2 個(gè)宏:

    #define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
    #define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))

這兩個(gè)宏使用類似函數(shù),也是不說(shuō)自明的。

對(duì)于創(chuàng)建 temporary 字段為 1 的 buf(就是其內(nèi)容可以被后續(xù)的 filter 模塊進(jìn)行修改),可以直接使用函數(shù) ngx_create_temp_buf 進(jìn)行創(chuàng)建。

    ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);

該函數(shù)創(chuàng)建一個(gè) ngx_buf_t 類型的對(duì)象,并返回指向這個(gè)對(duì)象的指針,創(chuàng)建失敗返回 NULL。

對(duì)于創(chuàng)建的這個(gè)對(duì)象,它的 start 和 end 指向新分配內(nèi)存開(kāi)始和結(jié)束的地方。pos 和 last 都指向這塊新分配內(nèi)存的開(kāi)始處,這樣,后續(xù)的操作可以在這塊新分配的內(nèi)存上存入數(shù)據(jù)。

  • pool: 分配該 buf 和 buf 使用的內(nèi)存所使用的 pool。
  • size: 該 buf 使用的內(nèi)存的大小。

為了配合對(duì) ngx_buf_t 的使用,Nginx 定義了以下的宏方便操作。

    #define ngx_buf_in_memory(b)        (b->temporary || b->memory || b->mmap)

返回這個(gè) buf 里面的內(nèi)容是否在內(nèi)存里。

    #define ngx_buf_in_memory_only(b)   (ngx_buf_in_memory(b) && !b->in_file)

返回這個(gè) buf 里面的內(nèi)容是否僅僅在內(nèi)存里,并且沒(méi)有在文件里。

    #define ngx_buf_special(b)                                                   \
        ((b->flush || b->last_buf || b->sync)                                    \
         && !ngx_buf_in_memory(b) && !b->in_file)

返回該 buf 是否是一個(gè)特殊的 buf,只含有特殊的標(biāo)志和沒(méi)有包含真正的數(shù)據(jù)。

    #define ngx_buf_sync_only(b)                                                 \
        (b->sync                                                                 \
         && !ngx_buf_in_memory(b) && !b->in_file && !b->flush && !b->last_buf)

返回該 buf 是否是一個(gè)只包含 sync 標(biāo)志而不包含真正數(shù)據(jù)的特殊 buf。

    #define ngx_buf_size(b)                                                      \
        (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                                (b->file_last - b->file_pos))

返回該 buf 所含數(shù)據(jù)的大小,不管這個(gè)數(shù)據(jù)是在文件里還是在內(nèi)存里。

ngx_list_t

ngx_list_t 顧名思義,看起來(lái)好像是一個(gè) list 的數(shù)據(jù)結(jié)構(gòu)。這樣的說(shuō)法,算對(duì)也不算對(duì)。因?yàn)樗?list 類型數(shù)據(jù)結(jié)構(gòu)的一些特點(diǎn),比如可以添加元素,實(shí)現(xiàn)自增長(zhǎng),不會(huì)像數(shù)組類型的數(shù)據(jù)結(jié)構(gòu),受到初始設(shè)定的數(shù)組容量的限制,并且它跟我們常見(jiàn)的 list 型數(shù)據(jù)結(jié)構(gòu)也是一樣的,內(nèi)部實(shí)現(xiàn)使用了一個(gè)鏈表。

那么它跟我們常見(jiàn)的鏈表實(shí)現(xiàn)的 list 有什么不同呢?不同點(diǎn)就在于它的節(jié)點(diǎn),它的節(jié)點(diǎn)不像我們常見(jiàn)的 list 的節(jié)點(diǎn),只能存放一個(gè)元素,ngx_list_t 的節(jié)點(diǎn)實(shí)際上是一個(gè)固定大小的數(shù)組。

在初始化的時(shí)候,我們需要設(shè)定元素需要占用的空間大小,每個(gè)節(jié)點(diǎn)數(shù)組的容量大小。在添加元素到這個(gè) list 里面的時(shí)候,會(huì)在最尾部的節(jié)點(diǎn)里的數(shù)組上添加元素,如果這個(gè)節(jié)點(diǎn)的數(shù)組存滿了,就再增加一個(gè)新的節(jié)點(diǎn)到這個(gè) list 里面去。

好了,看到這里,大家應(yīng)該基本上明白這個(gè) list 結(jié)構(gòu)了吧?還不明白也沒(méi)有關(guān)系,下面我們來(lái)具體看一下它的定義,這些定義和相關(guān)的操作函數(shù)定義在src/core/ngx_list.h|c文件中。

    typedef struct {
        ngx_list_part_t  *last;
        ngx_list_part_t   part;
        size_t            size;
        ngx_uint_t        nalloc;
        ngx_pool_t       *pool;
    } ngx_list_t;
  • last: 指向該鏈表的最后一個(gè)節(jié)點(diǎn)。
  • part: 該鏈表的首個(gè)存放具體元素的節(jié)點(diǎn)。
  • size: 鏈表中存放的具體元素所需內(nèi)存大小。
  • nalloc: 每個(gè)節(jié)點(diǎn)所含的固定大小的數(shù)組的容量。
  • pool: 該 list 使用的分配內(nèi)存的 pool。

好,我們?cè)诳匆幌旅總€(gè)節(jié)點(diǎn)的定義。

    typedef struct ngx_list_part_s  ngx_list_part_t;
    struct ngx_list_part_s {
        void             *elts;
        ngx_uint_t        nelts;
        ngx_list_part_t  *next;
    };
  • elts: 節(jié)點(diǎn)中存放具體元素的內(nèi)存的開(kāi)始地址。

  • nelts: 節(jié)點(diǎn)中已有元素個(gè)數(shù)。這個(gè)值是不能大于鏈表頭節(jié)點(diǎn) ngx_list_t 類型中的 nalloc 字段的。

  • next: 指向下一個(gè)節(jié)點(diǎn)。

我們來(lái)看一下提供的一個(gè)操作的函數(shù)。

    ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);

該函數(shù)創(chuàng)建一個(gè) ngx_list_t 類型的對(duì)象,并對(duì)該 list 的第一個(gè)節(jié)點(diǎn)分配存放元素的內(nèi)存空間。

  • pool: 分配內(nèi)存使用的 pool。

  • n: 每個(gè)節(jié)點(diǎn)(ngx_list_part_t)固定長(zhǎng)度的數(shù)組的長(zhǎng)度,即最多可以存放的元素個(gè)數(shù)。

  • size: 每個(gè)元素所占用的內(nèi)存大小。

  • 返回值: 成功返回指向創(chuàng)建的 ngx_list_t 對(duì)象的指針,失敗返回 NULL。
    void *ngx_list_push(ngx_list_t *list);

該函數(shù)在給定的 list 的尾部追加一個(gè)元素,并返回指向新元素存放空間的指針。如果追加失敗,則返回 NULL。

    static ngx_inline ngx_int_t
    ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size);

該函數(shù)是用于 ngx_list_t 類型的對(duì)象已經(jīng)存在,但是其第一個(gè)節(jié)點(diǎn)存放元素的內(nèi)存空間還未分配的情況下,可以調(diào)用此函數(shù)來(lái)給這個(gè) list 的首節(jié)點(diǎn)來(lái)分配存放元素的內(nèi)存空間。

那么什么時(shí)候會(huì)出現(xiàn)已經(jīng)有了 ngx_list_t 類型的對(duì)象,而其首節(jié)點(diǎn)存放元素的內(nèi)存尚未分配的情況呢?那就是這個(gè) ngx_list_t 類型的變量并不是通過(guò)調(diào)用 ngx_list_create 函數(shù)創(chuàng)建的。例如:如果某個(gè)結(jié)構(gòu)體的一個(gè)成員變量是 ngx_list_t 類型的,那么當(dāng)這個(gè)結(jié)構(gòu)體類型的對(duì)象被創(chuàng)建出來(lái)的時(shí)候,這個(gè)成員變量也被創(chuàng)建出來(lái)了,但是它的首節(jié)點(diǎn)的存放元素的內(nèi)存并未被分配。

總之,如果這個(gè) ngx_list_t 類型的變量,如果不是你通過(guò)調(diào)用函數(shù) ngx_list_create 創(chuàng)建的,那么就必須調(diào)用此函數(shù)去初始化,否則,你往這個(gè) list 里追加元素就可能引發(fā)不可預(yù)知的行為,亦或程序會(huì)崩潰!

ngx_queue_t

ngx_queue_t 是 Nginx 中的雙向鏈表,在 Nginx 源碼目錄src/core下面的ngx_queue.h|c里面。它的原型如下:

    ty