首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Windows Sockets 帶存檔套接字編程:CSocket 與 CArchive 序列化實戰指南

     編輯:關於C語言

前言

MFC CSocket 類提供比 CAsyncSocket 更高層級的抽象,通過 CArchive 對象實現基於序列化的網絡數據傳輸,自動處理阻塞、消息泵、緩衝管理等複雜細節。本文系統講解 CSocket 編程模型、操作流程、關鍵注意事項及實戰代碼模式。

一、CSocket 編程模型核心優勢

  • 阻塞模式簡化:內部管理 Windows 消息泵,阻塞調用不凍結界面
  • 序列化集成:CArchive 自動處理數據打包/解包、類型轉換、版本控制
  • Unicode/ANSI 透明互操作:跨平台字符集自動轉換
  • 緩衝區智能管理:IsBufferEmpty 確保數據讀取完整

最簡模型:CSocket + CArchive 組合是最簡單的編程模型。單獨使用 CSocket 作為 CAsyncSocket 便利版本亦可,但失去序列化優勢。

二、完整通信建立流程(服務端與客戶端)

步驟 1:構造 CSocket 對象

CSocket serverSocket, clientSocket;

步驟 2:創建底層 SOCKET 句柄

// 客戶端:通常用默認參數(流式、TCP)
clientSocket.Create();

// 服務端:必須指定監聽端口
serverSocket.Create(PORT_NUM); // 默認 SOCK_STREAM, AF_INET

關鍵限制:CArchive 僅支持流式套接字。若需數據報,請像 CAsyncSocket 一樣使用 CSocket(不帶存檔)。嘗試對數據報使用 CArchive 會觸發 MFC 斷言失敗。

步驟 3:連接建立(雙向不同操作)

// 客戶端:主動連接服務器
clientSocket.Connect(serverIP, PORT_NUM);

// 服務端:監聽並接受連接
serverSocket.Listen(); // 進入被動監聽狀態

// 接受連接時(通常在 OnAccept 通知中)
CSocket* pNewSocket = new CSocket; // 必須預先構造
serverSocket.Accept(*pNewSocket);  // 傳入引用,不要對新套接字調用 Create!
// 此時 pNewSocket 已連接,可用於通信
// 注意:pNewSocket 生命週期管理至關重要,超出作用域連接即斷開

步驟 4:創建 CSocketFile 關聯套接字

CSocketFile socketFile(pSocket); // pSocket 為客戶端 socket 或服務端接受的新 socket

步驟 5:創建 CArchive 對象(雙向需兩個)

// 發送存檔(存儲模式)
CArchive arOut(&socketFile, CArchive::store);

// 接收存檔(加載模式)
CArchive arIn(&socketFile, CArchive::load);

方向性:單個 CArchive 僅單向移動數據。雙向通信需兩個存檔:一個 store 發送,一個 load 接收。

步驟 6:使用 CArchive 傳輸數據

// 發送端:序列化寫入
arOut << messageType << userID << timestamp << contentString;
arOut.Flush(); // 強制發送緩衝區數據

// 接收端:反序列化讀取
UINT msgType; DWORD userID; CTime time; CString content;
arIn >> msgType >> userID >> time >> content;
// 處理業務邏輯...

步驟 7:認證與業務處理(連接建立後)

接受連接並建立存檔後,可執行密碼驗證、權限檢查等任務:

// 服務端示例:接收登錄請求
arIn >> username >> password;
if (ValidateUser(username, password)) {
    arOut << LOGIN_SUCCESS << sessionToken;
    arOut.Flush();
} else {
    arOut << LOGIN_FAILED;
    arOut.Flush();
    pNewSocket->Close(); // 認證失敗斷開
}

步驟 8:資源清理(逆序銷毀)

// 1. 銷毀存檔(自動 Flush)
arOut.Close();
arIn.Close();

// 2. 銷毀 SocketFile
socketFile.Close();

// 3. 關閉套接字
pSocket->Close();
// 4. 釋放動態分配的 CSocket 對象(服務端接受的新連接)
delete pNewSocket;

三、關鍵技術細節與最佳實踐

3.1 IsBufferEmpty 確保讀取完整

問題:網絡數據可能分多個包到達,若緩衝區殘留數據,下一次 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);
}

3.2 異常處理與斷開檢測

try {
    arIn >> cmd >> data;
} catch (CArchiveException* e) {
    // 連接斷開、數據損壞等
    e->Delete();
    Close(); // 觸發 OnClose
    return;
} catch (CFileException* e) {
    // 底層 socket 錯誤
    e->Delete();
    Close();
    return;
}

3.3 發送大數據塊優化

// 發送文件/大對象:分塊寫入,避免一次性佔用大量內存
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();

3.4 跨 MFC/非 MFC 互操作限制

警告:若與非 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 網絡應用開發。核心要點:

  1. 僅支持流式套接字(TCP)
  2. 雙向通信需雙 CArchive(store/load)
  3. IsBufferEmpty 循環讀取防止通知丟失
  4. 異常捕獲處理斷開/錯誤
  5. 跨平台/跨語言場景勿用 MFC 序列化

現代高性能服務器建議遷移至 IOCP + 標準序列化庫 架構,但對於中小型 MFC 專案、桌面客戶端、雙端可控的內部系統,CSocket/CArchive 仍是最高效的開發選擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved