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 等。所以,在我們寫 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)。
在 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é)束。所以,在寫 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)換成小寫存放在 dst 字符串當(dāng)中,調(diào)用者需要保證 dst 指向的空間大于等于n,且指向的空間必須可寫。操作不會(huì)對(duì)原字符串產(chǎn)生變動(dòng)。如要更改原字符串,可以:
ngx_strlow(str->data, str->data, str->len);
ngx_strncmp(s1, s2, n)
區(qū)分大小寫的字符串比較,只比較前n個(gè)字符。
ngx_strcmp(s1, s2)
區(qū)分大小寫的不帶長(zhǎng)度的字符串比較。
ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2);
不區(qū)分大小寫的不帶長(zhǎng)度的字符串比較。
ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);
不區(qū)分大小寫的帶長(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是一個(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。
看到這里,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 是 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)存中。
下面介紹 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ì)象。
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 是 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):
從上面的描述,我們可以看出來(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。
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。
Nginx 為了處理帶有通配符的域名的匹配問(wèn)題,實(shí)現(xiàn)了 ngx_hash_wildcard_t 這樣的 hash 表。他可以支持兩種類型的帶有通配符的域名。一種是通配符在前的,例如:\*.abc.com
,也可以省略掉星號(hào),直接寫成.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é)果。
該函數(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 表的。
該函數(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ō)明。
組合類型 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é)果。
返回查詢的結(jié)果,未查到則返回 NULL。
大家看到在構(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ù)。
在定義一個(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)行查找?;蛘呤侨匀槐3秩齻€(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ì)象指針。
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ù)名自解釋了。
有關(guān)于這個(gè)數(shù)據(jù)結(jié)構(gòu)的使用,可以參考src/http/ngx_http.c
中的 ngx_http_server_names 函數(shù)。
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)行分配。
這個(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。
對(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ù)。
為了配合對(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 顧名思義,看起來(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;
好,我們?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 字段的。
我們來(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)存大小。
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 是 Nginx 中的雙向鏈表,在 Nginx 源碼目錄src/core
下面的ngx_queue.h|c
里面。它的原型如下:
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
不同于教科書(shū)中將鏈表節(jié)點(diǎn)的數(shù)據(jù)成員聲明在鏈表節(jié)點(diǎn)的結(jié)構(gòu)體中,ngx_queue_t 只是聲明了前向和后向指針。在使用的時(shí)候,我們首先需要定義一個(gè)哨兵節(jié)點(diǎn)(對(duì)于后續(xù)具體存放數(shù)據(jù)的節(jié)點(diǎn),我們稱之為數(shù)據(jù)節(jié)點(diǎn)),比如:
ngx_queue_t free;
接下來(lái)需要進(jìn)行初始化,通過(guò)宏 ngx_queue_init()來(lái)實(shí)現(xiàn):
ngx_queue_init(&free);
ngx_queue_init()的宏定義如下:
#define ngx_queue_init(q) \
(q)->prev = q; \
(q)->next = q
可見(jiàn)初始的時(shí)候哨兵節(jié)點(diǎn)的 prev 和 next 都指向自己,因此其實(shí)是一個(gè)空鏈表。ngx_queue_empty()可以用來(lái)判斷一個(gè)鏈表是否為空,其實(shí)現(xiàn)也很簡(jiǎn)單,就是:
#define ngx_queue_empty(h) \
(h == (h)->prev)
那么如何聲明一個(gè)具有數(shù)據(jù)元素的鏈表節(jié)點(diǎn)呢?只要在相應(yīng)的結(jié)構(gòu)體中加上一個(gè) ngx_queue_t 的成員就行了。比如 ngx_http_upstream_keepalive_module 中的 ngx_http_upstream_keepalive_cache_t:
typedef struct {
ngx_http_upstream_keepalive_srv_conf_t *conf;
ngx_queue_t queue;
ngx_connection_t *connection;
socklen_t socklen;
u_char sockaddr[NGX_SOCKADDRLEN];
} ngx_http_upstream_keepalive_cache_t;
對(duì)于每一個(gè)這樣的數(shù)據(jù)節(jié)點(diǎn),可以通過(guò) ngx_queue_insert_head()來(lái)添加到鏈表中,第一個(gè)參數(shù)是哨兵節(jié)點(diǎn),第二個(gè)參數(shù)是數(shù)據(jù)節(jié)點(diǎn),比如:
ngx_http_upstream_keepalive_cache_t cache;
ngx_queue_insert_head(&free, &cache.queue);
相應(yīng)的幾個(gè)宏定義如下:
#define ngx_queue_insert_head(h, x) \
(x)->next = (h)->next; \
(x)->next->prev = x; \
(x)->prev = h; \
(h)->next = x
#define ngx_queue_insert_after ngx_queue_insert_head
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; \
(x)->prev->next = x; \
(x)->next = h; \
(h)->prev = x
ngx_queue_insert_head() 和 ngx_queue_insert_after() 都是往頭部添加節(jié)點(diǎn),ngx_queue_insert_tail() 是往尾部添加節(jié)點(diǎn)。從代碼可以看出哨兵節(jié)點(diǎn)的 prev 指向鏈表的尾數(shù)據(jù)節(jié)點(diǎn),next 指向鏈表的頭數(shù)據(jù)節(jié)點(diǎn)。另外 ngx_queue_head() 和 ngx_queue_last() 這兩個(gè)宏分別可以得到頭節(jié)點(diǎn)和尾節(jié)點(diǎn)。
那假如現(xiàn)在有一個(gè) ngx_queue_t *q
指向的是鏈表中的數(shù)據(jù)節(jié)點(diǎn)的 queue 成員,如何得到ngx_http_upstream_keepalive_cache_t 的數(shù)據(jù)呢? Nginx 提供了 ngx_queue_data() 宏來(lái)得到ngx_http_upstream_keepalive_cache_t 的指針,例如:
ngx_http_upstream_keepalive_cache_t *cache = ngx_queue_data(q,
ngx_http_upstream_keepalive_cache_t,
queue);
也許您已經(jīng)可以猜到 ngx_queue_data 是通過(guò)地址相減來(lái)得到的:
#define ngx_queue_data(q, type, link) \
(type *) ((u_char *) q - offsetof(type, link))
另外 Nginx 也提供了 ngx_queue_remove()宏來(lái)從鏈表中刪除一個(gè)數(shù)據(jù)節(jié)點(diǎn),以及 ngx_queue_add() 用來(lái)將一個(gè)鏈表添加到另一個(gè)鏈表。
更多建議: