TCP之選項(xiàng)TCP_KETEPALIVE
KEEPALIVE機(jī)制,是TCP協(xié)議規(guī)定的TCP層(非應(yīng)用層業(yè)務(wù)代碼實(shí)現(xiàn)的)檢測(cè)TCP本端到對(duì)方主機(jī)的TCP連接的連通性的行為。避免服務(wù)器在客戶(hù)端出現(xiàn)各種不良狀況時(shí)無(wú)法感知,而永遠(yuǎn)等在這條TCP連接上。
該選項(xiàng)可以設(shè)置這個(gè)檢測(cè)行為的細(xì)節(jié),如下代碼所示:
int keepAlive = 1; // 非0值,開(kāi)啟keepalive屬性
int keepIdle = 60; // 如該連接在60秒內(nèi)沒(méi)有任何數(shù)據(jù)往來(lái),則進(jìn)行此TCP層的探測(cè)
int keepInterval = 5; // 探測(cè)發(fā)包間隔為5秒
int keepCount = 3; // 嘗試探測(cè)的次數(shù).如果第1次探測(cè)包就收到響應(yīng)了,則后2次的不再發(fā)
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
設(shè)置該選項(xiàng)后,如果60秒內(nèi)在此套接口所對(duì)應(yīng)連接的任一方向都沒(méi)有數(shù)據(jù)交換,TCP層就自動(dòng)給對(duì)方發(fā)一個(gè)保活探測(cè)分節(jié)(keepalive probe)。這是一個(gè)對(duì)方必須響應(yīng)的TCP分節(jié)。它會(huì)導(dǎo)致以下三種情況:
對(duì)方接收一切正常:以期望的ACK響應(yīng)。60秒后,TCP將重新開(kāi)始下一輪探測(cè)。
對(duì)方已崩潰且已重新啟動(dòng):以RST響應(yīng)。套接口的待處理錯(cuò)誤被置為ECONNRESET。
對(duì)方無(wú)任何響應(yīng):比如客戶(hù)端那邊已經(jīng)斷網(wǎng),或者客戶(hù)端直接死機(jī)。以設(shè)定的時(shí)間間隔嘗試3次,無(wú)響應(yīng)就放棄。套接口的待處理錯(cuò)誤被置為ETIMEOUT。
全局設(shè)置可更改/etc/sysctl.conf,加上:
net.ipv4.tcp_keepalive_intvl = 5
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 60
在程序中表現(xiàn)為:
阻塞模型下,當(dāng)TCP層檢測(cè)到對(duì)端socket不再可用時(shí),內(nèi)核無(wú)法主動(dòng)通知應(yīng)用層出錯(cuò),只有應(yīng)用層主動(dòng)調(diào)用read()或者write()這樣的IO
系統(tǒng)調(diào)用時(shí),內(nèi)核才會(huì)利用出錯(cuò)來(lái)通知應(yīng)用層。
非阻塞模型下,select或者epoll會(huì)返回sockfd可讀,應(yīng)用層對(duì)其進(jìn)行讀取時(shí),read()會(huì)報(bào)錯(cuò)。
一點(diǎn)經(jīng)驗(yàn):
實(shí)際上我們?cè)谧龇?wù)器程序的時(shí)候,對(duì)客戶(hù)端的保活探測(cè)基本上不依賴(lài)于這個(gè)TCP層的keepalive探測(cè)機(jī)制。
而是我們自己做一套應(yīng)用層的請(qǐng)求應(yīng)答消息,在應(yīng)用層實(shí)現(xiàn)這樣一個(gè)功能。
在Window上遇到這個(gè)問(wèn)題,最后發(fā)現(xiàn)貌似只支持:
// 設(shè)置KEEPALIVE (開(kāi)啟檢測(cè))
int optval = 1;
setsockopt(m_hSocket, SOL_SOCKET, SO_KEEPALIVE, (char *) &optval, sizeof(optval));
然后實(shí)際斷開(kāi)是在主動(dòng)Recv或者Send調(diào)用后才觸發(fā)的