nginx的timeout(基於nginx1.17.9)

nginx中使用timeout的地方非常多,本文主要分析客戶端和nginx通信時涉及到的幾個timeout。

  1. 連接建立成功,接收業務數據超時
  2. 接收http報文的超時

1 連接建立成功,接收業務數據超時
這個邏輯從ngx_event_accept函數開始分析,ngx_event_accept是nginx在監聽某個端口時,底層建立tcp連接成功後回調的函數。我們首先需要了解,在nginx中。一個連接是使用ngx_connection_t表示。每個ngx_connection_t對應兩個ngx_event_t結構體,一個讀,一個寫。他們之前有內在的字段關聯起來。另外一個ngx_connection_t也會關聯到一個ngx_listening_t結構體,ngx_listening_t是用於表示一個被監聽的ip端口,比如我們設置的127.0.0.1 80。ngx_connection_t指向的ngx_listening_t,表示該連接來自哪個監聽的ip端口。下面看看該函數的主要邏輯。

void ngx_event_accept(ngx_event_t *ev)
{
    socklen_t          socklen;
    ngx_err_t          err;
    ngx_log_t         *log;
    ngx_uint_t         level;
    ngx_socket_t       s;
    ngx_event_t       *rev, *wev;
    ngx_sockaddr_t     sa;
    ngx_listening_t   *ls;
    ngx_connection_t  *c, *lc;
    ngx_event_conf_t  *ecf;

    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);

    // 對應的ngx_connection_t
    lc = ev->data;
    // 對應的ngx_listening_t,即來自哪個監聽的socket
    ls = lc->listening;
    ev->ready = 0;

    do {
        socklen = sizeof(ngx_sockaddr_t);
        s = accept(lc->fd, &sa.sockaddr, &socklen);
        // 爲通信socket,獲取一個connection結構體數組
        c = ngx_get_connection(s, ev->log);
        // TCP
        c->type = SOCK_STREAM;
        // 一個連接分配一個內存池
        c->pool = ngx_create_pool(ls->pool_size, ev->log);
        // 爲通信socket設置讀寫數據的函數,由事件驅動模塊提供
        c->recv = ngx_recv;
        c->send = ngx_send;
        c->recv_chain = ngx_recv_chain;
        c->send_chain = ngx_send_chain;
        // 標識該連接來自哪個監聽的socket,即ngx_listening_t
        c->listening = ls;
        // 讀寫事件的結構體
        rev = c->read;
        wev = c->write;
        /*
	        執行連接到來時的回調,對於http模塊是ngx_http_init_connection,
	        會註冊等待讀事件,等待數據到來
        */
        ls->handler(c);
    } while (ev->available); // 是否連續accept(儘可能),可能會沒有等待accept的節點了
}

ngx_event_accept會分配一個ngx_connection_t去表示一個連接,然後執行ngx_http_init_connection回調。

// 有連接到來時的處理函數
void ngx_http_init_connection(ngx_connection_t *c)
{
    ngx_uint_t              i;
    ngx_event_t            *rev;
    struct sockaddr_in     *sin;
    ngx_http_port_t        *port;
    ngx_http_in_addr_t     *addr;
    ngx_http_log_ctx_t     *ctx;
    ngx_http_connection_t  *hc;

	// 讀事件結構體
    rev = c->read;
    // 設置新的回調函數,在http報文到達時執行
    rev->handler = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;
    /*
	    建立連接後,post_accept_timeout這麼長時間還沒有數據到來則超時,
	    post_accept_timeout的值等於nginx.conf中client_header_timeout字段的值
    */
    ngx_add_timer(rev, c->listening->post_accept_timeout);
    // 註冊讀事件,等到http報文到來
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_http_close_connection(c);
        return;
    }
}

ngx_http_init_connection函數做了兩個事情
1 設置定時器,如果超時後還沒有收到http報文,則關閉連接。
2 設置下一步回調函數爲ngx_http_wait_request_handler。
我們繼續來到ngx_http_wait_request_handler函數。該函數在http報文到來時被執行。

// 連接上有請求到來時的處理函數
static void ngx_http_wait_request_handler(ngx_event_t *rev)
{
    u_char                    *p;
    size_t                     size;
    ssize_t                    n;
    ngx_buf_t                 *b;
    ngx_connection_t          *c;
    ngx_http_connection_t     *hc;
    ngx_http_core_srv_conf_t  *cscf;

    c = rev->data;
    // 已經超時,http報文還沒有到達,關閉連接
    if (rev->timedout) {
        ngx_http_close_connection(c);
        return;
    }
  
    hc = c->data;
    cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module);
    // 分配內存接收數據,client_header_buffer_size來自nginx.conf
    size = cscf->client_header_buffer_size;

    b = c->buffer;
	// 創建一個buf用於接收http報文
    if (b == NULL) {
        b = ngx_create_temp_buf(c->pool, size);
        c->buffer = b;
    } 
    // 接收數據,即讀取http報文
    n = c->recv(c, b->last, size);
    // 更新可寫指針的位置,n是讀取的http報文字節數
    b->last += n;
    // 創建一個ngx_http_request_t結構體表示本次http請求
    c->data = ngx_http_create_request(c);
    // 開始處理http報文
    rev->handler = ngx_http_process_request_line;
    ngx_http_process_request_line(rev);
}

該函數主要是
1 在超時時仍然沒有收到http報文,則處理超時。
2 分配結構體ngx_http_request_t用於表示一個http請求
3 分配保存http報文的buffer,然後開始解析http報文
剛纔分析了建立連接和收到http報文間有一個超時時間。nginx還定義了另外一個超時。就是儘管有部分http報文已經到達,但是如果整個http報文到達過慢,也可能會導致超時問題。現在我們繼續來看一下ngx_http_process_request_line函數。該函數用於解析http報文。

// 處理http數據包的請求報文
static void ngx_http_process_request_line(ngx_event_t *rev)
{
    ssize_t              n;
    ngx_int_t            rc, rv;
    ngx_str_t            host;
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    c = rev->data;
    r = c->data;

    // 超時了,關閉請求
    if (rev->timedout) {
        c->timedout = 1;
        ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }

    rc = NGX_AGAIN;

    for ( ;; ) {
		// 讀取http報文到r的buffer裏
        n = ngx_http_read_request_header(r);
        // 解析http請求行,r->header_in的內容由ngx_http_read_request_header設置
        rc = ngx_http_parse_request_line(r, r->header_in);
        // 請求行解析完
        if (rc == NGX_OK) {
           ngx_list_init(&r->headers_in.headers, r->pool, 20, sizeof(ngx_table_elt_t))
            // 開始處理http頭
            rev->handler = ngx_http_process_request_headers;
            ngx_http_process_request_headers(rev);
            break;
        }
    }
}

ngx_http_process_request_line函數主要是處理http報文到達超時的問題和讀取http請求行,然後解析。那我們看一下這個timeout字段是怎麼設置的。我們看讀取http報文的函數ngx_http_read_request_header。

static ssize_t ngx_http_read_request_header(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_event_t               *rev;
    ngx_connection_t          *c;
    ngx_http_core_srv_conf_t  *cscf;

    c = r->connection;
    rev = c->read;
    // 讀取http報文
    n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last);
    // 沒有數據可讀
    if (n == NGX_AGAIN) {
        // 還沒有設置定時器(還沒有插入定時器紅黑樹)
        if (!rev->timer_set) {
            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
            // 根據nginx配置設置讀取頭部的超時時間,client_header_timeout來自nginx.conf
            ngx_add_timer(rev, cscf->client_header_timeout);
        }
        // 註冊讀事件
        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
            return NGX_ERROR;
        }

        return NGX_AGAIN;
    }

    // 讀取成功,更新last指針
    r->header_in->last += n;

    return n;
}

ngx_http_read_request_header讀取http報文的時候,如果沒有數據可讀則設置超時時間。超時時會執行ngx_http_process_request_line關閉請求。
總結:本文介紹了兩個timeout,在nginx中很多地方都使用了定時器,後面有空再分析。

Python 面試100講(基於Python3.x)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章