MFC CSocket 類封裝了 Windows Socket API,但在多客戶端服務端場景下,判斷某個 CSocket 對象是否已與客戶端建立有效連接,需結合狀態變量與非阻塞檢測,單純依賴對象指針無效。
CSocket 本身無 IsConnected() 方法。需自行維護連接狀態標誌,輔以 IOCTL 或心跳包檢測。
// 自定義 Socket 類繼承 CSocket
class CClientSocket : public CSocket {
public:
bool m_bConnected = false; // 連接狀態標誌
UINT m_nClientID = 0; // 客戶端唯一標識
virtual void OnAccept(int nErrorCode) override {
if (nErrorCode == 0) {
m_bConnected = true;
// 通知主對話框/服務器有新連接
GetParent()->PostMessage(WM_USER_NEW_CLIENT, (WPARAM)this);
}
CSocket::OnAccept(nErrorCode);
}
virtual void OnClose(int nErrorCode) override {
m_bConnected = false;
GetParent()->PostMessage(WM_USER_CLIENT_DISCONNECT, (WPARAM)this);
CSocket::OnClose(nErrorCode);
}
virtual void OnReceive(int nErrorCode) override {
// 接收數據邏輯
CSocket::OnReceive(nErrorCode);
}
};
// 服務端監聽 Socket
class CServerSocket : public CSocket {
public:
virtual void OnAccept(int nErrorCode) override {
if (nErrorCode == 0) {
CClientSocket* pClient = new CClientSocket();
if (Accept(*pClient)) {
// 將 pClient 加入管理容器(如 CList)
m_clientList.AddTail(pClient);
} else {
delete pClient;
}
}
CSocket::OnAccept(nErrorCode);
}
CList m_clientList;
};
bool IsSocketConnected(CSocket& sock) {
// 設置非阻塞
u_long mode = 1; // 1 = 非阻塞
sock.IOCTL(FIONBIO, &mode);
char dummy;
int nRet = sock.Receive(&dummy, 1, MSG_PEEK); // 偷窺模式,不移除數據
// 恢復阻塞
mode = 0;
sock.IOCTL(FIONBIO, &mode);
if (nRet == 0) return false; // 优雅關閉 (FIN)
if (nRet == SOCKET_ERROR) {
int err = sock.GetLastError();
if (err == WSAEWOULDBLOCK) return true; // 非阻塞無數據,連接正常
return false; // 其他錯誤視為斷開
}
return true; // 有數據可讀,連接正常
}
PING 封包lastHeartbeatTime,超過 60 秒無心跳判定斷開| 方法 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 狀態標誌位 | 簡單、無額外開銷 | 無法檢測網絡層面斷開 | 局域網、可靠連接 |
| 非阻塞 Peek | 能檢測 TCP FIN/RST | 需切換阻塞模式、輕微影響性能 | 需實時性檢測 |
| 應用層心跳 | 最可靠、穿透中間設備 | 需協議支持、額外流量 | 公網、長連接、生產環境 |
建議採用狀態標誌位作為基礎,心跳作為補充的組合策略,在 OnClose 回調中及時清理資源,構建健壯的多客戶端服務端。