面向初學者的經典入門指南:拒絕 MFC 封裝,直擊 Socket 核心機制
MFC 的 CAsyncSocket/CSocket 雖方便,但封裝掩蓋了底層事件驅動本質。直接使用 WSAAsyncSelect 異步模型,能讓你真正理解:
// 定義自定義消息
#define WM_NET_EVENT (WM_USER + 100)
// 全局/成員變量
SOCKET g_sListen = INVALID_SOCKET;
HWND g_hWnd = NULL;
sockaddr_in g_clientAddr;
// 消息映射
ON_MESSAGE(WM_NET_EVENT, OnNetEvent)
// 初始化網絡
BOOL InitNetwork(HWND hWnd) {
g_hWnd = hWnd;
WSADATA wsa;
if (WSAStartup(MAKEWORD(2,2), &wsa)) return FALSE;
g_sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (g_sListen == INVALID_SOCKET) return FALSE;
sockaddr_in local = { AF_INET, htons(1688), {INADDR_ANY} };
if (bind(g_sListen, (sockaddr*)&local, sizeof(local)) == SOCKET_ERROR) return FALSE;
// 關鍵:將 Socket 事件映射到窗口消息
WSAAsyncSelect(g_sListen, g_hWnd, WM_NET_EVENT,
FD_ACCEPT | FD_CLOSE | FD_READ | FD_WRITE);
listen(g_sListen, SOMAXCONN);
return TRUE;
}
// 網絡事件總入口
LRESULT OnNetEvent(WPARAM wParam, LPARAM lParam) {
SOCKET s = (SOCKET)wParam;
int event = WSAGETSELECTEVENT(lParam);
int error = WSAGETSELECTERROR(lParam);
if (error) { HandleError(s, error); return 0; }
switch (event) {
case FD_ACCEPT: OnAccept(s); break;
case FD_READ: OnReceive(s); break;
case FD_WRITE: OnSend(s); break;
case FD_CLOSE: OnClose(s); break;
}
return 0;
}
void OnAccept(SOCKET sListen) {
sockaddr_in client; int len = sizeof(client);
SOCKET sClient = accept(sListen, (sockaddr*)&client, &len);
if (sClient == INVALID_SOCKET) return;
// 新 Socket 註冊事件(無 FD_ACCEPT)
WSAAsyncSelect(sClient, g_hWnd, WM_NET_EVENT,
FD_READ | FD_WRITE | FD_CLOSE);
SaveClient(sClient, client); // 保存到客戶端列表
}
void OnReceive(SOCKET s) {
char buf[4096];
int n = recv(s, buf, sizeof(buf), 0);
if (n > 0) {
NetMsg msg = { s, vector(buf, buf+n) };
PushToQueue(msg); // 放入線程安全隊列
SetEvent(g_hDataEvent); // 喚醒工作線程
}
}
void OnClose(SOCKET s) {
RemoveClient(s); closesocket(s);
}
UINT WorkerThread(LPVOID) {
HANDLE events[2] = { g_hDataEvent, g_hExitEvent };
while (true) {
DWORD ret = WaitForMultipleObjects(2, events, FALSE, INFINITE);
if (ret == WAIT_OBJECT_0 + 1) break; // 退出信號
NetMsg msg;
while (PopFromQueue(msg)) {
ProcessBusiness(msg); // 解析協議、操作數據庫、廣播等
}
}
return 0;
}
BOOL ConnectServer(LPCTSTR ip, int port) {
m_sock = socket(AF_INET, SOCK_STREAM, 0);
WSAAsyncSelect(m_sock, m_hWnd, WM_NET_EVENT,
FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);
sockaddr_in srv = { AF_INET, htons(port) };
inet_pton(AF_INET, ip, &srv.sin_addr);
connect(m_sock, (sockaddr*)&srv, sizeof(srv)); // 非阻塞,觸發 FD_CONNECT
return TRUE;
}
void OnConnect() { SendLoginPacket(); } // 連接成功發送登入包
void OnReceive() { /* 同服務器 */ }
void OnClose() { /* 重連邏輯 */ }
掌握異步 Socket 模型,是從「調用 API」進階為「設計網絡架構」的關鍵一步。