前言
場景:客戶端程序需要實時知道和服務器的連接狀態。比較通用的做法應用層是采用心跳機制,每隔一端時間發送心跳能回復說明服務器正常。
實際應用場景中,服務端和客戶端并不是一家廠商的,比如說筆者這種情況,服務端是其他廠商,應用層協議沒有心跳機制,客戶端顯示的連接狀態需要客戶端自己處理。
筆者最開始使用的QTcpSocket進行socket連接,在客戶端程序監聽下面3個信息。
void disconnected()
void error(QAbstractSocket::SocketError socketError)
void stateChanged(QAbstractSocket::SocketState socketState)
筆者這邊的測試結果是,第一次關閉服務器端口,客戶端能檢測到error信號,能獲取到錯誤信息" The remote host closed the connection "
再次啟動服務器端口,客戶端使用同一個socket再次成功連接后,再次關閉服務器端口就檢測不到斷開信號,自此之后就再監測不到socket被斷開的情況。
參考了網絡上1篇文章
https://www.cnblogs.com/tomato0906/articles/4697098.html
文章并沒有實際測試代碼但提供了解決思路,判斷socket是否已經斷開的方法是使用非阻塞的select方式進行socket檢查,筆者在Linux下使用這種方式沒生效,
總的解決思路是使用 非阻塞的socket,使用recv函數進行判斷。
換了個寫法。直接采用 非阻塞的socket 使用recv + peek 的方式進行連接狀態檢查。
代碼
使用windows平臺(vs2013) 和 ubuntu 平臺,服務器采用網絡調試助手模擬,服務器socket主動斷開,客戶端能及時檢測到,檢測的及時性取決于SocketIsDisconn函數調用的頻率。
具體測試代碼及詳細說明如下:
main.cpp
//#include "stdafx.h"
#include <stdlib.h>
#include <stdio.h>#ifdef WIN32
#include <Ws2tcpip.h>
#include <winsock2.h>// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")#else#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdbool.h>
#include <errno.h>
#endifbool SocketIsDisconn(int sockfd)
{char buf[32] = { 0 };// 采用 recv + peek的方式進行數據讀取進行連接狀態的判斷。int recvLen = recv(sockfd, buf, sizeof(buf), MSG_PEEK);/*recv函數說明:Windows: 如果未發生錯誤, recv 將返回收到的字節數, buf 參數指向的緩沖區將包含接收的此數據。 如果連接已正常關閉,則返回值為零。否則,將返回值 SOCKET_ERROR(值為-1),并且可以通過調用 WSAGetLastError 來檢索特定的錯誤代碼。Linux: These calls return the number of bytes received, or -1 if an error occurred. In the event of an error, errno is set to indicate the error. The return value will be 0 when the peer has per‐formed an orderly shutdown. 翻譯過來和Windows說明類似,發生錯誤返回-1 從errno獲取錯誤碼,當另一端按順序關閉時,返回值為0。Windows錯誤碼:WSAEWOULDBLOCK: 資源暫時不可用。此錯誤是從無法立即完成的非阻止套接字上的操作返回的,例如,在沒有排隊要從套接字讀取數據時進行 recv 。 這是一個非致命錯誤,應稍后重試該操作。 WSAEWOULDBLOCK 在非阻止SOCK_STREAM套接字上調用 連接是正常的,因為必須經過一段時間才能建立連接。Linux錯誤碼: EAGAIN Resource temporarily unavailable (may be the same value as EWOULDBLOCK) (POSIX.1) EWOULDBLOCK Operation would block (may be same value as EAGAIN) (POSIX.1)錯誤碼和Windows也是類型的。這里我們區分3種情況: 1.recv返回值大于0 正常收到數據,連接狀態。2.recv返回值為-1 errCode為 WSAEWOULDBLOCK/EWOULDBLOCK 表明沒讀取到數據但是可以稍后再讀,仍為連接狀態。3.recv返回值為0 連接斷開狀態,其他錯誤情況 這里統一認為連接斷開狀態。*/#ifdef WIN32int errCode = WSAGetLastError();
#elseint errCode = errno;
#endifif (recvLen > 0){return false;}#ifdef WIN32if ((recvLen == -1) && (errCode == WSAEWOULDBLOCK))
#elseif ((recvLen == -1) && (errCode == EWOULDBLOCK))
#endif{return false;}return true;
}int main()
{
#ifdef WIN32// Initialize WinsockWSADATA wsaData;int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != NO_ERROR) {printf("WSAStartup function failed with error: %d\n", iResult);return 1;}SOCKET sockfd;
#elseint sockfd;
#endifint len;struct sockaddr_in address;int result;sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);address.sin_family = AF_INET;address.sin_addr.s_addr = inet_addr("192.168.11.213");address.sin_port = htons(9201);len = sizeof(address);result = connect(sockfd, (struct sockaddr *)&address, len);if (result == -1) {
#ifdef WIN32printf("socket function failed with error: %ld\n", WSAGetLastError());WSACleanup();getchar();
#elseperror("connect error:");
#endifexit(1);}//設置socket套接字為非阻塞
#ifdef WIN32DWORD argp = 1;ioctlsocket(sockfd, FIONBIO, &argp);
#elseint old_flag = fcntl(sockfd, F_GETFL, 0);int new_flag = old_flag | O_NONBLOCK;fcntl(sockfd, F_SETFL, new_flag);
#endif//循環檢查連接狀態,斷開則退出循環while (1){printf("check...\n");
#ifdef WIN32Sleep(3000);
#elsesleep(3);
#endifbool bClose = SocketIsDisconn(sockfd);printf("bDisconn:%d\n", bClose);if (bClose){break;}}
#ifdef WIN32closesocket(sockfd);getchar();
#elseclose(sockfd);
#endifexit(0);
}
Linux 下,直接gcc編譯運行,windows下需要用vs2013 打開項目文件然后編譯運行,整個項目下載。