TCP長連接建立完成后,我們通常需要檢測網絡的連接狀態,以反饋給客戶做響應的處理。通過設置TCP keepalive的屬性,打開socket的keepalive屬性,並設置發送底層心跳包的時間間隔。TCP/IP五層網絡模型,我們調用socket等接口是應用層的函數,TCP keepalive是在底層定時發送心跳報文,服務器端接收到底層的心跳報文直接丟棄,不關心其內容。
以下是windows下TCP keepalive設置的函數:
#include <mstcpip.h> int CClientCtrl::socket_tcp_alive(int socket) { int ret = 0; int keep_alive = 1; ret = setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, (char*)&keep_alive, sizeof(keep_alive)); if (ret == SOCKET_ERROR) { printf("setsockopt failed: %d \n", WSAGetLastError()); return -1; } struct tcp_keepalive in_keep_alive = {0}; unsigned long ul_in_len = sizeof(struct tcp_keepalive); struct tcp_keepalive out_keep_alive = {0}; unsigned long ul_out_len = sizeof(struct tcp_keepalive); unsigned long ul_bytes_return = 0; in_keep_alive.onoff = 1; /*打開keepalive*/ in_keep_alive.keepaliveinterval = 5000; /*發送keepalive心跳時間間隔-單位為毫秒*/ in_keep_alive.keepalivetime = 1000; /*多長時間沒有報文開始發送keepalive心跳包-單位為毫秒*/ ret = WSAIoctl(socket, SIO_KEEPALIVE_VALS, (LPVOID)&in_keep_alive, ul_in_len, (LPVOID)&out_keep_alive, ul_out_len, &ul_bytes_return, NULL, NULL); if (ret == SOCKET_ERROR) { printf("WSAIoctl failed: %d \n", WSAGetLastError()); return -1; } return 0; }
以TCP 客戶端為例:
SHORT CClientCtrl::connetServer(LPCTSTR strAddr,USHORT portNum) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // TODO: Add your dispatch handler code here m_addr = strAddr; USES_CONVERSION; char *pCharAddr = T2A(m_addr); WSADATA wsd; WSAStartup(MAKEWORD(2, 2), &wsd); m_sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); SOCKADDR_IN ClientAddr; ClientAddr.sin_family = AF_INET; ClientAddr.sin_addr.S_un.S_addr = inet_addr(pCharAddr);// ClientAddr.sin_port = htons(portNum);//m_port int n = 0; n = connect(m_sockClient, (struct sockaddr*)&ClientAddr, sizeof(ClientAddr)); if (n == SOCKET_ERROR) { return -1; } //set keepAlive n = socket_tcp_alive(m_sockClient);//設置keepalive if (n <0 ) { return -1; } CreateThread(NULL, 0, &recvFromServer, (LPVOID)this, 0, NULL); return 0; }
DWORD WINAPI recvFromServer(LPVOID lpParameter) { char RecvBuf[MaxBufSize]; CClientCtrl* pParent = (CClientCtrl*)lpParameter; SOCKET *ClientSocket = &(pParent->m_sockClient); int iLength; while (true) { memset(RecvBuf, '\0', sizeof(RecvBuf)); iLength = recv(*ClientSocket, RecvBuf, MaxBufSize, 0); if (iLength <=0) //error { strcpy(RecvBuf,"ERROR"); ::SendMessage(pParent->m_hWnd,WM_RECVMSG,(WPARAM)RecvBuf,0); break; } else { ::SendMessage(pParent->m_hWnd,WM_RECVMSG,(WPARAM)RecvBuf,0); } } return 0; }
recv函數的返回值能反應網絡的狀態;
iLength = recv(*ClientSocket, RecvBuf, MaxBufSize, 0);
如果服務器端正常關閉socket ,那么recv 的返回值iLength 為0;這種情況即使不設置keepAlive 也是可以正常返回的;
如果出現暴力關閉,比如網線被拔,服務器程序強行關閉,recv 的返回值iLength 為-1; 如果不設置keepAlive 的話,這里是不會有任何反應的。
參考文獻:http://blog.csdn.net/embedded_sky/article/details/42077321