Windows网络编程
13、重叠IO模型
1、课程目标
- 理解重叠I/O的异步概念
- 掌握OVERLAPPED结构和完成例程
- 实现基于重叠I/O的高性能服务器
- 了解事件通知和完成例程两种模式
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| Overlapped I/O | 重叠I/O | 异步I/O操作,调用立即返回 |
| OVERLAPPED | - | 重叠操作结构体 |
| WSAOVERLAPPED | - | Winsock重叠操作结构 |
| WSASend | - | 异步发送函数 |
| WSARecv | - | 异步接收函数 |
| WSABUF | - | 数据缓冲区描述符 |
| Completion Routine | 完成例程 | I/O完成时的回调函数 |
| Alertable Wait | 可警醒等待 | 允许APC执行的等待状态 |
| APC | Asynchronous Procedure Call | 异步过程调用 |
3、使用工具
- Visual Studio 2022
- xperf/WPA(性能分析)
- Process Monitor(I/O监控)
- WinDbg(调试异步操作)
4、技术原理
4.1、重叠I/O工作原理
应用程序 内核
| |
| WSARecv(socket, &wsabuf, ..., &overlapped, callback)
|----------------------------->|
| | 记录I/O请求
|<-- 立即返回 WSA_IO_PENDING --|
| |
| 继续执行其他任务 | 异步执行I/O
| |
| SleepEx/WaitForSingleObjectEx (可警醒等待)
|----------------------------->|
| |
| 【进入可警醒状态】 | I/O完成
| | 触发完成通知
| |
|<-- 事件信号 或 APC回调 -------|
| |
| 处理完成的I/O |
4.2、OVERLAPPED结构
typedef struct _OVERLAPPED {
ULONG_PTR Internal; // 系统使用
ULONG_PTR InternalHigh; // 系统使用
union {
struct {
DWORD Offset; // 文件偏移低32位
DWORD OffsetHigh; // 文件偏移高32位
};
PVOID Pointer;
};
HANDLE hEvent; // 完成事件(事件通知模式)
} OVERLAPPED;
// WSABUF结构
typedef struct _WSABUF {
ULONG len; // 缓冲区长度
CHAR* buf; // 缓冲区指针
} WSABUF;
4.3、两种完成通知模式
// 模式1:事件通知
// 创建事件对象
WSAEVENT event = WSACreateEvent();
// 设置OVERLAPPED事件
WSAOVERLAPPED overlapped = {0};
overlapped.hEvent = event;
// 发起异步操作
WSARecv(socket, &wsabuf, 1, NULL, &flags, &overlapped, NULL);
// 等待事件
WSAWaitForMultipleEvents(1, &event, TRUE, INFINITE, FALSE);
// 获取结果
WSAGetOverlappedResult(socket, &overlapped, &bytesTransferred,
FALSE, &flags);
// 模式2:完成例程(回调函数)
void CALLBACK CompletionRoutine(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
);
// 发起异步操作(指定回调)
WSARecv(socket, &wsabuf, 1, NULL, &flags, &overlapped,
CompletionRoutine);
// 进入可警醒等待状态
SleepEx(INFINITE, TRUE); // 或 WaitForSingleObjectEx
5、代码实现
5.1、基于事件通知的重叠I/O服务器
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
#define MAX_BUFFER 4096
#define MAX_CLIENTS 64
// 每个连接的I/O上下文
typedef struct _IO_CONTEXT {
WSAOVERLAPPED overlapped;
SOCKET socket;
WSABUF wsabuf;
char buffer[MAX_BUFFER];
DWORD bytesTransferred;
DWORD flags;
int opType; // 0=recv, 1=send
char ip[32];
int port;
} IO_CONTEXT;
// 服务器上下文
typedef struct _OVERLAP_SERVER {
SOCKET listenSocket;
IO_CONTEXT clients[MAX_CLIENTS];
WSAEVENT events[MAX_CLIENTS + 1];
int clientCount;
BOOL running;
} OVERLAP_SERVER;
// 初始化I/O上下文
void InitIOContext(IO_CONTEXT* ctx, SOCKET socket,
const char* ip, int port) {
ZeroMemory(ctx, sizeof(IO_CONTEXT));
ctx->socket = socket;
ctx->wsabuf.buf = ctx->buffer;
ctx->wsabuf.len = MAX_BUFFER;
strncpy(ctx->ip, ip, sizeof(ctx->ip));
ctx->port = port;
// 创建事件
ctx->overlapped.hEvent = WSACreateEvent();
}
// 发起异步接收
BOOL PostRecv(IO_CONTEXT* ctx) {
ctx->opType = 0;
ctx->flags = 0;
ctx->wsabuf.len = MAX_BUFFER;
ZeroMemory(&ctx->overlapped, sizeof(WSAOVERLAPPED));
ctx->overlapped.hEvent = WSACreateEvent();
int result = WSARecv(
ctx->socket,
&ctx->wsabuf,
1,
&ctx->bytesTransferred,
&ctx->flags,
&ctx->overlapped,
NULL // 不使用完成例程
);
if (result == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSA_IO_PENDING) {
return FALSE;
}
}
return TRUE;
}
// 发起异步发送
BOOL PostSend(IO_CONTEXT* ctx, const char* data, int len) {
ctx->opType = 1;
memcpy(ctx->buffer, data, len);
ctx->wsabuf.len = len;
ZeroMemory(&ctx->overlapped, sizeof(WSAOVERLAPPED));
ctx->overlapped.hEvent = WSACreateEvent();
int result = WSASend(
ctx->socket,
&ctx->wsabuf,
1,
&ctx->bytesTransferred,
0,
&ctx->overlapped,
NULL
);
if (result == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSA_IO_PENDING) {
return FALSE;
}
}
return TRUE;
}
// 服务器主循环(事件通知模式)
void ServerRunEvent(OVERLAP_SERVER* server) {
while (server->running) {
// 构建事件数组
WSAEVENT events[MAX_CLIENTS + 1];
events[0] = WSACreateEvent(); // 监听用
int count = 1;
for (int i = 0; i < server->clientCount; i++) {
events[count++] = server->clients[i].overlapped.hEvent;
}
// 等待事件
DWORD result = WSAWaitForMultipleEvents(
count, events, FALSE, 1000, FALSE
);
if (result == WSA_WAIT_TIMEOUT) {
continue;
}
if (result == WSA_WAIT_FAILED) {
break;
}
int index = result - WSA_WAIT_EVENT_0;
if (index == 0) {
// 处理新连接
struct sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
SOCKET clientSocket = accept(server->listenSocket,
(struct sockaddr*)&clientAddr,
&addrLen);
if (clientSocket != INVALID_SOCKET &&
server->clientCount < MAX_CLIENTS) {
char* ip = inet_ntoa(clientAddr.sin_addr);
int port = ntohs(clientAddr.sin_port);
IO_CONTEXT* ctx = &server->clients[server->clientCount++];
InitIOContext(ctx, clientSocket, ip, port);
PostRecv(ctx);
printf("[+] 新连接: %s:%d\n", ip, port);
}
} else {
// 处理客户端I/O完成
int clientIndex = index - 1;
IO_CONTEXT* ctx = &server->clients[clientIndex];
DWORD bytesTransferred, flags;
BOOL success = WSAGetOverlappedResult(
ctx->socket,
&ctx->overlapped,
&bytesTransferred,
FALSE,
&flags
);
WSAResetEvent(ctx->overlapped.hEvent);
if (!success || bytesTransferred == 0) {
// 连接断开
printf("[-] 断开: %s:%d\n", ctx->ip, ctx->port);
closesocket(ctx->socket);
WSACloseEvent(ctx->overlapped.hEvent);
// 移除客户端
for (int i = clientIndex; i < server->clientCount - 1; i++) {
server->clients[i] = server->clients[i + 1];
}
server->clientCount--;
continue;
}
if (ctx->opType == 0) {
// 接收完成
ctx->buffer[bytesTransferred] = '\0';
printf("[%s:%d] %s\n", ctx->ip, ctx->port, ctx->buffer);
// 回显
char response[MAX_BUFFER];
sprintf(response, "Echo: %s", ctx->buffer);
PostSend(ctx, response, strlen(response));
} else {
// 发送完成,继续接收
PostRecv(ctx);
}
}
}
}
5.2、基于完成例程的重叠I/O服务器
// 扩展I/O上下文(包含自引用)
typedef struct _IO_CONTEXT_EX {
WSAOVERLAPPED overlapped;
SOCKET socket;
WSABUF wsabuf;
char buffer[MAX_BUFFER];
int opType;
char ip[32];
int port;
struct _OVERLAP_SERVER_EX* server; // 反向引用
} IO_CONTEXT_EX;
typedef struct _OVERLAP_SERVER_EX {
SOCKET listenSocket;
IO_CONTEXT_EX* clients[MAX_CLIENTS];
int clientCount;
BOOL running;
CRITICAL_SECTION cs;
} OVERLAP_SERVER_EX;
// 完成例程
void CALLBACK RecvCompletion(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
) {
// 获取上下文(OVERLAPPED是结构体第一个成员)
IO_CONTEXT_EX* ctx = (IO_CONTEXT_EX*)lpOverlapped;
if (dwError != 0 || cbTransferred == 0) {
printf("[-] 断开: %s:%d\n", ctx->ip, ctx->port);
closesocket(ctx->socket);
// 从服务器移除(需要加锁)
OVERLAP_SERVER_EX* server = ctx->server;
EnterCriticalSection(&server->cs);
for (int i = 0; i < server->clientCount; i++) {
if (server->clients[i] == ctx) {
server->clients[i] = server->clients[--server->clientCount];
break;
}
}
LeaveCriticalSection(&server->cs);
HeapFree(GetProcessHeap(), 0, ctx);
return;
}
ctx->buffer[cbTransferred] = '\0';
printf("[%s:%d] %s\n", ctx->ip, ctx->port, ctx->buffer);
// 回显
char response[MAX_BUFFER];
int len = sprintf(response, "Echo: %s", ctx->buffer);
ctx->opType = 1;
memcpy(ctx->buffer, response, len);
ctx->wsabuf.len = len;
ZeroMemory(&ctx->overlapped, sizeof(WSAOVERLAPPED));
WSASend(ctx->socket, &ctx->wsabuf, 1, NULL, 0,
&ctx->overlapped, SendCompletion);
}
void CALLBACK SendCompletion(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
) {
IO_CONTEXT_EX* ctx = (IO_CONTEXT_EX*)lpOverlapped;
if (dwError != 0) {
// 发送失败,关闭连接
closesocket(ctx->socket);
HeapFree(GetProcessHeap(), 0, ctx);
return;
}
// 发送完成,继续接收
ctx->opType = 0;
ctx->wsabuf.len = MAX_BUFFER;
DWORD flags = 0;
ZeroMemory(&ctx->overlapped, sizeof(WSAOVERLAPPED));
WSARecv(ctx->socket, &ctx->wsabuf, 1, NULL, &flags,
&ctx->overlapped, RecvCompletion);
}
// 接受连接线程
DWORD WINAPI AcceptThread(LPVOID param) {
OVERLAP_SERVER_EX* server = (OVERLAP_SERVER_EX*)param;
while (server->running) {
struct sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
SOCKET clientSocket = accept(server->listenSocket,
(struct sockaddr*)&clientAddr,
&addrLen);
if (clientSocket == INVALID_SOCKET) {
continue;
}
// 创建I/O上下文
IO_CONTEXT_EX* ctx = (IO_CONTEXT_EX*)HeapAlloc(
GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IO_CONTEXT_EX)
);
ctx->socket = clientSocket;
ctx->wsabuf.buf = ctx->buffer;
ctx->wsabuf.len = MAX_BUFFER;
strcpy(ctx->ip, inet_ntoa(clientAddr.sin_addr));
ctx->port = ntohs(clientAddr.sin_port);
ctx->server = server;
// 添加到服务器
EnterCriticalSection(&server->cs);
if (server->clientCount < MAX_CLIENTS) {
server->clients[server->clientCount++] = ctx;
}
LeaveCriticalSection(&server->cs);
printf("[+] 新连接: %s:%d\n", ctx->ip, ctx->port);
// 发起第一个异步接收
DWORD flags = 0;
WSARecv(clientSocket, &ctx->wsabuf, 1, NULL, &flags,
&ctx->overlapped, RecvCompletion);
}
return 0;
}
// 主线程(可警醒等待)
void ServerRunCompletion(OVERLAP_SERVER_EX* server) {
// 启动接受线程
HANDLE acceptThread = CreateThread(
NULL, 0, AcceptThread, server, 0, NULL
);
// 主线程保持可警醒状态
while (server->running) {
// SleepEx使线程进入可警醒状态
// 当有I/O完成时,会调用完成例程
SleepEx(100, TRUE); // TRUE表示可警醒
}
WaitForSingleObject(acceptThread, INFINITE);
CloseHandle(acceptThread);
}
// 初始化服务器
BOOL InitServerEx(OVERLAP_SERVER_EX* server, int port) {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
InitializeCriticalSection(&server->cs);
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(port);
bind(server->listenSocket, (struct sockaddr*)&addr, sizeof(addr));
listen(server->listenSocket, SOMAXCONN);
server->clientCount = 0;
server->running = TRUE;
printf("[+] 完成例程服务器启动,端口: %d\n", port);
return TRUE;
}
5.3、C2 Agent重叠I/O通信
// Agent异步通信上下文
typedef struct _AGENT_IO_CONTEXT {
WSAOVERLAPPED overlapped;
SOCKET socket;
WSABUF wsabuf;
char buffer[8192];
int opType;
BOOL connected;
} AGENT_IO_CONTEXT;
AGENT_IO_CONTEXT g_AgentIO = {0};
// Agent接收完成例程
void CALLBACK AgentRecvCompletion(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
) {
AGENT_IO_CONTEXT* ctx = (AGENT_IO_CONTEXT*)lpOverlapped;
if (dwError != 0 || cbTransferred == 0) {
printf("[-] C2连接断开\n");
ctx->connected = FALSE;
return;
}
// 处理接收到的命令
ProcessC2Command(ctx->buffer, cbTransferred);
// 继续接收
PostAgentRecv(ctx);
}
// Agent发送完成例程
void CALLBACK AgentSendCompletion(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
) {
// 发送完成,可以继续发送队列中的数据
printf("[*] 发送完成: %d bytes\n", cbTransferred);
}
// 发起接收
void PostAgentRecv(AGENT_IO_CONTEXT* ctx) {
ctx->opType = 0;
ctx->wsabuf.buf = ctx->buffer;
ctx->wsabuf.len = sizeof(ctx->buffer);
DWORD flags = 0;
ZeroMemory(&ctx->overlapped, sizeof(WSAOVERLAPPED));
WSARecv(ctx->socket, &ctx->wsabuf, 1, NULL, &flags,
&ctx->overlapped, AgentRecvCompletion);
}
// 异步发送数据
void AgentAsyncSend(const void* data, int len) {
AGENT_IO_CONTEXT* ctx = &g_AgentIO;
memcpy(ctx->buffer, data, len);
ctx->wsabuf.buf = ctx->buffer;
ctx->wsabuf.len = len;
ctx->opType = 1;
ZeroMemory(&ctx->overlapped, sizeof(WSAOVERLAPPED));
WSASend(ctx->socket, &ctx->wsabuf, 1, NULL, 0,
&ctx->overlapped, AgentSendCompletion);
}
// Agent主循环
void AgentMainLoop(const char* c2Host, int c2Port) {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
while (TRUE) {
// 连接C2
g_AgentIO.socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(c2Host);
addr.sin_port = htons(c2Port);
if (connect(g_AgentIO.socket,
(struct sockaddr*)&addr, sizeof(addr)) == 0) {
g_AgentIO.connected = TRUE;
printf("[+] C2连接成功\n");
// 发起第一个接收
PostAgentRecv(&g_AgentIO);
// 保持可警醒状态
while (g_AgentIO.connected) {
SleepEx(1000, TRUE);
}
}
closesocket(g_AgentIO.socket);
// 重连延迟
Sleep(30000);
}
}
6、课后作业
-
实现文件异步传输
- 使用重叠I/O读写文件
- 同时处理网络和文件I/O
- 实现大文件分片传输
-
实现多缓冲区接收
- 使用WSARecv的多缓冲区功能
- 实现零拷贝接收
- 优化内存使用
-
性能对比测试
- 对比同步/异步I/O性能
- 测试不同负载下的吞吐量
- 分析CPU和内存使用