TCP 是面向連接的 , 在實際應用中通常都需要檢測對端是否還處於連接中。如果已斷開連接,主要分為以下幾種情況:
1.連接的對端正常關閉,即使用 closesocket 關閉連接。
2.連接的對端非正常關閉,包括對端異常關閉,網絡斷開等情況。
對於第一種情況,很好判斷,但是對於第二種情況,可能會要麻煩一些。在網上找到了一些文章,大致有以下兩種解決方法:
自己編寫心跳包程序
簡單的說也就是在自己的程序中加入一條線程,定時向對端發送數據包,查看是否有 ACK ,如果有則連接正常,沒有的話則連接斷開。
使用 TCP 的 keepalive 機制
這個需要在 WinSock 編程時對當前 SOCKET 進行相應設置即可,比較方便。
為了方便起見,我這里采用 keepalive 機制,下面我就以 WinSock 上我實驗得到的結果來大致講一下其機理和使用方法。
首先說一下 keepalive 來判斷異常斷開的原理,其實 keepalive 的原理就是 TCP 內嵌的一個心跳包。
以服務器端為例,如果當前 server 端檢測到超過一定時間(默認是 7,200,000 milliseconds ,也就是 2 個小時)沒有數據傳輸,那么會 向client 端發送一個 keep-alive packet (該 keep-alive packet 就是 ACK 和當前 TCP 序列號減一的組合),此時 client 端應該為以下三種情況之一:
1. client 端仍然存在,網絡連接狀況良好。此時 client 端會返回一個 ACK 。 server 端接收到 ACK 后重置計時器,在2 小時后再發送探測。如果 2 小時內連接上有數據傳輸,那么在該時間基礎上向后推延 2 個小時。
2. 客戶端異常關閉,或是網絡斷開。在這兩種情況下, client 端都不會響應。服務器沒有收到對其發出探測的響應,並且在一定時間(系統默認為 1000 ms )后重復發送 keep-alive packet ,並且重復發送一定次數( 2000 XP 2003 系統默認為 5 次 , Vista 后的系統默認為 10 次)。
3. 客戶端曾經崩潰,但已經重啟。這種情況下,服務器將會收到對其存活探測的響應,但該響應是一個復位,從而引起服務器對連接的終止。
測試代碼如下:
#ifdef WIN32 //#include <WS2tcpip.h> #include <mstcpip.h> #else #include <netinet/in.h> #include <netinet/tcp.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #endif void WanLanTcpClient::setKeepalive(int sock, int keepAlive, int keepInterval, int keepCount, int keepIdle) { #ifdef WIN32 BOOL bKeepAlive = TRUE; int nRet = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive)); if (nRet == SOCKET_ERROR) { ERROR_LOG("setsockopt failed: %d", WSAGetLastError()); return; } // set KeepAlive parameter tcp_keepalive alive_in; tcp_keepalive alive_out; alive_in.keepalivetime = keepAlive; //3000ms alive_in.keepaliveinterval = keepInterval; //300ms alive_in.onoff = TRUE; unsigned long ulBytesReturn = 0; nRet = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in), &alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL); if (nRet == SOCKET_ERROR) { ERROR_LOG("WSAIoctl failed: %d/n", WSAGetLastError()); return; } #else //keepAlive = 1; //開啟keepalive屬性. 缺省值: 0(關閉) keepIdle = 3; //如果在1秒內沒有任何數據交互,則進行探測. 缺省值:7200(s) keepInterval = 1; //探測時發探測包的時間間隔為1秒. 缺省值:75(s) keepCount = 3; //探測重試的次數. 全部超時則認定連接失效..缺省值:9(次) setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive)); setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, (void*)&keepIdle, sizeof(keepIdle)); setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, (void*)&keepInterval, sizeof(keepInterval)); setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (void*)&keepCount, sizeof(keepCount)); #endif }