MFC CSocket 類提供比 CAsyncSocket 更高層級的抽象,通過 CArchive 對象實現基於序列化的網絡數據傳輸,自動處理阻塞、消息泵、緩衝管理等複雜細節。本文系統講解 CSocket 編程模型、操作流程、關鍵注意事項及實戰代碼模式。
CArchive 自動處理數據打包/解包、類型轉換、版本控制IsBufferEmpty 確保數據讀取完整最簡模型:
CSocket+CArchive組合是最簡單的編程模型。單獨使用CSocket作為CAsyncSocket便利版本亦可,但失去序列化優勢。
CSocket serverSocket, clientSocket;
// 客戶端:通常用默認參數(流式、TCP)
clientSocket.Create();
// 服務端:必須指定監聽端口
serverSocket.Create(PORT_NUM); // 默認 SOCK_STREAM, AF_INET
關鍵限制:
CArchive僅支持流式套接字。若需數據報,請像CAsyncSocket一樣使用CSocket(不帶存檔)。嘗試對數據報使用CArchive會觸發 MFC 斷言失敗。
// 客戶端:主動連接服務器
clientSocket.Connect(serverIP, PORT_NUM);
// 服務端:監聽並接受連接
serverSocket.Listen(); // 進入被動監聽狀態
// 接受連接時(通常在 OnAccept 通知中)
CSocket* pNewSocket = new CSocket; // 必須預先構造
serverSocket.Accept(*pNewSocket); // 傳入引用,不要對新套接字調用 Create!
// 此時 pNewSocket 已連接,可用於通信
// 注意:pNewSocket 生命週期管理至關重要,超出作用域連接即斷開
CSocketFile socketFile(pSocket); // pSocket 為客戶端 socket 或服務端接受的新 socket
// 發送存檔(存儲模式)
CArchive arOut(&socketFile, CArchive::store);
// 接收存檔(加載模式)
CArchive arIn(&socketFile, CArchive::load);
方向性:單個
CArchive僅單向移動數據。雙向通信需兩個存檔:一個store發送,一個load接收。
// 發送端:序列化寫入
arOut << messageType << userID << timestamp << contentString;
arOut.Flush(); // 強制發送緩衝區數據
// 接收端:反序列化讀取
UINT msgType; DWORD userID; CTime time; CString content;
arIn >> msgType >> userID >> time >> content;
// 處理業務邏輯...
接受連接並建立存檔後,可執行密碼驗證、權限檢查等任務:
// 服務端示例:接收登錄請求
arIn >> username >> password;
if (ValidateUser(username, password)) {
arOut << LOGIN_SUCCESS << sessionToken;
arOut.Flush();
} else {
arOut << LOGIN_FAILED;
arOut.Flush();
pNewSocket->Close(); // 認證失敗斷開
}
// 1. 銷毀存檔(自動 Flush)
arOut.Close();
arIn.Close();
// 2. 銷毀 SocketFile
socketFile.Close();
// 3. 關閉套接字
pSocket->Close();
// 4. 釋放動態分配的 CSocket 對象(服務端接受的新連接)
delete pNewSocket;
問題:網絡數據可能分多個包到達,若緩衝區殘留數據,下一次 FD_READ 通知可能延遲甚至丟失。
void CMySocket::OnReceive(int nErrorCode) {
if (nErrorCode != 0) return;
CSocketFile file(this);
CArchive arIn(&file, CArchive::load);
// 關鍵:循環讀取直到緩衝區為空
while (!arIn.IsBufferEmpty()) {
UINT cmd; CString data;
arIn >> cmd >> data;
ProcessCommand(cmd, data);
}
arIn.Close();
file.Close();
CSocket::OnReceive(nErrorCode);
}
try {
arIn >> cmd >> data;
} catch (CArchiveException* e) {
// 連接斷開、數據損壞等
e->Delete();
Close(); // 觸發 OnClose
return;
} catch (CFileException* e) {
// 底層 socket 錯誤
e->Delete();
Close();
return;
}
// 發送文件/大對象:分塊寫入,避免一次性佔用大量內存
const int CHUNK_SIZE = 64 * 1024;
BYTE buffer[CHUNK_SIZE];
while (file.Read(buffer, CHUNK_SIZE) > 0) {
arOut.Write(buffer, bytesRead);
if (totalSize > LARGE_THRESHOLD) arOut.Flush(); // 定期刷新
}
arOut.Flush();
警告:若與非 MFC 應用通信,勿通過存檔發送 C++ 對象。除非對端同為 MFC 且知曉對象類型,否則無法反序列化。跨語言/平台請使用標準協議(Protobuf、JSON、MessagePack、FlatBuffers)或自定義二進制格式。
class CChatServer : public CSocket {
public:
virtual void OnAccept(int nErrorCode) {
if (nErrorCode == 0) {
CSocket* pClient = new CSocket;
if (Accept(*pClient)) {
// 為每個客戶端創建獨立處理線程或狀態機
AfxBeginThread(ClientThread, pClient);
} else {
delete pClient;
}
}
CSocket::OnAccept(nErrorCode);
}
};
UINT ClientThread(LPVOID pParam) {
CSocket* pSocket = (CSocket*)pParam;
CSocketFile file(pSocket);
CArchive arIn(&file, CArchive::load);
CArchive arOut(&file, CArchive::store);
try {
while (true) {
if (arIn.IsBufferEmpty()) {
// 等待數據到達(阻塞模式下自動處理)
// 或使用 PumpMessages 保持響應
}
UINT cmd; CString data;
arIn >> cmd >> data;
// 業務處理...
arOut << responseCmd << responseData;
arOut.Flush();
}
} catch (...) { /* 清理 */ }
arOut.Close(); arIn.Close(); file.Close(); pSocket->Close(); delete pSocket;
return 0;
}
CSocket + CArchive 模型以其高級抽象、自動序列化、阻塞友好特性,極大加速了 MFC 網絡應用開發。核心要點:
CArchive(store/load)IsBufferEmpty 循環讀取防止通知丟失現代高性能服務器建議遷移至 IOCP + 標準序列化庫 架構,但對於中小型 MFC 專案、桌面客戶端、雙端可控的內部系統,CSocket/CArchive 仍是最高效的開發選擇。