短連接&長連接&並行連接
再說keep-alive之前,先說說HTTP的短連接&長連接。
-
短連接
所謂短連接,就是每次請求一個資源就建立連接,請求完成後連接立馬關閉。每次請求都經過“創建tcp連接->請求資源->響應資源->釋放連接”這樣的過程
-
長連接
所謂長連接(persistent connection),就是隻建立一次連接,多次資源請求都複用該連接,完成後關閉。要請求一個頁面上的十張圖,只需要建立一次tcp連接,然後依次請求十張圖,等待資源響應,釋放連接。
-
並行連接
所謂並行連接(multiple connections),其實就是併發的短連接。
keep-alive
具體client和server要從短連接到長連接最簡單演變需要做如下改進:
- client發出的HTTP請求頭需要增加Connection:keep-alive字段
- Web-Server端要能識別Connection:keep-alive字段,並且在http的response裏指定Connection:keep-alive字段,告訴client,我能提供keep-alive服務,並且"應允"client我暫時不會關閉socket連接
在HTTP/1.0裏,爲了實現client到web-server能支持長連接,必須在HTTP請求頭裏顯示指定
Connection:keep-alive
在HTTP/1.1裏,就默認是開啓了keep-alive,要關閉keep-alive需要在HTTP請求頭裏顯示指定
Connection:close
現在大多數瀏覽器都默認是使用HTTP/1.1,所以keep-alive都是默認打開的。一旦client和server達成協議,那麼長連接就建立好了。
接下來client就給server發送http請求,繼續上面的例子:請求十張圖片。如果每次"請求->響應"都是獨立的,那還好,10張圖片的內容都是獨立的。但是如果pipeline模式,上一個請求還沒響應,下一個請求就發出,這樣併發地發出10個請求,對於10個response client要怎麼區分呢?而HTTP協議又是沒有辦法區分的,所以這種情況下必須要求server端地響應是順序的,通過Conten-Length區分每次請求,這還只是針對靜態資源,那對於動態資源無法預知頁面大小的情況呢?我還沒有深入研究,可以查看https://www.byvoid.com/blog/http-keep-alive-header
另外注意: 指定keep-alive是一種client和server端儘可能需要滿足的約定,client和server可以在任意時刻都關閉keep-alive,彼此都不應該受影響。
Nginx keepa-alive配置
具體到Nginx的HTTP層的keepalive配置有
- keepalive_timeout
Syntax: keepalive_timeout timeout [header_timeout];
Default: keepalive_timeout 75s;
Context: http, server, location
The first parameter sets a timeout during which a keep-alive client connection will stay open on the server side. The zero value disables keep-alive client connections. The optional second parameter sets a value in the “Keep-Alive: timeout=time” response header field. Two parameters may differ.
- keepalive_requests
Syntax: keepalive_requests number;
Default: keepalive_requests 100;
Context: http, server, location
Sets the maximum number of requests that can be served through one keep-alive connection. After the maximum number of requests are made, the connection is closed.
可以看看Nginx的關於 keepalive_timeout 是實現
./src/http/ngx_http_request.c
static void
ngx_http_finalize_connection(ngx_http_request_t *r){
...
if (!ngx_terminate
&& !ngx_exiting
&& r->keepalive
&& clcf->keepalive_timeout > 0)
{
ngx_http_set_keepalive(r);
return;
}
...
}
static void
ngx_http_set_keepalive(ngx_http_request_t *r){
//如果發現是pipeline請求,判斷條件是緩存區裏有N和N+1個請求同時存在
if (b->pos < b->last) {
/* the pipelined request */
}
// 本次請求已經結束,開始釋放request對象資源
r->keepalive = 0;
ngx_http_free_request(r, 0);
c->data = hc;
// 如果嘗試讀取keep-alive的socket返回值不對,可能是客戶端close了。那麼就關閉socket
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
//開始正式處理pipeline
...
rev->handler = ngx_http_keepalive_handler;
...
// 設置了一個定時器,觸發時間是keepalive_timeout的設置
ngx_add_timer(rev, clcf->keepalive_timeout);
...
}
static void
ngx_http_keepalive_handler(ngx_event_t *rev){
// 發現超時則關閉socket
if (rev->timedout || c->close) {
ngx_http_close_connection(c);
return;
}
// 讀取keep-alive設置從socket
n = c->recv(c, b->last, size);
if (n == NGX_AGAIN) {
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
return;
}
...
}
//此處尚有疑惑?
ngx_reusable_connection(c, 0);
c->data = ngx_http_create_request(c);
// 刪除定時器
ngx_del_timer(rev);
// 重新開始處理請求
rev->handler = ngx_http_process_request_line;
ngx_http_process_request_line(rev);
}