Ningx代碼研究.

概述

Nginx  ("engine x")是一個高性能的 HTTP 反向代理服務器,也是一個 IMAP/POP3/SMTP 代理服務器
 
Nginx是由IgorSysoev爲俄羅斯訪問量第二的Rambler.ru 站點開發的,它已經在該站點運行超過四年多了。
Igor將源代碼以類BSD許可證的形式發佈。自Nginx發佈四年來,Nginx已經由於它的穩定性、豐富的功能集
、示例配置文件和低系統資源的消耗而聞名了。目前國內各大門戶網站已經部署了Nginx
如新浪、網易、騰訊等;國內幾個重要的視頻分享網站也部署了Nginx,如六房間、酷6等。
新近發現Nginx技術在國內日趨火熱,愈來愈多的網站開始部署Nginx

- from http://wiki.nginx.org/NginxChsphp

咱們研究nginx的源代碼的動機是爲了完成分段反向代理項目的開發,因爲分段反向代理的需求要求對web server的併發性很強,而且是給予http協議的基礎上進行的, 因此咱們選擇了使用Nginx的模塊的形式進行開發。html

我 們發現目前學習nginx的例子不多,主要是emiller的模塊開發介紹這篇文章, 可是單獨研究這篇文章發現不少晦澀難懂的地方,而目前尚未其餘更好的文章來對這些地方作解釋, 有些東西必需要經過源代碼的研讀才能夠了解的更加清楚,因此咱們決定開始進行代碼研究計劃,以便於更好的完成開發任務前端

根據目前的情況,咱們決定使用最新的穩定版本進行研究,故而選擇 0.7.61 版做爲調研對象。node

http://sysoev.ru/nginx/nginx-0.7.61.tar.gzlinux

研究計劃

下面是王曉哲幫助制定的調研計劃nginx

  • 學習emiller的文章
  • 熟悉nginx的基本數據結構 1w
  • 瞭解nginx的core module 的結構和運行機制, 同時參考 event 和 os module 的實現 1w
  • 瞭解nginx的http core module 的結構和運行機制 1w
  • 學習handler 類型module的編寫 2d
  • 學習filter 類型module的編寫 2d
  • upstream類型module的編寫 1w

參與人員

徐景(rainx), 王曉哲(chaoslawful)web

研究文檔

學習emiller的文章

http://www.evanmiller.org/nginx-modules-guide.html算法

熟悉nginx的基本數據結構

nginx 代碼的目錄結構

解開nginx的代碼後,在src目錄下發現有以下的幾個目錄apache

core  event  http  mail  misc  os

其中 :後端

  • core : 該目錄存放core module的代碼,也是nginx服務的入口
  • http : http core module 的代碼,nginx做爲web/http proxy server運行時的核心模塊
  • mail : mail core module 的代碼,nginx做爲pop3/imap/smtp proxy server運行時的核心模塊 ( 不在咱們本次研究範圍內 )
  • event : nginx 自身對事件處理邏輯的封裝
  • os : nginx對各個平臺抽象邏輯的封裝
  • misc : nginx 的一些utils,定義了test和profiler的一些外圍模塊的邏輯

爲了方便了解整個結構, 咱們在src目錄下創建了一個子目錄,叫作demo,而後根據每一個章節內容的不一樣,分別在demo下創建子目錄,存放一些學習時使用的代碼,如咱們目前的章節是學習基本的數據類型,因此創建basic_types子目錄

rainx@rainx-laptop:~/land/nginx-0.7.61/src$ mkdir -p demo/basic_types/

nginx簡單的數據類型的表示

在 core/ngx_config.h 目錄裏面定義了基本的數據類型的映射,大部分都映射到c語言自身的數據類型

typedef intptr_t        ngx_int_t;
typedef uintptr_t       ngx_uint_t;
typedef intptr_t        ngx_flag_t;

其中 ngx_int_t, nginx_flag_t, 都映射爲 intptr_t; ngx_uint_t映射爲 uintptr_t

這兩個類型在/usr/include/stdint.h的定義爲:

/* Types for `void *' pointers.  */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedeflongint                intptr_t;
#  define __intptr_t_defined
# endif
typedefunsignedlongint       uintptr_t;
#else
# ifndef __intptr_t_defined
typedefint                     intptr_t;
#  define __intptr_t_defined
# endif
typedefunsignedint            uintptr_t;
#endif

因此基本的操做和整形/指針類型的操做相似

創建文件

#include<stdio.h>
#include"../../core/ngx_config.h"

int main()
{
    ngx_uint_t a
;
    ngx_int_t b
;
    a
=1000;
    b
=-1000;
    printf
("%d + %d = %d\n", a, b, a+b);
   
return0;
}

編譯測試

gcc -I ../../../objs/-I ../../os/unix/ basic_types_int.c -o basic_types_int
./basic_types_int
1000+-1000=0

nginx字符串的數據類型的表示

nginx對c語言的字符串類型進行了簡單的封裝, core/ngx_string.h/c 裏面包含這些封裝的內容

其中定義了 ngx_str_t ,ngx_keyval_t, ngx_variable_value_t

這幾個基礎類型的定義以下

typedefstruct{
    size_t      len
;
    u_char    
*data;
} ngx_str_t;


typedefstruct{
    ngx_str_t   key
;
    ngx_str_t   value
;
} ngx_keyval_t;


typedefstruct{
   
unsigned    len:28;

   
unsigned    valid:1;
   
unsigned    no_cacheable:1;
   
unsigned    not_found:1;
   
unsigned    escape:1;

    u_char    
*data;
} ngx_variable_value_t;

能夠看出 ngx_str_t 在原有的uchar* 的基礎上加入的字符串長度的附加信息, 初始化使用ngx_string宏進行,他的定義爲:

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

測試字符串的代碼 demo/basic_types/basic_types_str.c

#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_string.h"

volatile ngx_cycle_t  *ngx_cycle;

void
ngx_log_error_core
(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
           
constchar*fmt,...)
{
}

int main()
{
    u_char
* p = NULL;
    ngx_uint_t size
;
    ngx_str_t dst
;
    ngx_str_t mystr
= ngx_string("hello, world !");
    ngx_keyval_t pair
={ngx_string("url"), ngx_string("http://rainx.cn/index.php?test=1")};
   
int dst_len  =ngx_base64_encoded_length(mystr.len);
    printf
("source length is %d, destination length is %d\n", mystr.len, dst_len );
    p
= malloc( ngx_base64_encoded_length(mystr.len)+1);
    dst
.data = p;
    ngx_encode_base64
(&dst,&mystr);
    printf
("source str is %s\ndestination str is %s\n", mystr.data, dst.data);
    free
(p);
    size
= pair.value.len +2* ngx_escape_uri(NULL, pair.value.data, pair.value.len, NGX_ESCAPE_URI);
    p
= malloc (size *sizeof(u_char));
    ngx_escape_uri
(p, pair.value.data, pair.value.len, NGX_ESCAPE_URI);
    printf
("escaped %s is : %s (%d)\noriginal url size is %d\n", pair.key.data, p, size, pair.value.len);
    free
(p);
   
return0;

}

編譯運行

gcc  -c -O -pipe  -O -W -Wall-Wpointer-arith -Wno-unused-parameter -Wunused-function-Wunused-variable -Wunused-value -Werror-g -I ../../../objs/-I ../../os/unix/ basic_types_str.c -I../../core/-I../../event/-I../../os/-o basic_types_str.o
gcc
-o basic_types_str basic_types_str.o ../../../objs/src/core/ngx_{string,palloc}.o ../../../objs/src/os/unix/ngx_alloc.o -lcrypt -lpcre -lcrypto -lz
rainx@rainx
-laptop:~/land/nginx-0.7.61/src/demo/basic_types$ ./basic_types_str
source length
is14, destination length is20
source str
is hello, world !
destination str
is aGVsbG8sIHdvcmxkICE=
escaped url
is: http://rainx.cn/index.php%3ftest=1 (34)
original url size
is32

core/ngx_string.h/c 中同時也封裝了一批字符/字符串處理的函數和宏,他們的使用大多數狀況下和c標準庫中的相似,只是在內存分配相關的函數中有必定的區別。

好比 u_char *ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src); 除了源字符串駐外,還要傳入ngx_pool_t的指針做爲參數,使用nginx本身的內存分配方式進行內存的分配。

除了標準的字符串操做外, nginx還實現了例如:

// base64 編碼/解碼函數和宏

#define ngx_base64_encoded_length(len)  (((len +2)/3)*4)
#define ngx_base64_decoded_length(len)  (((len +3)/4)*3)

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);

//utf-8 編碼/解碼相關函數

uint32_t ngx_utf8_decode
(u_char **p, size_t n);
size_t ngx_utf8_length
(u_char *p, size_t n);
u_char
*ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len);

// urlencode和html實體的編碼解碼
uintptr_t ngx_escape_uri
(u_char *dst, u_char *src, size_t size,
    ngx_uint_t type
);
void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);
uintptr_t ngx_escape_html
(u_char *dst, u_char *src, size_t size);

等對於http服務有幫助的宏和函數

內存分配相關

系統功能封裝

內存相關的操做主要在 os/unix/ngx_alloc.{h,c} 和 core/ngx_palloc.{h,c} 下

其中 os/unix/ngx_alloc.{h,c} 封裝了最基本的內存分配函數,是對c原有的malloc/free/memalign 等原有的函數的封裝,對應的函數爲:

  • ngx_alloc 使用malloc分配內存空間
  • ngx_calloc 使用malloc分配內存空間,而且將空間內容初始化爲0
  • ngx_memalign 返回基於一個指定的alignment大小的數值爲對齊基數的空間
  • ngx_free 對內存的釋放操做

ngx的內存池

爲 了方便系統模塊對內存的使用,方便內存的管理,nginx本身實現了進程池的機制來進行內存的分配和釋放, 首先nginx會在特定的生命週期幫你統一創建內存池,當須要進行內存分配的時候統一經過內存池中的內存進行分配,最後nginx會在適當的時候釋放內存 池的資源,開發者只要在須要的時候對內存進行申請便可,不用過多考慮內存的釋放等問題,大大提升了開發的效率。

內存池的主要結構爲:

//ngx_palloc.h
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_core.h
typedefstruct ngx_pool_s        ngx_pool_t;
typedefstruct ngx_chain_s       ngx_chain_t;

下面是我簡單畫的一個圖來描述這個結構:

link : http://www.flickr.com/photos/rainx/3765612584/sizes/o/

下面解釋一下主要的幾個操做:

// 建立內存池
ngx_pool_t
*ngx_create_pool(size_t size, ngx_log_t *log);

大體的過程是建立使用 ngx_alloc 分配一個size大小的空間, 而後將 ngx_pool_t* 指向這個空間, 而且初始化裏面的成員, 其中

p->d.last=(u_char *) p +sizeof(ngx_pool_t);// 初始指向 ngx_pool_t 結構體後面
p
->d.end=(u_char *) p + size;// 整個結構的結尾後面
p
->max =(size < NGX_MAX_ALLOC_FROM_POOL)? size : NGX_MAX_ALLOC_FROM_POOL;// 最大不超過 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小

其餘大都設置爲null或者0

// 銷燬內存池
void ngx_destroy_pool(ngx_pool_t *pool);

遍歷鏈表,全部釋放內存,其中若是註冊了clenup(也是一個鏈表結構), 會一次調用clenup 的 handler 進行清理。

// 重置內存池
void ngx_reset_pool(ngx_pool_t *pool);

釋放全部large段內存, 而且將d->last指針從新指向 ngx_pool_t 結構以後(和建立時同樣)

// 從內存池裏分配內存
void*ngx_palloc(ngx_pool_t *pool, size_t size);
void*ngx_pnalloc(ngx_pool_t *pool, size_t size);
void*ngx_pcalloc(ngx_pool_t *pool, size_t size);
void*ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

ngx_palloc 的過程通常爲,首先判斷待分配的內存是否大於 pool->max的大小,若是大於則使用 ngx_palloc_large 在 large 鏈表裏分配一段內存並返回, 若是小於測嘗試從鏈表的 pool->current 開始遍歷鏈表,嘗試找出一個能夠分配的內存,當鏈表裏的任何一個節點都沒法分配內存的時候,就調用 ngx_palloc_block 生成鏈表裏一個新的節點, 並在新的節點裏分配內存並返回, 同時, 還會將pool->current 指針指向新的位置(從鏈表裏面pool->d.failed小於等於4的節點裏找出) ,其餘幾個函數也基本上爲 ngx_palloc 的變種,實現方式大同小異

// 釋放指定的內存
ngx_int_t ngx_pfree
(ngx_pool_t *pool,void*p);

這個操做只有在內存在large鏈表裏註冊的內存在會被真正釋放,若是分配的是普通的內存,則會在destory_pool的時候統一釋放.

// 註冊cleanup回叫函數(結構體)
ngx_pool_cleanup_t
*ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);

這個過程和咱們以前常用的有些區別, 他首先在傳入的內存池中分配這個結構的空間(包括data段), 而後將爲結構體分配的空間返回, 經過操做返回的ngx_pool_cleanup_t結構來添加回叫的實現。 (這個過程在nginx裏面出現的比較多,也就是 xxxx_add 操做一般不是實際的添加操做,而是分配空間並返回一個指針,後續咱們還要經過操做指針指向的空間來實現所謂的add)

下面是內存操做的一些例子 demo/basic_types/mem_op.c

#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"

volatile ngx_cycle_t  *ngx_cycle;

void
ngx_log_error_core
(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
           
constchar*fmt,...)
{
}

typedefstruct example_s {
   
int a;
   
char* b;
} example_t;

int main()
{
    ngx_pool_t
*pool;
    example_t
* exp;
   
char* s;

    pool
= ngx_create_pool(5000, NULL);
    printf
("available pool regular pool free size is %d now\n",(ngx_uint_t)(pool->d.end- pool->d.last));
    exp
= ngx_palloc(pool,sizeof(example_t));
    s
= ngx_palloc(pool,sizeof("hello,world"));
    printf
("available pool regular pool free size is %d now\n",(ngx_uint_t)(pool->d.end- pool->d.last));
    exp
->a =1;
    exp
->b = s;
    strcpy
(s,"hello,world");
    printf
("pool max is %d\n", pool->max);
    printf
("exp->a is %d, exp->b is %s\n", exp->a, exp->b);
    ngx_destroy_pool
(pool);
   
return0;
}

編譯運行結果

gcc  -c -O -pipe  -O -W -Wall-Wpointer-arith -Wno-unused-parameter -Wunused-function-Wunused-variable -Wunused-value -Werror-g -I ../../../objs/-I ../../os/unix/ mem_op.c -I../../core/-I../../event/-I../../os/-o mem_op.o
 gcc
-o mem_op mem_op.o ../../../objs/src/core/ngx_{string,palloc}.o ../../../objs/src/os/unix/ngx_alloc.o -lcrypt -lpcre -lcrypto -lz
rainx@rainx
-laptop:~/land/nginx-0.7.61/src/demo/basic_types$ ./mem_op
available pool regular pool free size
is4960 now
available pool regular pool free size
is4940 now
pool max
is4960
exp
->a is1, exp->b is hello,world

ngx的基本容器

ngx_array

對應的文件爲 core/ngx_array.{c|h}

ngx_array是nginx內部封裝的使用 ngx_pool_t對內存池進行分配的數組容器,其中的數據是在一整片內存區中連續存放的。更新數組時只能在尾部壓入1個或多個元素。

數組的實現結構爲

struct ngx_array_s {
   
void        *elts;
    ngx_uint_t   nelts
;
    size_t       size
;
    ngx_uint_t   nalloc
;
    ngx_pool_t  
*pool;
};

其中 elts 爲具體的數據區域的指針, nelts 爲數組實際包含的元素數量, size爲數組單個元素的大小, nalloc爲數組容器預先(或者從新)分配的內存大小, pool 爲分配基於的內存池

經常使用的操做有

// 建立一個新的數組容器
ngx_array_t
*ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
// 銷燬數組容器
void ngx_array_destroy(ngx_array_t *a);
// 將新的元素加入數組容器
void*ngx_array_push(ngx_array_t *a);
void*ngx_array_push_n(ngx_array_t *a, ngx_uint_t n); //返回n個元素的指針

這裏須要注意的是,和以前的ngx_pool_cleanup_add同樣, ngx_array_push只是進行內存分配的操做,咱們須要對返回的指針指向的地址進行賦值等操做來實現實際數組值的添加。

具體一點的push操做的實現爲,

  1. 首先判斷 nalloc是否和nelts相等,即數組預先分配的空間已經滿了,若是沒滿則計算地址直接返回指針
  2. 若是已經滿了則先判斷是否咱們的pool中的當前鏈表節點還有剩餘的空間,若是有則直接在當前的pool鏈表節點中分配內存,並返回
  3. 若是當前鏈表節點沒有足夠的空間則使用ngx_palloc從新分配一個2倍於以前數組空間大小的數組,而後將數據轉移過來,並返回新地址的指針

下面是一個array的例子:

demo/basic_types/array_and_hash.c

#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"

volatile ngx_cycle_t  *ngx_cycle;

void
ngx_log_error_core
(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
           
constchar*fmt,...)
{
}

int main()
{
   ngx_pool_t
* pool;
   ngx_array_t
* arr;
   
int n;
   
int* ele;
   pool
= ngx_create_pool(4000, NULL);
   arr
= ngx_array_create(pool,10,sizeof(ngx_uint_t));
   
for(n=0; n <5; n++){
      ele
=(int*) ngx_array_push(arr);
     
*ele = n;
      printf
("new element %d added\n", n);
   
}

   printf
("arr->nelts is %d, arr->nalloc = %d\n", arr->nelts, arr->nalloc);

   
for(n=5; n <15; n++){
      ele
=(int*) ngx_array_push(arr);
     
*ele = n;
      printf
("new element %d added\n", n);
   
}
   printf
("arr->nelts is %d, arr->nalloc = %d\n", arr->nelts, arr->nalloc);

   ngx_array_destroy
(arr);
   ngx_destroy_pool
(pool);
   
return0;
}

編譯運行

gcc  -c -O -pipe  -O -W -Wall-Wpointer-arith -Wno-unused-parameter -Wunused-function-Wunused-variable -Wunused-value -Werror-g -I ../../../objs/-I ../../os/unix array_and_hash.c -I../../core/-I../../event/-I../../os/-o array_and_hash.o
gcc
-o array_and_hash array_and_hash.o ../../../objs/src/core/ngx_{string,palloc,array}.o ../../../objs/src/os/unix/ngx_alloc.o -lcrypt -lpcre -lcrypto -lz
rainx@rainx
-laptop:~/land/nginx-0.7.61/src/demo/basic_types$ ./array_and_hash
new element 0 added
new element 1 added
new element 2 added
new element 3 added
new element 4 added
arr
->nelts is5, arr->nalloc =10
new element 5 added
new element 6 added
new element 7 added
new element 8 added
new element 9 added
new element 10 added
new element 11 added
new element 12 added
new element 13 added
new element 14 added
arr
->nelts is15, arr->nalloc =15

ngx_queue

ngx_queue.{c,h} 實現了一個隊列的操做邏輯,隊列的基本結構爲一個雙向隊列

基礎的數據結構爲

typedefstruct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  
*prev;
    ngx_queue_t  
*next;
};

注 意nginx的隊列操做和結構只進行指針的操做,不負責節點內容空間的分配和保存,因此在定義本身的隊列節點的時候,須要本身定義數據結構以及分配空間, 幷包含一個ngx_queue_t類型的成員, 須要得到原始的數據節點的時候須要使用ngx_queue_data宏

#define ngx_queue_data(q, type, link)                                         \
   
(type *)((u_char *) q - offsetof(type, link))

另外,整個queue結構中包含一個 sentinel(哨兵) 節點, 他指向隊列的頭和尾

下面是一個queue操做的例子

#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_queue.h"

volatile ngx_cycle_t  *ngx_cycle;
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,constchar*fmt,...){}

// 用雅虎的成員列表做爲一個簡單的例子
typedefstruct yahoo_s {
    ngx_queue_t   queue
;
} yahoo_t;

typedefstruct yahoo_guy_s {
    ngx_uint_t    id
;
    u_char
*       name;
    ngx_queue_t   queue
;
} yahoo_guy_t;

// 排序使用的比較函數, 按照id的大小排序,id大放到到前面
ngx_int_t yahoo_no_cmp
(const ngx_queue_t* p,const ngx_queue_t* n)
{
    yahoo_guy_t
*pre,*next;
    pre  
=(yahoo_guy_t*) ngx_queue_data(p, yahoo_guy_t, queue);
   
next=(yahoo_guy_t*) ngx_queue_data(n, yahoo_guy_t, queue);
   
return((pre->id >next->id)?1:0);
}

int main()
{
    ngx_pool_t
*     pool;
    yahoo_guy_t
*    guy;
    ngx_queue_t
*    q;
    yahoo_t
*        yahoo;
    pool            
= ngx_create_pool(1024*10, NULL);//初始化內存池
   
int             i;
   
// 構建隊列
   
const ngx_str_t   names[]={
        ngx_string
("rainx"), ngx_string("xiaozhe"), ngx_string("zhoujian")
   
};
   
constint       ids[]   ={4611,8322,6111};

    yahoo
= ngx_palloc(pool,sizeof(yahoo_t));
    ngx_queue_init
(&yahoo->queue);//初始化queue

   
for(i =0; i <3; i++)
   
{
      guy
=(yahoo_guy_t*) ngx_palloc(pool,sizeof(yahoo_guy_t));
      guy
->id   = ids[i];
     
//guy->name = (char*) ngx_palloc(pool, (size_t) (strlen(names[i]) + 1) );
      guy
->name =(u_char*) ngx_pstrdup(pool,(ngx_str_t*)&(names[i]));

      ngx_queue_init
(&guy->queue);
     
// 從頭部進入隊列
      ngx_queue_insert_head
(&yahoo->queue,&guy->queue);
   
}

   
// 從尾部遍歷輸出
   
for(q = ngx_queue_last(&yahoo->queue);
        q
!= ngx_queue_sentinel(&yahoo->queue);
        q
= ngx_queue_prev(q)){

        guy
= ngx_queue_data(q, yahoo_guy_t, queue);
        printf
("No. %d guy in yahoo is %s \n", guy->id, guy->name);
   
}

   
// 排序從頭部輸出
    ngx_queue_sort
(&yahoo->queue, yahoo_no_cmp);
    printf
("sorting....\n");
   
for(q = ngx_queue_prev(&yahoo->queue);
        q
!= ngx_queue_sentinel(&yahoo->queue);
        q
= ngx_queue_last(q)){

        guy
= ngx_queue_data(q, yahoo_guy_t, queue);
        printf
("No. %d guy in yahoo is %s \n", guy->id, guy->name);
   
}

    ngx_destroy_pool
(pool);
   
return0;
}

運行結果爲

rainx@rainx-laptop:~/land/nginxsrp/src/demo/basic_types$ ./queue_op 
No.4611 guy in yahoo is rainx
No.8322 guy in yahoo is xiaozhe
No.6111 guy in yahoo is zhoujian
sorting
....
No.8322 guy in yahoo is xiaozhe
No.6111 guy in yahoo is zhoujian
No.4611 guy in yahoo is rainx

ngx_hash

ngx_hash.{c|h} 實現了nginx裏面比較重要的一個hash結構, 這個在模塊配置解析裏常常被用到。該 hash 結構是隻讀的,即僅在初始建立時能夠給出保存在其中的 key-val 對,其後就只能查詢而不能進行增刪改操做了。

下面是簡單 hash 結構的內存佈局:

link: http://www.flickr.com/photos/chaoslawful/3780810336/sizes/o/

雖然代碼理解起來比較混亂,可是使用仍是比較簡單的,經常使用的有建立 hash 和在 hash 中進行查找兩個操做,對於建立hash的操做,過程通常爲:

  1. 構造一個 ngx_hash_key_t 爲成員的數組, 包含 key, value 和 使用key計算出的一個hash值
  2. 構建一個 ngx_hash_init_t結構體的變量, 其中包含了ngx_hash_t 的成員, 爲hash的結構體, 還包括一些其餘初始設置,如bucket的大小,內存池等
  3. 調用 ngx_hash_init 傳入 ngx_hash_init_t 結構, ngx_hash_key_t 的數組,和數組的長度, 進行初始化,這樣 ngx_hash_init_t的hash成員就是咱們要的hash結構

查找的過程很簡單

  1. 計算 key 的hash值
  2. 使用 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,constchar*fmt,...){}


static ngx_str_t names[]={ngx_string("rainx"),
                            ngx_string
("xiaozhe"),
                            ngx_string
("zhoujian")};
staticchar* 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){
       
return1;
   
}

   
// 查找
    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);

   
return0;
}

運行結果

rainx@rainx-laptop:~/land/nginxsrp/src/demo/basic_types$ ./hash_op 
caculated key
is3654358412
key
: rainx , key_hash:108275556
key
: xiaozhe , key_hash:2225329080
key
: zhoujian , key_hash:3269715264
rainx key
is108275556
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的源代碼研究資料,頗有參考意義,因此大量節省了咱們的工做,我以爲對於本章的進行比較有用的是,下面這幾個文章

  1. nginx源代碼分析 http://hi.baidu.com/langwan/blog/item/6b18ef24cd859e064c088d28.html
  2. nginx 緩衝區構造 http://hi.baidu.com/langwan/blog/item/822b758d5d1d9a1ab31bbaf8.html
  3. 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 這裏定義:

typedefenum{
    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數組的下標轉移到下一個成員。

關於subrequest

在 ngx_http_core_module 裏面,咱們能夠看到一些subrequest的函數,根據evan miller 的文章,咱們知道 subrequest是在主請求的基礎上發起的子請求,subrequest返回的內容會被附加到自請求上面,他的實現方法就是調用 ngx_http_subrequest 函數,subrequest函數的圓形是

ngx_int_t
ngx_http_subrequest
(ngx_http_request_t *r,
    ngx_str_t
*uri, ngx_str_t *args, ngx_http_request_t **psr,
    ngx_http_post_subrequest_t
*ps, ngx_uint_t flags)

在裏面,在這個函數裏,他會1)建立新的request變量,根據 主request的值去填充這個變量2)註冊新的變量的write event handler

    sr->read_event_handler = ngx_http_request_empty_handler;
    sr
->write_event_handler = ngx_http_handler;

3) 而且把subrequet 的request 註冊到主request的 posted_requests 變量裏

    for(p =&r->main->posted_requests;*p; p =&(*p)->next){/* void */}

也就是說,一但調用了 ngx_http_subrequest 只後,subrequest已經已經註冊到了nginx的事件循環中,和主循環並行進行處理,因此根據evan miller的文章裏咱們也能夠看到,subrequest的串行處理是比較困難的。

關於 internal redirect

nginx 的 internal redirect 有兩種方式, 一個是調用 ngx_http_internal_redirect 命令, 一個是使用 X-Accel-Redirect 頭,其中X-Accel-Redirect 頭是交由upstream模塊進行的, 在ngx_http_upstream_process_headers函數中,咱們能夠看到

   if(u->headers_in.x_accel_redirect
       
&&!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT))
   
{
       
.....
       
.....
        ngx_http_internal_redirect
(r, uri,&args);
       
return NGX_DONE;
   
}

也就是說,本質上,這兩種方法都是經過 ngx_http_internal_redirect 的函數實現的,而ngx_http_internal_redirect 函數又作了些什麼呢?

    r->internal=1;

    ngx_http_handler
(r);

   
return NGX_DONE;

能夠看到,其實它只是簡單的修改request結構體,並經過 ngx_http_handler 從新進行http處理流程而已。

關 於internal redirect 和 subrequest,他們有相似的地方,都是在最後調用 ngx_http_handler 從新開始對request的處理,可是不一樣的地方我想主要是 subrequest會新創建一個request結構,原來的request處理並不會結束,而 internal redirect會結束當前的請求處理,使用嗯但前的request結構體來發起請求。

關於upstream

upstream 是nginx裏面很是重要的一種處理模式,nginx的不少模塊都是使用他來完成的, upstream和普通的handler有很大的不一樣,好比nginx的location handler 和phase handler, 他們的處理模式很是相似apache的handler,就是獲取後同步的進行處理,可是對於有些場合,好比proxy或者fastcgi, nginx進程須要和上游的服務(http,或者php-cgi)進行交互,而且將結果返回給客戶端,在交互的過程當中,因爲後端服務的響應時間各異,若是 後端服務的響應時間過長,nginx等待後端服務響應的過程一之同步阻塞在這裏是不現實的,因此nginx採用了異步的方案,每當上游服務器有響應的時候 才進行處理,並將處理結果返回給前端,這樣nginx在處理上游請求的時候,能夠同時註冊多個io時間,使用epoll_wait(linux下默認) 也就是nginx的主事件循環進行處理,大大提升了nginx的併發能裏,每一個進程能夠處理多個鏈接 ,不會由於一個上游鏈接阻塞整個nginx進程, 固然這也有依賴於你程序的實現方式。

關於upstream的使用方式, evan miller的文章裏已經說的很清楚了,就是能夠在location handler裏(或者其它插入點中) 註冊upstream的一些callback

    u->conf =&plcf->upstream;

/* attach the callback functions */
    u
->create_request = ngx_http_proxy_create_request;
    u
->reinit_request = ngx_http_proxy_reinit_request;
    u
->process_header = ngx_http_proxy_process_status_line;
    u
->abort_request = ngx_http_proxy_abort_request;
    u
->finalize_request = ngx_http_proxy_finalize_request;

    r
->upstream = u;

並 且最後在調用 ngx_http_read_client_request_body 中傳入 ngx_http_upstream_init 做爲callback function 參數初始化 upstream流程, 在 ngx_http_upsteam_init 裏面,會調用 ngx_http_upstream_connect創建鏈接,並註冊讀寫的event handler

        /* event handler 最終會根據讀/或者寫的請求調用 write_event_handler和 read_event_handler ,也就是  ngx_http_upstream_send_request_handler和
        ngx_http_upstream_process_header */

    c
->write->handler = ngx_http_upstream_handler;
    c
->read->handler = ngx_http_upstream_handler;

    u
->write_event_handler = ngx_http_upstream_send_request_handler;
    u
->read_event_handler = ngx_http_upstream_process_header;

這 裏須要注意的是,在處理上游請求的時候,因爲是異步的事件,因此每次上游服務器在調用callback的時候並不必定返回的是所有數 據,process_header 函數必須根據判斷每次返回的數據包是否完成,若是沒有處理完成則返回 NGX_AGAIN,來指示nginx須要從新進入process_header函數進行處理,並知道處理完成的時候返回NGX_OK 來指示nginx完成處理。 具體的例子能夠參考 fastcgi 模塊的ngx_http_fastcgi_process_header函數

另外, nginx能夠針對上游服務的響應body進行處理, 經過註冊 u->pipe->input_filter 這個回叫來實現,一樣能夠參考fastcgi模塊,下面是例子

    /* 這裏註冊對於響應體的處理 */
    u
->pipe->input_filter = ngx_http_fastcgi_input_filter;
    u
->pipe->input_ctx = r;

一般在處理的過程當中須要本身建立狀態機來處理請求的不一樣狀態,對於一些請求相關的信息,能夠本身建立結構體,經過

#define ngx_http_get_module_ctx(r,module)  (r)->ctx[module.ctx_index]
#define ngx_http_set_ctx(r, c,module)      r->ctx[module.ctx_index]= c;

這兩個宏進行讀取和存儲。

相關文章
相關標籤/搜索