nginx中使用timeout的地方非常多,本文主要分析客戶端和nginx通信時涉及到的幾個timeout。
- 連接建立成功,接收業務數據超時
- 接收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)