Windows网络编程
11、WSAAsyncSelect异步选择模型
1、课程目标
- 理解Windows消息驱动的I/O模型
- 掌握WSAAsyncSelect函数的使用
- 实现基于消息的异步网络服务器
- 了解WSAAsyncSelect与GUI程序的结合
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| WSAAsyncSelect | Windows Socket Async Select | 异步选择函数 |
| WM_USER | User Message | 用户自定义消息起始值 |
| FD_READ | - | 数据可读事件 |
| FD_WRITE | - | 数据可写事件 |
| FD_ACCEPT | - | 连接请求事件 |
| FD_CONNECT | - | 连接完成事件 |
| FD_CLOSE | - | 连接关闭事件 |
| WSAGETSELECTEVENT | - | 获取事件类型宏 |
| WSAGETSELECTERROR | - | 获取错误码宏 |
3、使用工具
- Visual Studio 2022
- Spy++(监控窗口消息)
- Process Monitor
- Resource Hacker(查看窗口资源)
4、技术原理
4.1、WSAAsyncSelect工作原理
应用程序
|
+------------------+------------------+
| | |
窗口过程 消息循环 网络I/O
| | |
|<-- WM_SOCKET ----+<-- PostMessage --|
| | |
处理网络事件 GetMessage WSAAsyncSelect
| | 注册事件通知
| | |
+------------------+------------------+
|
内核
监控套接字状态
有事件时发送消息
4.2、WSAAsyncSelect函数
int WSAAsyncSelect(
SOCKET s, // 套接字
HWND hWnd, // 接收消息的窗口句柄
u_int wMsg, // 自定义消息ID
long lEvent // 要监听的事件
);
// 事件标志(可组合)
#define FD_READ 0x01 // 可读
#define FD_WRITE 0x02 // 可写
#define FD_OOB 0x04 // 带外数据
#define FD_ACCEPT 0x08 // 有连接请求
#define FD_CONNECT 0x10 // 连接完成
#define FD_CLOSE 0x20 // 连接关闭
#define FD_QOS 0x40 // QoS变化
#define FD_GROUP_QOS 0x80 // 组QoS变化
// 获取事件和错误
#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
#define WSAGETSELECTERROR(lParam) HIWORD(lParam)
5、代码实现
5.1、基于WSAAsyncSelect的服务器
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
// 自定义消息
#define WM_SOCKET (WM_USER + 100)
// 最大客户端数
#define MAX_CLIENTS 256
// 客户端信息
typedef struct _ASYNC_CLIENT {
SOCKET socket;
char ip[32];
int port;
char recvBuffer[4096];
int recvLen;
char sendBuffer[4096];
int sendLen;
int sendOffset;
} ASYNC_CLIENT;
// 服务器上下文
typedef struct _ASYNC_SERVER {
HWND hWnd; // 窗口句柄
SOCKET listenSocket; // 监听套接字
ASYNC_CLIENT clients[MAX_CLIENTS];
int clientCount;
} ASYNC_SERVER;
// 全局服务器实例
ASYNC_SERVER g_Server = {0};
// 查找客户端
int FindClient(SOCKET socket) {
for (int i = 0; i < g_Server.clientCount; i++) {
if (g_Server.clients[i].socket == socket) {
return i;
}
}
return -1;
}
// 添加客户端
int AddClient(SOCKET socket, const char* ip, int port) {
if (g_Server.clientCount >= MAX_CLIENTS) {
return -1;
}
int index = g_Server.clientCount++;
ASYNC_CLIENT* client = &g_Server.clients[index];
client->socket = socket;
strncpy(client->ip, ip, sizeof(client->ip));
client->port = port;
client->recvLen = 0;
client->sendLen = 0;
client->sendOffset = 0;
printf("[+] 客户端连接: %s:%d (#%d)\n", ip, port, index);
return index;
}
// 移除客户端
void RemoveClient(int index) {
if (index < 0 || index >= g_Server.clientCount) {
return;
}
ASYNC_CLIENT* client = &g_Server.clients[index];
printf("[-] 客户端断开: %s:%d\n", client->ip, client->port);
closesocket(client->socket);
// 移动后面的元素
for (int i = index; i < g_Server.clientCount - 1; i++) {
g_Server.clients[i] = g_Server.clients[i + 1];
}
g_Server.clientCount--;
}
// 处理FD_ACCEPT事件
void OnAccept(SOCKET listenSocket) {
struct sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
SOCKET clientSocket = accept(listenSocket,
(struct sockaddr*)&clientAddr,
&addrLen);
if (clientSocket == INVALID_SOCKET) {
return;
}
char* ip = inet_ntoa(clientAddr.sin_addr);
int port = ntohs(clientAddr.sin_port);
// 为新客户端注册事件
if (WSAAsyncSelect(clientSocket, g_Server.hWnd,
WM_SOCKET,
FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR) {
closesocket(clientSocket);
return;
}
if (AddClient(clientSocket, ip, port) < 0) {
closesocket(clientSocket);
}
}
// 处理FD_READ事件
void OnRead(SOCKET socket) {
int index = FindClient(socket);
if (index < 0) return;
ASYNC_CLIENT* client = &g_Server.clients[index];
// 读取数据
int available = sizeof(client->recvBuffer) - client->recvLen - 1;
if (available <= 0) {
// 缓冲区满
client->recvLen = 0;
available = sizeof(client->recvBuffer) - 1;
}
int recvLen = recv(socket,
client->recvBuffer + client->recvLen,
available,
0);
if (recvLen <= 0) {
RemoveClient(index);
return;
}
client->recvLen += recvLen;
client->recvBuffer[client->recvLen] = '\0';
printf("[%s:%d] %s\n", client->ip, client->port, client->recvBuffer);
// 准备回显
sprintf(client->sendBuffer, "Echo: %s", client->recvBuffer);
client->sendLen = strlen(client->sendBuffer);
client->sendOffset = 0;
// 清空接收缓冲区
client->recvLen = 0;
// 触发发送(可能需要等待FD_WRITE)
OnWrite(socket);
}
// 处理FD_WRITE事件
void OnWrite(SOCKET socket) {
int index = FindClient(socket);
if (index < 0) return;
ASYNC_CLIENT* client = &g_Server.clients[index];
// 检查是否有数据要发送
int remaining = client->sendLen - client->sendOffset;
if (remaining <= 0) {
return;
}
// 发送数据
int sent = send(socket,
client->sendBuffer + client->sendOffset,
remaining,
0);
if (sent == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
RemoveClient(index);
}
return;
}
client->sendOffset += sent;
// 检查是否发送完成
if (client->sendOffset >= client->sendLen) {
client->sendLen = 0;
client->sendOffset = 0;
}
}
// 处理FD_CLOSE事件
void OnClose(SOCKET socket) {
int index = FindClient(socket);
if (index >= 0) {
RemoveClient(index);
}
}
// 窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_SOCKET: {
SOCKET socket = (SOCKET)wParam;
int event = WSAGETSELECTEVENT(lParam);
int error = WSAGETSELECTERROR(lParam);
if (error) {
printf("[-] Socket错误: %d\n", error);
int index = FindClient(socket);
if (index >= 0) {
RemoveClient(index);
}
break;
}
switch (event) {
case FD_ACCEPT:
OnAccept(socket);
break;
case FD_READ:
OnRead(socket);
break;
case FD_WRITE:
OnWrite(socket);
break;
case FD_CLOSE:
OnClose(socket);
break;
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
// 创建消息窗口
HWND CreateMessageWindow(HINSTANCE hInstance) {
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "AsyncSocketWindow";
RegisterClass(&wc);
// 创建不可见的消息窗口
HWND hWnd = CreateWindow(
wc.lpszClassName,
"AsyncSocket Server",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 300,
NULL, NULL,
hInstance,
NULL
);
return hWnd;
}
// 初始化服务器
BOOL InitServer(HINSTANCE hInstance, int port) {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
// 创建窗口
g_Server.hWnd = CreateMessageWindow(hInstance);
if (!g_Server.hWnd) {
return FALSE;
}
// 创建监听套接字
g_Server.listenSocket = socket(AF_INET, SOCK_STREAM, 0);
if (g_Server.listenSocket == INVALID_SOCKET) {
return FALSE;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(g_Server.listenSocket,
(struct sockaddr*)&addr, sizeof(addr)) != 0) {
closesocket(g_Server.listenSocket);
return FALSE;
}
if (listen(g_Server.listenSocket, SOMAXCONN) != 0) {
closesocket(g_Server.listenSocket);
return FALSE;
}
// 注册异步事件
if (WSAAsyncSelect(g_Server.listenSocket, g_Server.hWnd,
WM_SOCKET, FD_ACCEPT) == SOCKET_ERROR) {
closesocket(g_Server.listenSocket);
return FALSE;
}
printf("[+] 服务器启动,监听端口: %d\n", port);
return TRUE;
}
// 主函数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
// 分配控制台用于调试
AllocConsole();
freopen("CONOUT$", "w", stdout);
if (!InitServer(hInstance, 8888)) {
printf("服务器启动失败\n");
return 1;
}
// 显示窗口(可选)
ShowWindow(g_Server.hWnd, nCmdShow);
UpdateWindow(g_Server.hWnd);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
closesocket(g_Server.listenSocket);
WSACleanup();
return 0;
}
5.2、控制台版本(仅消息窗口)
// 无GUI的异步服务器
int main() {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
// 使用HWND_MESSAGE创建纯消息窗口
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "AsyncServerClass";
RegisterClass(&wc);
// HWND_MESSAGE表示消息专用窗口
g_Server.hWnd = CreateWindow(
wc.lpszClassName,
NULL,
0,
0, 0, 0, 0,
HWND_MESSAGE, // 消息专用窗口
NULL,
wc.hInstance,
NULL
);
if (!g_Server.hWnd) {
printf("创建消息窗口失败\n");
return 1;
}
// 初始化监听套接字...
g_Server.listenSocket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8888);
bind(g_Server.listenSocket, (struct sockaddr*)&addr, sizeof(addr));
listen(g_Server.listenSocket, SOMAXCONN);
WSAAsyncSelect(g_Server.listenSocket, g_Server.hWnd,
WM_SOCKET, FD_ACCEPT);
printf("[+] 异步服务器启动,端口: 8888\n");
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
5.3、异步客户端实现
// 异步客户端
typedef struct _ASYNC_CLIENT_CTX {
HWND hWnd;
SOCKET socket;
BOOL connected;
char recvBuffer[4096];
int recvLen;
} ASYNC_CLIENT_CTX;
ASYNC_CLIENT_CTX g_Client = {0};
#define WM_CLIENT_SOCKET (WM_USER + 101)
// 客户端窗口过程
LRESULT CALLBACK ClientWndProc(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CLIENT_SOCKET: {
int event = WSAGETSELECTEVENT(lParam);
int error = WSAGETSELECTERROR(lParam);
if (error) {
printf("[-] 连接错误: %d\n", error);
g_Client.connected = FALSE;
break;
}
switch (event) {
case FD_CONNECT:
printf("[+] 连接成功\n");
g_Client.connected = TRUE;
break;
case FD_READ: {
int len = recv(g_Client.socket,
g_Client.recvBuffer,
sizeof(g_Client.recvBuffer) - 1,
0);
if (len > 0) {
g_Client.recvBuffer[len] = '\0';
printf("[服务器] %s\n", g_Client.recvBuffer);
}
break;
}
case FD_CLOSE:
printf("[-] 服务器断开连接\n");
g_Client.connected = FALSE;
break;
}
break;
}
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
// 异步连接服务器
BOOL AsyncConnect(const char* host, int port) {
// 创建消息窗口
WNDCLASS wc = {0};
wc.lpfnWndProc = ClientWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "AsyncClientClass";
RegisterClass(&wc);
g_Client.hWnd = CreateWindow(
wc.lpszClassName, NULL, 0,
0, 0, 0, 0,
HWND_MESSAGE, NULL, wc.hInstance, NULL
);
// 创建套接字
g_Client.socket = socket(AF_INET, SOCK_STREAM, 0);
// 注册异步事件
WSAAsyncSelect(g_Client.socket, g_Client.hWnd,
WM_CLIENT_SOCKET,
FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);
// 异步连接(立即返回)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(host);
addr.sin_port = htons(port);
int result = connect(g_Client.socket,
(struct sockaddr*)&addr, sizeof(addr));
// 异步模式下connect会返回错误WSAEWOULDBLOCK
if (result == SOCKET_ERROR &&
WSAGetLastError() != WSAEWOULDBLOCK) {
return FALSE;
}
printf("[*] 正在连接 %s:%d ...\n", host, port);
return TRUE;
}
5.4、C2 Agent使用WSAAsyncSelect
// C2 Agent异步通信模块
typedef struct _C2_AGENT_ASYNC {
HWND hWnd;
SOCKET socket;
char c2Host[256];
int c2Port;
BOOL connected;
DWORD reconnectInterval;
DWORD lastReconnectTime;
// 缓冲区
char recvBuffer[8192];
int recvLen;
char sendBuffer[8192];
int sendLen;
int sendOffset;
} C2_AGENT_ASYNC;
C2_AGENT_ASYNC g_Agent = {0};
#define WM_C2_SOCKET (WM_USER + 200)
#define WM_RECONNECT (WM_USER + 201)
// C2 Agent窗口过程
LRESULT CALLBACK AgentWndProc(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_C2_SOCKET: {
int event = WSAGETSELECTEVENT(lParam);
int error = WSAGETSELECTERROR(lParam);
if (error) {
g_Agent.connected = FALSE;
// 安排重连
SetTimer(hWnd, 1, g_Agent.reconnectInterval, NULL);
break;
}
switch (event) {
case FD_CONNECT:
OnAgentConnected();
break;
case FD_READ:
OnAgentRecv();
break;
case FD_WRITE:
OnAgentSend();
break;
case FD_CLOSE:
OnAgentDisconnect();
break;
}
break;
}
case WM_TIMER:
if (wParam == 1 && !g_Agent.connected) {
KillTimer(hWnd, 1);
AgentConnect();
}
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
// 处理连接成功
void OnAgentConnected(void) {
g_Agent.connected = TRUE;
printf("[+] C2连接成功\n");
// 发送上线信息
SendCheckin();
}
// 处理接收数据
void OnAgentRecv(void) {
char buffer[4096];
int len = recv(g_Agent.socket, buffer, sizeof(buffer), 0);
if (len <= 0) {
OnAgentDisconnect();
return;
}
// 追加到接收缓冲区
if (g_Agent.recvLen + len < sizeof(g_Agent.recvBuffer)) {
memcpy(g_Agent.recvBuffer + g_Agent.recvLen, buffer, len);
g_Agent.recvLen += len;
}
// 处理完整消息
ProcessMessages();
}
// 处理断开连接
void OnAgentDisconnect(void) {
printf("[-] C2连接断开\n");
closesocket(g_Agent.socket);
g_Agent.socket = INVALID_SOCKET;
g_Agent.connected = FALSE;
// 安排重连
SetTimer(g_Agent.hWnd, 1, g_Agent.reconnectInterval, NULL);
}
// Agent连接C2
void AgentConnect(void) {
if (g_Agent.connected) return;
g_Agent.socket = socket(AF_INET, SOCK_STREAM, 0);
WSAAsyncSelect(g_Agent.socket, g_Agent.hWnd,
WM_C2_SOCKET,
FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(g_Agent.c2Host);
addr.sin_port = htons(g_Agent.c2Port);
connect(g_Agent.socket, (struct sockaddr*)&addr, sizeof(addr));
printf("[*] 正在连接C2...\n");
}
// 初始化Agent
void InitAgent(const char* c2Host, int c2Port) {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
strcpy(g_Agent.c2Host, c2Host);
g_Agent.c2Port = c2Port;
g_Agent.reconnectInterval = 30000; // 30秒重连间隔
// 创建消息窗口
WNDCLASS wc = {0};
wc.lpfnWndProc = AgentWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "C2AgentClass";
RegisterClass(&wc);
g_Agent.hWnd = CreateWindow(
wc.lpszClassName, NULL, 0,
0, 0, 0, 0,
HWND_MESSAGE, NULL, wc.hInstance, NULL
);
// 开始连接
AgentConnect();
}
// Agent主循环
void AgentRun(void) {
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
6、课后作业
-
实现GUI聊天客户端
- 使用Win32 API创建聊天界面
- 显示消息列表
- 支持发送和接收消息
- 显示在线状态
-
实现多服务器连接管理
- 同时连接多个服务器
- 为每个连接分配不同的消息ID
- 实现负载均衡或故障转移
-
添加心跳保活机制
- 使用SetTimer定时发送心跳
- 检测服务器无响应
- 实现自动重连逻辑