最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

nginx長連接keepalive與pipeline使用及說明

 更新時間:2025年12月22日 09:21:56   作者:ApeLife  
TCP和HTTP都支持keepalive機制,但實現(xiàn)方式不同,TCP通過自動發(fā)送空報文來確認對方是否在線,而HTTP通過復用TCP連接來提高效率

tcp與http都支持keepalive機制,但兩者是不同的。先看下tcp的keepalive機制。當客戶端與服務器建立了tcp連接后,如果客戶端一直不發(fā)送數(shù)據(jù),或者隔很長時間才發(fā)送一次數(shù)據(jù)。當連接很久沒有數(shù)據(jù)報文傳輸時,服務器如何去確定對方還在線。到底是掉線了還是確實沒有數(shù)據(jù)傳輸,連接還需不需要保持,這種情況在TCP協(xié)議設(shè)計中是需要考慮的。TCP協(xié)議通過一種巧妙的方式去解決這個問題,當超過一段時間之后,TCP自動發(fā)送一個數(shù)據(jù)為空的報文給對方,如果對方回應了這個報文,說明對方還在線,連接可以繼續(xù)保持,如果對方?jīng)]有報文返回并且重試了多次之后則認為連接丟失,沒有必要保持連接。這個過程相當于服務器向客戶端發(fā)送心跳包,確認客戶端是否還在線。

而http的keepalive機制為:通??蛻舳藶g覽器要展現(xiàn)一張完整的頁面需要很多個請求才能完成,如圖片,js,CSS等。如果每一個HTTP請求都需要新建并斷開一個TCP,這個開銷是完全沒有必要的。開啟HTTP Keep-Alive之后,能復用已有的TCP鏈接, 當一個請求已經(jīng)響應完畢,服務器端沒有立即關(guān)閉TCP連接,而是等待一段時間繼續(xù)接收瀏覽器可能發(fā)送過來的第二個請求,通常瀏覽器在第一個請求返回之后會立即發(fā)送第二個請求。因此在一個tcp連接上可以存在多個http請求,當然這個如果客戶端一直不發(fā)送新的http請求,超過一段時間后,nginx服務器還是會關(guān)閉這個TCP長連接。

開啟keepalive功能,可以減少time-wait套接字的個數(shù)

一、keealive的請求頭部解析

在http1.0協(xié)議里,需要客戶端發(fā)送connection: keep-alive請求頭來實現(xiàn)與服務器之間的長連接,否則長連接默認是關(guān)閉的。 http1.1默認支持keepalive,也就是說即使客戶端沒有發(fā)送connection:keep_alive請求頭部,服務器也會設(shè)置keepalive屬性,然后等待客戶端下一次請求。但通過請求頭connection: close可明確要求不進行長連接保持。先看下如何設(shè)置keepalive。

//http請求頭部常用頭部對應的處理函數(shù)
ngx_http_header_t  ngx_http_headers_in[] = 
{
    { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),ngx_http_process_connection },
};
//處理http請求頭部的connection字段
static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h,
    ngx_uint_t offset)
{
    if (ngx_strcasestrn(h->value.data, "close", 5 - 1))
	{
		//長連接關(guān)閉
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;		

    } 
	else if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) 
	{
		//保持長連接
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
    }

    return NGX_OK;
}

在解析http頭部的connection字段后,將會設(shè)置TCP的連接類型,是打開長連接還是關(guān)閉長連接。然后會設(shè)置到請求對象的keepalive中。對于keepalive只用于服務器與客戶端保持TCP長連接,而nginx內(nèi)部操作,例如內(nèi)部跳轉(zhuǎn), 命名location跳轉(zhuǎn)、子請求等是不需要用到keepalive的。

//調(diào)用各個http模塊協(xié)同處理這個請求
void ngx_http_handler(ngx_http_request_t *r)
{
	//不需要進行內(nèi)部跳轉(zhuǎn)。keepalive機制是在客戶端和nginx服務器之間才需要關(guān)注。對于內(nèi)部跳轉(zhuǎn)則不會用到
	//keepalive機制
    if (!r->internal) 	
	{
        switch (r->headers_in.connection_type) 
		{
	        case 0:
	            r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);//http1.1版本默認開啟keepalive
	            break;
	        case NGX_HTTP_CONNECTION_CLOSE:
	            r->keepalive = 0;					//關(guān)閉長連接
	            break;
	        case NGX_HTTP_CONNECTION_KEEP_ALIVE: 
	            r->keepalive = 1;                   //打開長連接
	            break;
        }
	}
}

二、keepalive的設(shè)置

當一個http請求完成后, 這個時候引用計數(shù)為0了,會釋放這個http請求,但到底要不要釋放tcp連接,是由keepalive機制與延遲關(guān)閉機制決定的。keepalive相比延遲關(guān)閉,優(yōu)先級更高。來看下ngx_http_finalize_connection函數(shù)的實現(xiàn)。

//釋放http請求與連接
static void ngx_http_finalize_connection(ngx_http_request_t *r)
{
	//執(zhí)行到這里,引用計數(shù)為1,則要準備結(jié)束請求了
	//keepalive為1表示請求需要釋放,但tcp連接還是用復用的
    if (!ngx_terminate
         && !ngx_exiting
         && r->keepalive
         && clcf->keepalive_timeout > 0)
    {
        ngx_http_set_keepalive(r);
        return;
    }
}

keepalive_timeout可以在nginx.conf配置文件中,通過keepalive_timeout指令設(shè)置,默認情況下超時時間就是75秒。如果超過這個時間都還沒有收到來自客戶端新的http請求,則會關(guān)閉這個tcp連接,keepalive功能也就結(jié)束了。ngx_http_set_keepalive函數(shù)則是正式開始keepalive的處理,函數(shù)有點長,實現(xiàn)了pipeline長連接與普通長連接。先來看下pipeline的實現(xiàn)。

三、pipeline處理

那么什么是pipeline呢?pipeline其實就是流水線作業(yè),它可以看作為keepalive的一種升華,因為pipeline也是基于長連接的,目的就是利用一個連接做多次請求。如果客戶端要提交多個請求,對于keepalive來說,那么第二個請求,必須要等到第一個請求的響應接收完全后,才能發(fā)起,這和TCP的停止等待協(xié)議是一樣的,得到兩個響應的時間至少為2*RTT。而對pipeline來說,客戶端不必等到第一個請求處理完后,就可以馬上發(fā)起第二個請求。得到兩個響應的時間可能能夠達到1*RTT。nginx是直接支持pipeline的,但是,nginx對pipeline中的多個請求的處理卻不是并行的,依然是一個請求接一個請求的處理,只是在處理第一個請求的時候,客戶端就可以發(fā)起第二個請求。這樣,nginx利用pipeline減少了處理完一個請求后,等待第二個請求的請求行與請求頭部的時間。其實nginx的做法很簡單,前面說到,nginx在讀取數(shù)據(jù)時,會將讀取的數(shù)據(jù)放到一個buffer里面,所以,如果nginx在處理完前一個請求后,如果發(fā)現(xiàn)buffer里面還有數(shù)據(jù),就認為剩下的數(shù)據(jù)是下一個請求的開始,然后接下來處理下一個請求,否則就設(shè)置keepalive。

來看下nginx服務器是如何處理pipeline的?

//設(shè)置keepalive過程
static void ngx_http_set_keepalive(ngx_http_request_t *r)
{
	//在接收到來自客戶端的連接請求時,同時也接收到了同一個tcp連接上的第2個http請求頭部。
	//因此在處理完第一個請求時,pos不等于last,說明接收到了第二個http請求頭部,接下來要立馬處理第二個請求
    if (b->pos < b->last) 
	{
		//在接收來自客戶端的請求行、或者請求頭部時。如果連接對象的buffer緩沖區(qū)不能夠存放所有的請求行,請求頭。
		//則http請求對象自己會開辟新的空間。因此在請求結(jié)束時,需要把請求對象開辟的空間加入到空閑表中。
		//這樣在這個連接上有新的http請求到來時,可以復用上一個請求對象開辟的空間
        if (b != c->buffer) 
		{
            if (hc->free == NULL)
			{
                hc->free = ngx_palloc(c->pool, cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
            }
			
			//將在用表移動到空閑表
            for (i = 0; i < hc->nbusy - 1; i++)
			{
                f = hc->busy[i];
                hc->free[hc->nfree++] = f;
                f->pos = f->start;
                f->last = f->start;
            }

            hc->busy[0] = b;
            hc->nbusy = 1;
        }
    }
}

(1) nginx服務器是怎么知道需要進行pipeline處理呢? 條件就是這個b->pos < b->last。 多個http請求可以復用同一個tcp連接,客戶端瀏覽器在發(fā)出第一個http請求時,可以不需要等待收到服務器的響應,可以立馬發(fā)出第二個http請求。在這種請求下,nginx服務器收到第一個http請求時,是有可能也接收到了第二個http請求的頭部信息,并保存到http請求對象的header_in緩沖區(qū)中。看下這個接收過程, 函數(shù)只負責從內(nèi)核讀取數(shù)據(jù)到緩沖區(qū),并沒有限制只讀取第一個http請求的數(shù)據(jù),如果有第二個http請求,也會把第二個http請求頭部也讀取到緩沖區(qū)。

static ssize_t ngx_http_read_request_header(ngx_http_request_t *r)
{
	//事件就緒,也就是從epoll_wait中返回后,從內(nèi)核緩沖區(qū)中讀取內(nèi)容到應用層緩沖區(qū)header_in
    if (rev->ready) 
	{
		//ngx_unix_recv
        n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last);  
    } 
}

那這個b != c->buffer又怎么理解呢? 默認情況下http請求結(jié)構(gòu)的header_in緩沖區(qū)是等于連接對象的buffer緩沖區(qū)的。 看下面這個函數(shù)就知道了。剛建立http請求時,兩個緩沖區(qū)都指向同一個內(nèi)存空間。

//首次建立tcp連接后,讀事件的回調(diào),用于創(chuàng)建一個ngx_http_request_t對象
static void ngx_http_init_request(ngx_event_t *rev)
{	
	//剛建立的請求,默認情況下這兩個緩沖區(qū)是相等的
    if (r->header_in == NULL) 
	{
        r->header_in = c->buffer;
    }
}

什么情況下http請求結(jié)構(gòu)的header_in緩沖區(qū)會不等于連接對象的buffer緩沖區(qū)呢? nginx服務器在接收到來自客戶端的http請求行或者請求頭部時,如果接受緩沖區(qū)不能夠存放請求行或者請求頭時,是會開辟一個新的緩沖區(qū),并把這個緩沖區(qū)加入到在用緩沖區(qū)數(shù)組busy中。通常情況下nginx服務器的默認緩沖區(qū)是足夠存放一個http請求行或者請求頭部的, 之所以緩沖區(qū)不夠,大部分情況下是接收到了多個http請求頭部。這種情況下將會執(zhí)行pipeline處理

//開辟一個大的緩沖區(qū),并把舊緩沖區(qū)的數(shù)據(jù)拷貝到新緩沖區(qū)中
//解析請求行時,request_line = 1, 解析請求頭時,request_line = 0
static ngx_int_t ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, ngx_uint_t request_line)
{
	/* 檢查給該請求分配的請求頭緩沖區(qū)個數(shù)是否已經(jīng)超過限制,默認最大個數(shù)為4個 */ 
	if (hc->busy == NULL) 
	{
		hc->busy = ngx_palloc(r->connection->pool, cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
	}

	/* 如果還沒有達到最大分配數(shù)量,則分配一個新的大緩沖區(qū) */  
	b = ngx_create_temp_buf(r->connection->pool, cscf->large_client_header_buffers.size);

	/* 將從空閑隊列取得的或者新分配的緩沖區(qū)加入已使用隊列 */  
    hc->busy[hc->nbusy++] = b;
	
	//header_in指向這個新的緩沖區(qū), 連接對象的緩沖區(qū)仍然為舊的緩沖區(qū),大小沒有改變
    r->header_in = b;
}

為什么要把busy這個在用緩沖區(qū)數(shù)據(jù)移動到空閑緩沖區(qū)數(shù)組呢? 為什么不直接釋放這些空間呢? 還是因為pipeline的原因,因為除了當前要結(jié)束的這個http請求外,緩沖區(qū)中還存放了其它的http請求。當這個http請求結(jié)束時,可以立即處理剩余的http請求。而如果不是pipeline,則會關(guān)閉這些緩沖區(qū)的, 這種情況下是一個普通的TCP長連接,客戶端什么時候會再次發(fā)送http請求,以及是否真的還會在發(fā)送http請求是不可知的, nginx將會釋放這些資源。

(2)接下來要釋放一個http請求了,但請求本身這個對象沒有釋放, 這樣在這個tcp連接上的已經(jīng)接收到的其它請求可以復用這個http請求對象。同時也會設(shè)置keepalive的超時時間,超過這個時間后會真正關(guān)閉這個tcp連接。

//設(shè)置keepalive過程
static void ngx_http_set_keepalive(ngx_http_request_t *r)
{
	//釋放請求的內(nèi)容空間
    ngx_http_free_request(r, 0);
	//將讀事件注冊到紅黑樹實現(xiàn)的定時器中,超時時間由keepalive_timeout命令設(shè)置,默認為75秒。
	//如果超時時間到后都還沒有再收到來自客戶端的http請求,則會關(guān)閉連接。
    ngx_add_timer(rev, clcf->keepalive_timeout);
    ngx_handle_read_event(rev, 0);
	//接收來自客戶端請求時,還不需要向客戶端寫入數(shù)據(jù),因此把寫回調(diào)設(shè)置為不做任何事情
    wev = c->write;
    wev->handler = ngx_http_empty_handler;
}

(3)在pipeline這種情況下,當前http請求已經(jīng)結(jié)束了。那nginx如何處理這個tcp連接上的其它http請求呢? nginx服務器將會設(shè)置讀事件ngx_event_t的接收回調(diào)handler為:ngx_http_init_request,并立馬把讀事件加入到post隊列中。因此這個讀事件就會被立即調(diào)用,從而開始處理這個tcp連接上的其它http請求。

static void ngx_http_set_keepalive(ngx_http_request_t *r)
{
	//該請求結(jié)束了,設(shè)置接收回調(diào)為ngx_http_init_request。在這個tcp連接上的下一個請求到來時,
	//重新開始對一個新請求進行處理
    if (b->pos < b->last) 
	{
		//設(shè)置為串行請求
        hc->pipeline = 1;
		//設(shè)置讀事件的回調(diào),并加入到post事件隊列
        rev->handler = ngx_http_init_request;
        ngx_post_event(rev, &ngx_posted_events);
        return;
    }
}

看下ngx_http_init_request這個函數(shù)怎么在這個tcp連接上重新創(chuàng)建一個http請求。對于pipeline,是不會創(chuàng)建一個新的http請求的,而是復用已經(jīng)結(jié)束的http請求。也可以看出不管是在這個tcp連接上新建立的http請求,還是當前請求結(jié)束后重新建立了一個新的http請求,都會調(diào)用ngx_http_init_request這個函數(shù)進行處理。

static void ngx_http_init_request(ngx_event_t *rev)
{
    r = hc->request;
    if (r) 
	{
        ngx_memzero(r, sizeof(ngx_http_request_t));
		//串行請求時值為1,表示舊請求結(jié)束后,內(nèi)部資源已經(jīng)釋放了,但請求本身沒有釋放。
		//這樣的話,在這個tcp連接上的新請求可以使用舊請求的對象。
		//配和ngx_http_set_keepalive函數(shù)就理解這部分的實現(xiàn)了
        r->pipeline = hc->pipeline;

		//串行請求時,在用緩沖區(qū)加入到了空閑緩沖區(qū)數(shù)組中。配和ngx_http_set_keepalive函數(shù)幫助理解
        if (hc->nbusy) 
		{
            r->header_in = hc->busy[0];
        }
    } 
}

需要注意的是pipeline只支持冪等請求,也就是并發(fā)請求之間沒有依賴關(guān)系,不管執(zhí)行多少次結(jié)果都應該是一樣的。例如在第二個請求需要等待第一個請求的響應從而決定一些行為時,這種場景下就不能使用pipeline。第一個請求添加用戶信息,第二個請求更新用戶信息,這種場景下就不能使用pipeline。當然這需要由客戶端自己來保證,并發(fā)發(fā)送多個冪等性的請求。

到此對pipeline這種長連接的處理過程已經(jīng)分析完成了,下面將分析下非pipeline情況下的長連接的處理過程。暫且稱非pipeline的長連接為普通長連接吧!

四、普通長連接處理

如果不是pipeline這種串行請求,因此會盡可能的釋放該請求空間。因為這個tcp連接上什么時候會有客戶端發(fā)來新的http請求,以及是否真的會有新的http請求是不確定的。nginx秉承一個能盡量減少資源占用就減少資源的原則,會把這個http請求內(nèi)部開辟的資源給釋放,同時也把請求對象本身也真正的釋放, 但tcp連接還是沒有關(guān)閉,等超時沒有收到來自客戶端的http請求時在關(guān)閉。以此同時將會設(shè)置讀事件ngx_event_t的接收回調(diào)handler為:ngx_http_keepalive_handler,用來處理在這個tcp連接上的其它http請求(這里不像pipeline, 當前http請求結(jié)束了,但此時這個tcp連接上并沒有其它的http請求存在)。

static void ngx_http_set_keepalive(ngx_http_request_t *r)
{
	//釋放資源
	if (hc->busy) 
	{
        for (i = 0; i < hc->nbusy; i++) 
		{
            ngx_pfree(c->pool, hc->busy[i]->start);
            hc->busy[i] = NULL;
        }
    }
	
	//設(shè)置讀事件回調(diào),用于處理來自客戶端的新的http請求
    rev->handler = ngx_http_keepalive_handler;
}

現(xiàn)在來看是ngx_http_keepalive_handler這個函數(shù)的處理過程。函數(shù)內(nèi)部會開辟一些必要的緩沖區(qū)外,最終還是回調(diào)用ngx_http_init_request這個函數(shù)重新開始一個新的http請求。

//http請求關(guān)閉后,tcp連接并沒有關(guān)閉。這個函數(shù)用于在這個tcp連接上接收新的http請求
static void ngx_http_keepalive_handler(ngx_event_t *rev)
{
	//開辟接收請求行、請求頭緩沖區(qū)。因為非pipeline情況下,釋放之前的一個請求時,
	//也把連接對象的buffer緩沖區(qū)也給釋放了。因此新的請求到來時,需要重新開辟空間
	b->pos = ngx_palloc(c->pool, size);

	//調(diào)用這個函數(shù),重新開始一個新的http請求處理
    ngx_http_init_request(rev);
}

可以看出,在同一個tcp連接下,不管是第一次建立的請求,還是之后重新建立的請求,最終都會調(diào)用ngx_http_init_request這個函數(shù)。由這個函數(shù)負責對請求的初始化操作。由此也可以知道,多個http請求是可以復用同一個tcp連接的。沒有必要客戶端發(fā)起一個http請求就建立一個tcp連接,太浪費資源了。到此keepalive機制與pipeline已經(jīng)分析完成了, 因為keepalive機制和在同一個tcp連接上重新建立一個新的http請求有關(guān),也與關(guān)閉一個http請求有關(guān),跨度比較大,看了也比較混亂。如果對http請求初始化過程還不是很清楚,可以參考前面的文章。下一篇文章將會對nginx的延遲關(guān)閉功能進行分析。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • nginx日志格式分析以及修改詳解

    nginx日志格式分析以及修改詳解

    Nginx日志對于統(tǒng)計、系統(tǒng)服務排錯很有用,下面這篇文章主要給大家介紹了關(guān)于nginx日志格式分析以及修改的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-04-04
  • filebeat同時收集錯誤日志與普通日志并存詳解

    filebeat同時收集錯誤日志與普通日志并存詳解

    這篇文章主要為大家介紹了filebeat同時收集錯誤日志與普通日志并存詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • nginx常用配置conf的示例代碼詳解

    nginx常用配置conf的示例代碼詳解

    這篇文章主要介紹了nginx常用配置conf,包括配置vue項目,配置接口代理的代碼詳解,代碼簡單易懂,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • ubuntu16.04下徹底卸載nginx的相關(guān)命令

    ubuntu16.04下徹底卸載nginx的相關(guān)命令

    nginx是一款自由的、開源的、高性能的HTTP服務器和反向代理服務器;這篇文章主要介紹了ubuntu16.04下徹底卸載nginx的相關(guān)命令,需要的朋友可以參考下
    2018-12-12
  • 前端服務器部署Nginx?+docker?+?ubuntu的完整過程

    前端服務器部署Nginx?+docker?+?ubuntu的完整過程

    Docker是一個開源的容器化平臺,可以讓你快速構(gòu)建、測試和部署應用程序,Nginx是一個高性能的Web服務器和反向代理服務器,常用于部署靜態(tài)網(wǎng)站、負載均衡等場景,這篇文章主要介紹了前端服務器部署Nginx?+docker?+?ubuntu的完整過程,需要的朋友可以參考下
    2025-11-11
  • 解讀Nginx變量字段大全

    解讀Nginx變量字段大全

    這篇文章主要介紹了Nginx變量字段,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-07-07
  • Nginx超時時間的配置說明

    Nginx超時時間的配置說明

    Nginx超時時間非常重要,因為它將直接影響網(wǎng)站的響應速度和用戶體驗,本文主要介紹了Nginx超時時間的配置說明,具有一定的參考價值,感興趣的可以了解一下
    2024-07-07
  • Nginx try_files 指令常見用法示例

    Nginx try_files 指令常見用法示例

    try_files是Nginx用于按順序檢查文件是否存在并返回第一個找到的文件,本文給大家介紹Nginx try_files 指令常見用法示例,感興趣的朋友跟隨小編一起看看吧
    2026-05-05
  • Linux下Nginx安裝教程

    Linux下Nginx安裝教程

    這篇文章主要為大家詳細介紹了Linux中Nginx的安裝教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Nginx服務器如何設(shè)置url鏈接

    Nginx服務器如何設(shè)置url鏈接

    這篇文章主要介紹了Nginx服務器如何設(shè)置url鏈接,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-10-10

最新評論

达孜县| 龙州县| 涟源市| 荣昌县| 青浦区| 达拉特旗| 贵溪市| 成都市| 策勒县| 关岭| 葫芦岛市| 荥阳市| 聂拉木县| 筠连县| 彭阳县| 哈巴河县| 正蓝旗| 右玉县| 江口县| 山阴县| 南雄市| 龙门县| 日土县| 沁水县| 山阳县| 廉江市| 浦江县| 大名县| 尚义县| 凌云县| 思茅市| 尉犁县| 奉贤区| 普宁市| 白城市| 格尔木市| 额尔古纳市| 桑日县| 阿坝县| 岱山县| 辽宁省|