MFC CAsyncSocket 封裝了 Winsock 細節,提供非同步事件驅動模型,讓網路程式設計更簡潔。本文完整示範 Client/Server 雙向通訊實作。
Dialog-based 專案 CSockClient,包含:
m_MSG (CEdit)、m_MSGS (CListBox)// 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);
}
資源建立對話框 IDD_ADDR,含 IP (IDC_ADDR) 與 Port (IDC_PORT) 編輯框,ClassWizard 建立 CAddrDlg 類別與變數 m_Addr (CString)、m_Port (int)。
// 成員變數
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);
}
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);
}
// 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;
}
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); }
}
}
WSAStartup、bind 為 Create 單一呼叫FD_READ, FD_WRITE, FD_CONNECT, FD_ACCEPT) 取代複雜執行緒同步OnReceive/OnSend/OnConnect/OnAccept 即時響應網路事件