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

拋棄繁瑣 Socket API:用 MFC CAsyncSocket 輕鬆實作網路程式設計

     編輯:VC 教學

前言:為什麼選擇 CAsyncSocket?

傳統 Socket 程式設計需直接操作 wsock32.dll,流程繁瑣且易出錯。MFC 提供的 CAsyncSocket 類別封裝了非同步 Socket 操作,將 WSAStartup、bind、位址轉換等複雜步驟簡化為 Create、Connect 等直觀方法,且非同步特性天然替代了多線程處理網路事件的需求,大幅提升開發效率。本文以對話框架專案為例,示範 Client/Server 雙向通訊實作。

客戶端實作步驟

1. 專案與介面建立

建立 Dialog Based 專案 CSockClient,對話框放置:

  • 按鈕:ID_Connect(連線)、ID_Send(傳送)、ID_Exit(關閉)
  • ListBox:IDC_LISTMSG(訊息顯示),關聯 CListBox m_MSGS
  • Edit:IDC_EDITMSG(輸入訊息),關聯 CEdit m_MSG

2. 自訂 Socket 類別 (MySock.h / MySock.cpp)

繼承 CAsyncSocket,加入狀態與緩衝區成員:

// MySock.h
class CMySocket : public CAsyncSocket
{
public:
    BOOL m_bConnected;
    UINT m_nLength;
    char m_szBuffer[4096];

    CMySocket();
    virtual ~CMySocket();

    // 覆寫非同步通知函數
    virtual void OnReceive(int nErrorCode);
    virtual void OnSend(int nErrorCode);
    virtual void OnConnect(int nErrorCode);
};

關鍵實作:

// MySock.cpp
void CMySocket::OnReceive(int nErrorCode)
{
    m_nLength = Receive(m_szBuffer, sizeof(m_szBuffer), 0);
    // 取得主對話框指標並顯示訊息
    CCSockClientApp* pApp = (CCSockClientApp*)AfxGetApp();
    CCSockClientDlg* pDlg = (CCSockClientDlg*)pApp->m_pMainWnd;
    pDlg->m_MSGS.InsertString(0, m_szBuffer);
    memset(m_szBuffer, 0, sizeof(m_szBuffer));
    CAsyncSocket::OnReceive(nErrorCode);
}

void CMySocket::OnSend(int nErrorCode)
{
    Send(m_szBuffer, m_nLength, 0);
    m_nLength = 0;
    memset(m_szBuffer, 0, sizeof(m_szBuffer));
    AsyncSelect(FD_READ);  // 繼續監聽讀取事件
    CAsyncSocket::OnSend(nErrorCode);
}

void CMySocket::OnConnect(int nErrorCode)
{
    if (nErrorCode == 0) {
        m_bConnected = TRUE;
        CCSockClientApp* pApp = (CCSockClientApp*)AfxGetApp();
        CCSockClientDlg* pDlg = (CCSockClientDlg*)pApp->m_pMainWnd;
        memcpy(m_szBuffer, "Connected to ", 13);
        strncat(m_szBuffer, pDlg->m_szServerAdr, sizeof(pDlg->m_szServerAdr));
        pDlg->m_MSGS.InsertString(0, m_szBuffer);
        AsyncSelect(FD_READ);  // 準備接收資料
    }
    CAsyncSocket::OnConnect(nErrorCode);
}

3. 連線對話框 (AddrDlg)

建立對話框 IDD_Addr,含 IDC_Addr (CString) 與 IDC_Port (int) 控制項。

4. 主對話框邏輯 (CSockClientDlg)

// 成員變數
protected:
    int m_nTryCount;
    CMySocket m_clientSocket;
    UINT m_nPort;
public:
    char m_szServerAdr[256];

// 連線按鈕
void CCSockClientDlg::OnConnect()
{
    m_clientSocket.ShutDown(2);
    m_clientSocket.m_hSocket = INVALID_SOCKET;
    m_clientSocket.m_bConnected = FALSE;

    CAddrDlg dlg;
    dlg.m_Port = 1088;
    if (dlg.DoModal() == IDOK && !dlg.m_Addr.IsEmpty()) {
        strcpy(m_szServerAdr, dlg.m_Addr);
        m_nPort = dlg.m_Port;
        SetTimer(1, 1000, NULL);  // 每秒嘗試連線
        m_nTryCount = 0;
    }
}

// Timer 處理連線重試
void CCSockClientDlg::OnTimer(UINT nIDEvent)
{
    if (m_clientSocket.m_hSocket == INVALID_SOCKET) {
        BOOL bFlag = m_clientSocket.Create(0, SOCK_STREAM, FD_CONNECT);
        if (!bFlag) {
            AfxMessageBox(_T("Socket Error!"));
            m_clientSocket.Close();
            PostQuitMessage(0);
            return;
        }
    }
    m_clientSocket.Connect(m_szServerAdr, m_nPort);
    m_nTryCount++;
    if (m_nTryCount >= 10 || m_clientSocket.m_bConnected) {
        KillTimer(1);
        if (m_nTryCount >= 10) AfxMessageBox(_T("Connect Failed!"));
        return;
    }
    CDialog::OnTimer(nIDEvent);
}

// 傳送按鈕
void CCSockClientDlg::OnSend()
{
    if (m_clientSocket.m_bConnected) {
        m_clientSocket.m_nLength = m_MSG.GetWindowText(
            m_clientSocket.m_szBuffer, sizeof(m_clientSocket.m_szBuffer));
        m_clientSocket.AsyncSelect(FD_WRITE);
        m_MSG.SetWindowText(_T(""));
    }
}

// 關閉按鈕
void CCSockClientDlg::OnExit()
{
    m_clientSocket.ShutDown(2);
    EndDialog(0);
}

伺服器端實作重點

Server 端架構類似,核心在於 CMyServerSocket::OnAccept 處理新連線:

// MyServerSocket.h
class CMyServerSocket : public CAsyncSocket
{
public:
    CNewSocket* m_pSocket;  // 對應每個客戶端的通訊 Socket

    virtual void OnAccept(int nErrorCode);
};

// MyServerSocket.cpp
void CMyServerSocket::OnAccept(int nErrorCode)
{
    CNewSocket* pSocket = new CNewSocket();
    if (Accept(*pSocket)) {
        pSocket->AsyncSelect(FD_READ);  // 監聽讀取事件
        m_pSocket = pSocket;
    } else {
        delete pSocket;
    }
}

CNewSocket 覆寫 OnReceive/OnSend 實作 Echo 功能:

void CNewSocket::OnReceive(int nErrorCode)
{
    m_nLength = Receive(m_szBuffer, sizeof(m_szBuffer), 0);
    AsyncSelect(FD_WRITE);  // 收到資料後立即準備回傳
    CAsyncSocket::OnReceive(nErrorCode);
}

void CNewSocket::OnSend(int nErrorCode)
{
    Send(m_szBuffer, m_nLength, 0);
    CAsyncSocket::OnSend(nErrorCode);
}

對話框加入「偵聽」按鈕啟動伺服器:

void CCSockServerDlg::OnListen()
{
    if (m_srvrSocket.m_hSocket == INVALID_SOCKET) {
        BOOL bFlag = m_srvrSocket.Create(m_nPort, SOCK_STREAM, FD_ACCEPT);
        if (!bFlag) { AfxMessageBox(_T("Socket Error!")); return; }
    }
    if (!m_srvrSocket.Listen(1)) {
        int nError = m_srvrSocket.GetLastError();
        if (nError != WSAEWOULDBLOCK) { AfxMessageBox(_T("Socket Error!")); return; }
    }
}

總結:CAsyncSocket 的優勢

  • 簡化流程:Create 取代 WSAStartup + socket + bind;字串直接處理 IP/主機名轉換。
  • 非同步事件驅動:OnReceive/OnSend/OnConnect/OnAccept 自動回呼,無需手動建立執行緒輪詢。
  • MFC 整合:自然配合訊息迴路,GUI 回應流暢不卡頓。

善用 MFC 類別庫,能讓網路程式開發事半功倍。下次需要 Socket 通訊時,請優先考慮 CAsyncSocket。

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