傳統 Socket 程式設計需直接操作 wsock32.dll,流程繁瑣且易出錯。MFC 提供的 CAsyncSocket 類別封裝了非同步 Socket 操作,將 WSAStartup、bind、位址轉換等複雜步驟簡化為 Create、Connect 等直觀方法,且非同步特性天然替代了多線程處理網路事件的需求,大幅提升開發效率。本文以對話框架專案為例,示範 Client/Server 雙向通訊實作。
建立 Dialog Based 專案 CSockClient,對話框放置:
ID_Connect(連線)、ID_Send(傳送)、ID_Exit(關閉)IDC_LISTMSG(訊息顯示),關聯 CListBox m_MSGSIDC_EDITMSG(輸入訊息),關聯 CEdit m_MSG繼承 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);
}
建立對話框 IDD_Addr,含 IDC_Addr (CString) 與 IDC_Port (int) 控制項。
// 成員變數
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; }
}
}
Create 取代 WSAStartup + socket + bind;字串直接處理 IP/主機名轉換。OnReceive/OnSend/OnConnect/OnAccept 自動回呼,無需手動建立執行緒輪詢。善用 MFC 類別庫,能讓網路程式開發事半功倍。下次需要 Socket 通訊時,請優先考慮 CAsyncSocket。