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

VC++ 拋棄 Socket 直接用 CAsyncSocket 實作網路程式設計

     編輯:VC++

前言

MFC CAsyncSocket 封裝了 Winsock 細節,提供非同步事件驅動模型,讓網路程式設計更簡潔。本文完整示範 Client/Server 雙向通訊實作。

Client 端實作

1. 專案建立與 UI 設計

Dialog-based 專案 CSockClient,包含:

  • 按鈕:連線 (ID_CONNECT)、傳送 (ID_SEND)、離開 (ID_EXIT)
  • ListBox (IDC_LISTMSG) 顯示訊息
  • Edit (IDC_EDITMSG) 輸入訊息
  • ClassWizard 建立控制項變數:m_MSG (CEdit)、m_MSGS (CListBox)

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

// 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
CMySocket::CMySocket() : m_bConnected(FALSE), m_nLength(0) { memset(m_szBuffer, 0, sizeof(m_szBuffer)); }
CMySocket::~CMySocket() { if (m_hSocket != INVALID_SOCKET) Close(); }

void CMySocket::OnReceive(int nErrorCode)
{
    m_nLength = Receive(m_szBuffer, sizeof(m_szBuffer), 0);
    CMyApp* pApp = (CMyApp*)AfxGetApp();
    CMyDlg* pDlg = (CMyDlg*)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;
        CMyApp* pApp = (CMyApp*)AfxGetApp();
        CMyDlg* pDlg = (CMyDlg*)pApp->m_pMainWnd;
        strcpy(m_szBuffer, "Connected to ");
        strcat(m_szBuffer, pDlg->m_szServerAdr);
        pDlg->m_MSGS.InsertString(0, m_szBuffer);
        AsyncSelect(FD_READ);
    }
    CAsyncSocket::OnConnect(nErrorCode);
}

3. 連線對話框 (AddrDlg.h/.cpp)

資源建立對話框 IDD_ADDR,含 IP (IDC_ADDR) 與 Port (IDC_PORT) 編輯框,ClassWizard 建立 CAddrDlg 類別與變數 m_Addr (CString)、m_Port (int)。

4. 主對話框實作 (CSockClientDlg.cpp)

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

// 連線按鈕
void CMyDlg::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); // 1 秒重試
        m_nTryCount = 0;
    }
}

// Timer 重試連線
void CMyDlg::OnTimer(UINT nIDEvent)
{
    if (m_clientSocket.m_hSocket == INVALID_SOCKET)
    {
        if (!m_clientSocket.Create(0, SOCK_STREAM, FD_CONNECT))
        { 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 CMyDlg::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 CMyDlg::OnExit()
{
    m_clientSocket.ShutDown(2);
    EndDialog(0);
}

Server 端實作

1. 資料處理 Socket (CDataSocket)

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

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

2. 監聽 Socket (CListenSocket)

// CListenSocket.h
class CListenSocket : public CAsyncSocket
{
public:
    CDataSocket* m_pDataSocket;
    virtual void OnAccept(int nErrorCode);
};

// CListenSocket.cpp
void CListenSocket::OnAccept(int nErrorCode)
{
    CDataSocket* pSock = new CDataSocket();
    if (Accept(*pSock))
    {
        pSock->AsyncSelect(FD_READ);
        m_pDataSocket = pSock;
    }
    else delete pSock;
}

3. 伺服器對話框啟動監聽

void CServerDlg::OnListen()
{
    if (m_listenSocket.m_hSocket == INVALID_SOCKET)
    {
        if (!m_listenSocket.Create(m_nPort, SOCK_STREAM, FD_ACCEPT))
        { AfxMessageBox(_T("Socket Error!")); m_listenSocket.Close(); PostQuitMessage(0); return; }
    }
    if (!m_listenSocket.Listen(1))
    {
        int err = m_listenSocket.GetLastError();
        if (err != WSAEWOULDBLOCK)
        { AfxMessageBox(_T("Listen Error!")); m_listenSocket.Close(); PostQuitMessage(0); }
    }
}

CAsyncSocket 優勢總結

  • 封裝 WSAStartup、bind 為 Create 單一呼叫
  • IP/主機名解析自動處理,字串/整數直接操作
  • 非同步事件模型 (FD_READ, FD_WRITE, FD_CONNECT, FD_ACCEPT) 取代複雜執行緒同步
  • 虛擬函式 OnReceive/OnSend/OnConnect/OnAccept 即時響應網路事件
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved