默認http1.1協議的請求頭是默認開啓keepalive,如圖:nginx
keepalive是在TCP中一個能夠檢測死鏈接的機制,做用是保持socket長鏈接不被斷開,屬於tcp層的功能,並不屬於應用層。瀏覽器
先看keepalive的用法:有三個參數,開放給應用層使用服務器
sk->keepalive_probes:探測次數,重試次數 sk->keepalive_time 探測的心跳間隔,TCP連接在多少秒以後沒有數據報文傳輸啓動探測報文 sk->keepalive_intvl 探測間隔,未收到回覆時,重試的時間間隔
默認配置查看:socket
[***@*** ~]$ cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 [***@*** ~]$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 [***@*** ~]$ cat /proc/sys/net/ipv4/tcp_keepalive_probes 9
使用方法:tcp
int keepalive = 1; // 開啓keepalive屬性 int keepidle = 60; // 如該鏈接在60秒內沒有任何數據往來,則進行探測 int keepinterval = 5; // 探測時發包的時間間隔爲5 秒 int keepcount = 3; // 探測嘗試的次數。若是第1次探測包就收到響應了,則後2次的再也不發。而且清零該計數 setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive , sizeof(keepalive )); setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepidle , sizeof(keepidle )); setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepinterval , sizeof(keepinterval )); setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepcount , sizeof(keepcount ));
應用層這麼設置後,會把默認配置覆蓋,走手動設置的配置。
對於一個已經創建的tcp鏈接。若是在keepalive_time時間內雙方沒有任何的數據包傳輸,則開啓keepalive功能的一端將發送 keepalive數據心跳包,若沒有收到應答,則每隔keepalive_intvl時間再發送該數據包,發送keepalive_probes次。一直沒有 收到應答,則發送rst包關閉鏈接。若收到應答,則將計時器清零。函數
根據抓包繼續分析keepalive發送及回覆的心跳包內容:ui
tcp頭部結構體源碼爲:spa
typedef struct _TCP_HEADER { short m_sSourPort; // 源端口號16bit short m_sDestPort; // 目的端口號16bit unsigned int m_uiSequNum; // req字段 序列號32bit unsigned int m_uiAcknowledgeNum; //ack字段 確認號32bit short m_sHeaderLenAndFlag; // 前4位:TCP頭長度;中6位:保留;後6位:標誌位 short m_sWindowSize; //win字段 窗口大小16bit short m_sCheckSum; // 檢驗和16bit short m_surgentPointer; // 緊急數據偏移量16bit }__attribute__((packed))TCP_HEADER, *PTCP_HEADER;
看發送的心跳包內容:code
0000 d4 6d 50 f5 02 7f f4 5c 89 cb 35 29 08 00 //mac頭 14字節: 45 00 // ip頭 20字節 : 0010 00 28 10 f4 00 00 40 06 5b dd ac 19 42 76 0a b3 0020 14 bd e4 4a 1f 7c 32 7e 7a cb 4c bc 55 08 50 10 // tcp頭 20字節 0030 10 00 3f 00 00 00 //分析tcp頭部內容 e4 4a //源端口號16bit 10進製爲:58442 1f 7c //目的端口號16bit 10進製爲 : 8060 32 7e 7a cb // req字段 序列號32bit 10進製爲 : 4c bc 55 08 // ack字段 確認號32bit 5 // 前4位:TCP頭長度 5*4 =20 字節 沒問題 0 10 /// 中6位:保留;後6位:標誌位 10 表明倒數第5位爲1, 標識改tcp包爲 ACK 確認包 0030 10 00 3f 00 00 00
繼續看回復的心跳包內容 :圖片
0000 f4 5c 89 cb 35 29 d4 6d 50 f5 02 7f 08 00 45 00 0010 00 34 47 28 40 00 36 06 ef 9c 0a b3 14 bd ac 19 0020 42 76 // 前面數據不解讀 1f 7c e4 4a 4c bc 55 08 32 7e 7a cc 8// TCP頭長度爲8 * 4 = 32 除了頭部 還有 選項數據 12字節 0 10 // 中6位:保留;後6位:標誌位 10 表明倒數第5位爲1, 標識該tcp包爲 ACK 確認包 0030 01 3f //win字段 窗口大小16bit 4e 0d // 檢驗和16bit 00 00 // 緊急數據偏移量16bit 01 01 08 0a 00 59 be 1c 39 13 0040 cf 12 // 選項數據 12字節
由上能夠看出,tcp維持長鏈接的心跳包是由瀏覽器向服務器先出發送一個ACK包,而後服務器再回復一個ACK包,且帶了選項數據
首先作的是版本判斷 :http協議版本低於1.1時,該連接的keepalive置爲0 if (r->http_version < NGX_HTTP_VERSION_11) { r->keepalive = 0; } ngx_http_process_connection 函數中 ngx_http_request_t 中帶有keep-alive則把改連接標識起來 if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) { r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE; } ngx_http_handler函數中對r->headers_in.connection_type 判斷,給r->keepalive賦值爲1 switch (r->headers_in.connection_type) { case NGX_HTTP_CONNECTION_KEEP_ALIVE: r->keepalive = 1; break; } ngx_configure_listening_sockets函數中,當keepalive爲1時,對該鏈接開啓KEEPALIVE,以後tcp底層就會對該鏈接fd作檢測死鏈接的機制,保持長鏈接,不斷開。 if (ls[i].keepalive) { value = (ls[i].keepalive == 1) ? 1 : 0; if (setsockopt(ls[i].fd, SOL_SOCKET, SO_KEEPALIVE,//開啓keepalive功能 (const void *) &value, sizeof(int)) == -1) }
在nginx經過 setsockopt(ls[i].fd, SOL_SOCKET, SO_KEEPALIVE,(const void *) &value, sizeof(int))開啓keepalive後,會始終和客戶端保持長鏈接,如此會出現一個很嚴峻的問題,
每一個woker的能保持的鏈接數是有限的(ep = epoll_create(cycle->connection_n / 2); cycle->connection_n / 2 爲epoll能管理的fd上限),如此一來,鏈接數很快就被耗盡,這時候nginx應該怎麼處理 ?
爲了找到這個答案,咱們來看nginx關於keeoalive的兩個配置參數
keepalive_timeout
keepalive_timeout timeout [header_timeout];
keepalive_requests
keepalive_requests指令用於設置一個keep-alive鏈接上能夠服務的請求的最大數量,當最大請求數量達到時,鏈接被關閉,值爲0會也禁用keep-alive客戶端鏈接;。默認是100。
答案顯而易見,經過 keepalive_timeout keepalive_requests 來管理長鏈接,
經過這兩個機制來保證每一個worker的鏈接數不會超過epoll所能管理的數目。