【Nginx源碼研究】Nginx中FastCGI淺析

運營研發團隊 施洪寶php

一. FastCGI協議簡介

1.1 簡介

FastCGI(Fast Common Gateway Interface, 快速通用網關接口)是一種通訊協議。能夠經過Unix Domain Socket, Named Pipe, TCP等方式按照FastCGI協議通訊。html

clipboard.png

圖 1.1 FastCGI簡介linux

1.2 數據包格式

FastCGI數據包兩部分, 頭部(header), 包體(body), 每一個數據包都必須包含header, body能夠沒有。header爲8個字節, body必須爲8的整數倍, 不是的話須要填充。nginx

1.2.1 頭部

typedef struct {
    unsigned char version;            // 版本號
    unsigned char type;               // 數據包類型
    unsigned char requestIdB1;        // 記錄id高8位
    unsigned char requestIdB0;        // 記錄id低8位
    unsigned char contentLengthB1;    // 記錄內容長度高8位(body長度高8位)
    unsigned char contentLengthB0;    // 記錄內容長度低8位(body長度低8位)
    unsigned char paddingLength;      // 補齊位長度(body補齊長度)
    unsigned char reserved;           // 補齊位
}Header;

clipboard.png

圖 1.2 FastCGI協議數據包頭部字段說明web

type的取值docker

#define FCGI_BEGIN_REQUEST       1                     //(web->fastcgi)請求開始數據包
#define FCGI_ABORT_REQUEST       2                     //(web->fastcgi)終止請求
#define FCGI_END_REQUEST         3                     //(fastcgi->web)請求結束
#define FCGI_PARAMS              4                     //(web->fastcgi)傳遞參數
#define FCGI_STDIN               5                     //(web->fastcgi)數據流傳輸數據
#define FCGI_STDOUT              6                     //(fastcgi->web)數據流傳輸數據
#define FCGI_STDERR              7                     //(fastcgi->web)數據流傳輸
#define FCGI_DATA                8                     //(web->fastcgi)數據流傳輸
#define FCGI_GET_VALUES          9                     //(web->fastcgi)查詢fastcgi服務器性能參數
#define FCGI_GET_VALUES_RESULT  10                     //(fastcgi->web)fastcgi性能參數查詢返回
#define FCGI_UNKNOWN_TYPE       11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

1.2.2 params類型數據包

clipboard.png

圖 1.3 Params數據包segmentfault

說明:緩存

  • params數據包以key, value格式發送, 具體格式爲(keyLen, valLen, key, val)
  • key或者val長度大於127時,會用4個字節存儲長度,不然用一個字節

1.2.3 數據流類型數據包(stdin, stdout, stderr, data)

clipboard.png

圖 1.4 數據流類型數據包服務器

1.3 通訊流程示例

clipboard.png

圖 1.5 FastCGI簡單通訊流程curl

說明:

  • begin request 表明請求開始, end request 表明請求結束。
  • 除begin request, end request類型數據包外, 其餘類型數據包在發送完成後,須要發送一個只有頭部,包體長度爲0, 也就是沒有包體的數據包,表明這種類型的數據包發送結束。上述params, stdin, stdout, stderr 都須要發送結束包。

1.4 參考

二. Nginx FastCGI

nginx發送的緩衝區數據格式以下:

clipboard.png

圖 2.1 FastCGI數據包整體結構圖

說明:

  • 本部分主要依據ngx_http_fastcgi_create_request函數, 該函數會構造緩存區,並向其中寫入上圖所示內容,在該函數執行完後,會根據實際狀況選擇是否繼續向緩衝區寫入數據,後續狀況在此不作討論。
  • ngx_http_fastcgi_create_request 函數所需的變量, 在進入該函數以前認爲已經初始化完成。

2.1 示例

2.1.1 upstream 發送緩衝區

//r.upstream.request_bufs.buf.pos
//begin request header
\001\001\000\001\000\b\000\000
//begin request body
\000\001\000\000\000\000\000\000
//begin params header
\001\004\000\001\002\b\000\000
//begin parmas body
\n\004PRODUCTIONtrue
\017\061SCRIPT_FILENAME/home/codes/nginx-1.6.2/output/html8100/index.php
\f\000QUERY_STRING
\016\003REQUEST_METHODGET
\f\000CONTENT_TYPE
\016\000CONTENT_LENGTH
\v\nSCRIPT_NAME/index.php
\v\001REQUEST_URI/
\f\nDOCUMENT_URI/index.php
\r'DOCUMENT_ROOT/home/codes/nginx-1.6.2/output/html8100
\017\bSERVER_PROTOCOLHTTP/1.1
\021\aGATEWAY_INTERFACECGI/1.1
\017\vSERVER_SOFTWAREnginx/1.6.2
\v\tREMOTE_ADDR127.0.0.1
\v\005REMOTE_PORT42146
\v\tSERVER_ADDR127.0.0.1
\v\004SERVER_PORT8100
\v\000SERVER_NAME
\017\003REDIRECT_STATUS200
\017\vHTTP_USER_AGENTcurl/7.29.0
\t\016HTTP_HOSTlocalhost:8100
\v\003HTTP_ACCEPT*/*
//end params header
\001\004\000\001\000\000\000\000
//begin stdin header, 包體長度爲0, 能夠認爲stdin類型數據包發送結束
\001\005\000\001\000\000\000\000

2.1.2 Params 數據包參數整理

clipboard.png

2.2 源碼解析

2.2.1 基礎

2.2.1.1 le.ip結構圖

clipboard.png

clipboard.png

圖 2.2 le.ip 結構圖

2.2.1.2 e.ip結構圖

clipboard.png

clipboard.png

圖2.3 e.ip結構圖

2.2.2 ngx_http_fastcgi_create_request

  • 該函數主要依據圖2.1, 將所需數據寫入到ngx_http_request_t對應的ngx_http_upstream_t的緩衝區中。
  • 寫入key, val時, 經過調用相應函數實現, 該函數是與對應的key, val放置在一塊兒的, 如圖2.3所示。
//params數據包寫入核心代碼
while (*(uintptr_t *) le.ip) {
    lcode = *(ngx_http_script_len_code_pt *) le.ip;
    key_len = (u_char) lcode(&le); //獲取key的長度
    lcode = *(ngx_http_script_len_code_pt *) le.ip;
    skip_empty = lcode(&le);      //查看空時是否跳過
    for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) {
        lcode = *(ngx_http_script_len_code_pt *) le.ip;
    }
    le.ip += sizeof(uintptr_t);        //當前key:value結束, le.ip後移1位
    if (skip_empty && val_len == 0) { //value爲空, 而且設置空時跳過該key
        e.skip = 1;
        while (*(uintptr_t *) e.ip) {
            code = *(ngx_http_script_code_pt *) e.ip;
            code((ngx_http_script_engine_t *) &e);
        }
        e.ip += sizeof(uintptr_t);
        e.skip = 0;
        continue;
    }
    *e.pos++ = (u_char) key_len; //寫入key len 
    if (val_len > 127) {         //寫入value len
        *e.pos++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80);
        *e.pos++ = (u_char) ((val_len >> 16) & 0xff);
        *e.pos++ = (u_char) ((val_len >> 8) & 0xff);
        *e.pos++ = (u_char) (val_len & 0xff);
    } else {
        *e.pos++ = (u_char) val_len;
    }
    while (*(uintptr_t *) e.ip) {
        code = *(ngx_http_script_code_pt *) e.ip;
        code((ngx_http_script_engine_t *) &e);  //調用code存儲的處理函數, 負責將key, value內容寫入緩存, 並將e.ip後移
    }
    e.ip += sizeof(uintptr_t);                  //當前Key:Value結束, 跳過NULL, e.ip後移一位
    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "fastcgi param: \"%*s: %*s\"",
                   key_len, e.pos - (key_len + val_len),
                   val_len, e.pos - val_len);
}

三. 抓包分析

3.1 TCP三次握手

14:50:02.836252 IP bogon.46288 > localhost.cslistener: Flags [S], seq 304127093, win 29200, options [mss 1460,sackOK,TS val 105743206 ecr 0,nop,wscale 7], length 0
14:50:02.874743 IP localhost.cslistener > bogon.46288: Flags [S.], seq 15154, ack 304127094, win 32768, options [mss 1460], length 0
14:50:02.874804 IP bogon.46288 > localhost.cslistener: Flags [.], ack 15155, win 29200, length 0

代碼1.1 tcpdump三次握手

clipboard.png

圖1.1 三次握手連接過程

Client發送請求包seq 304127093,Server返回確認數據包ack=seq(client)+1,同時返回Server本身的seq 15154,Client收到後發送確認包ack=seq(server)+1,創建連接。
Client和Server都有本身的seq,且互不干涉,後續發送的序列號以此爲基準。

3.2 發送數據

clipboard.png

圖2.2 請求數據包圖2.1 Client向Server發送請求數據包

  • Mac幀頭部(14字節)
  • IP頭部(20字節)
  • TCP頭部(20字節)
  • FastCGI數據包(begin request(8+ 8 = 16) + params(8 + 507 + 5 = 520) + end params(8+ 0 = 8) + stdin(8 + 0 = 8) = 552)

Params 數據包參數整理:

clipboard.png

說明

  • stdin類型數據包長度大於32k時, nginx fastcgi會進行分包, 此時會發送多個stdin類型數據包, 發送完成後再發送stdin結束包(只有包頭,沒有包體的數據包)。
  • wireshark或者tcpdump抓包時, 可能會出現某個數據包長度大於MTU, 主要是因爲主機開啓TSO功能致使, 具體能夠參考http://wsfdl.com/%E8%B8%A9%E5...
  • TSO進行的是TCP分段, 不是IP分片
  • 若是抓到的包, IP頭部, identified field is 0, 代表tcp不需分段, don't fragment 置位1, 具體參考http://www.linuxsa.org.au/pip...
  • TCP頭部可選項含義參考https://www.jianshu.com/p/39b...

3.3 響應包

14:50:02.913289 IP localhost.cslistener > bogon.46288: Flags [P.], seq 15155:15395, ack 304127646, win 32216, length 240
    0x0000:  0800 2701 5190 5254 0012 3500 0800 4500  ..'.Q.RT..5...E.
    0x0010:  0118 02bf 0000 ff06 2fb6 0a60 7207 0a00  ......../..`r...
    0x0020:  0204 2328 b4d0 0000 3b33 1220 9e9e 5018  ..#(....;3....P.
    0x0030:  7dd8 df40 0000 0106 0001 00d8 0000 5365  }..@..........Se
    0x0040:  742d 436f 6f6b 6965 3a20 5048 5053 4553  t-Cookie:.PHPSES
    0x0050:  5349 443d 6268 3174 3772 6e61 3233 716c  SID=bh1t7rna23ql
    0x0060:  6d63 6235 6d6a 686d 3967 756f 7631 3b20  mcb5mjhm9guov1;.
    0x0070:  7061 7468 3d2f 0d0a 4578 7069 7265 733a  path=/..Expires:
    0x0080:  2054 6875 2c20 3139 204e 6f76 2031 3938  .Thu,.19.Nov.198
    0x0090:  3120 3038 3a35 323a 3030 2047 4d54 0d0a  1.08:52:00.GMT..
    0x00a0:  4361 6368 652d 436f 6e74 726f 6c3a 206e  Cache-Control:.n
    0x00b0:  6f2d 7374 6f72 652c 206e 6f2d 6361 6368  o-store,.no-cach
    0x00c0:  652c 206d 7573 742d 7265 7661 6c69 6461  e,.must-revalida
    0x00d0:  7465 0d0a 5072 6167 6d61 3a20 6e6f 2d63  te..Pragma:.no-c
    0x00e0:  6163 6865 0d0a 436f 6e74 656e 742d 7479  ache..Content-ty
    0x00f0:  7065 3a20 7465 7874 2f68 746d 6c3b 2063  pe:.text/html;.c
    0x0100:  6861 7273 6574 3d55 5446 2d38 0d0a 0d0a  harset=UTF-8....
    0x0110:  646f 636b 6572 0103 0001 0008 0000 0000  docker..........
    0x0120:  0000 0064 223a                           ...d":

代碼2.2 Server向Client發送響應數據包

  • Mac幀頭部(14字節)
  • IP頭部(20字節)
  • TCP頭部(20字節)
  • FastCGI數據包(begin stdout(8+ 216 = 224) + end request(8+ 8 = 16) = 240)

3.4 Client發送接收Server數據的確認包

14:50:02.913365 IP bogon.46288 > localhost.cslistener: Flags [.], ack 15395, win 30016, length 0

clipboard.png

圖2.2 數據發送

3.5 斷開連接

14:50:02.913629 IP bogon.46288 > localhost.cslistener: Flags [F.], seq 304127646, ack 15395, win 30016, length 0
14:50:02.913767 IP localhost.cslistener > bogon.46288: Flags [.], ack 304127647, win 32215, length 0
14:50:02.951270 IP localhost.cslistener > bogon.46288: Flags [F.], seq 15395, ack 304127647, win 32215, length 0
14:50:02.951452 IP bogon.46288 > localhost.cslistener: Flags [.], ack 15396, win 30016, length 0

代碼3.1 tcp斷開連接

clipboard.png

圖3.1 tcpdump抓包對應的tcp流程圖

3.6 參考

相關文章
相關標籤/搜索