概述
什么是完成端口
Windows
的I/O
完成端口(I/O Completion Ports
,IOCP
)是一种高效的I/O
复用模型- 广泛用于构建高性能的网络服务器和其他需要处理大量并发
I/O
操作的应用程序
- 广泛用于构建高性能的网络服务器和其他需要处理大量并发
- 完成端口(
Completion Port
)是Windows
内核提供的一种系统资源,就像文件句柄、套接字一样 - 调用
CreateIoCompletionPort
函数时,本质上是在向操作系统申请一个完成端口的资源,用于管理异步I/O
操作的完成通知
原理
IOCP
是基于Windows
的异步I/O
模型,旨在通过减少线程上下文切换和锁竞争,提高高并发环境下的系统性能- 其核心思想是使用完成端口(
Completion Port
)来管理和分发I/O
操作的完成通知
核心组件
- 完成端口
- 用于管理多个异步
I/O
请求,并将完成的I/O
操作通知工作线程
- 用于管理多个异步
- 工作线程
- 执行异步
I/O
操作的回调处理
- 执行异步
- 重叠结构
- 用于表示异步
I/O
操作的状态和结果
- 用于表示异步
工作原理
- 创建完成端口
- 当调用
CreateIoCompletionPort
创建完成端口时,操作系统会分配一个完成端口对象,并为其创建一个内部队列,用于存储完成的I/O
操作通知
- 当调用
1 |
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); |
- 关联文件或套接字句柄
- 当你将文件句柄或套接字句柄与完成端口关联时,操作系统会记录这个关联,并在该句柄上进行的异步
I/O
操作时,使用这个完成端口进行通知
- 当你将文件句柄或套接字句柄与完成端口关联时,操作系统会记录这个关联,并在该句柄上进行的异步
1 2 |
HANDLE hFile = CreateFile(...); HANDLE hCompletionPort = CreateIoCompletionPort(hFile, hCompletionPort, (ULONG_PTR)hFile, 0); |
- 启动异步
I/O
操作- 使用异步
I/O
函数(如ReadFile
、WriteFile
或WSARecv
、WSASend
等)启动I/O
操作,并传入重叠结构(OVERLAPPED
结构)
- 使用异步
1 2 3 4 5 6 |
char buffer[1024]; OVERLAPPED ol = {0}; BOOL result = ReadFile(hFile, buffer, sizeof(buffer), NULL, &ol); if (!result && GetLastError() != ERROR_IO_PENDING) { // 处理错误 } |
- 操作系统管理和监控
- 操作系统内部会监控这些异步
I/O
操作 - 当
I/O
操作完成或出现错误时,操作系统会将完成信息(包括完成键、传输的字节数和重叠结构指针)放入完成端口的队列中
- 操作系统内部会监控这些异步
- 工作线程处理完成通知
- 在你的应用程序中,工作线程会调用
GetQueuedCompletionStatus
函数,从完成端口队列中获取完成的I/O
操作通知 - 这个函数会阻塞,直到有完成的
I/O
操作可供处理
- 在你的应用程序中,工作线程会调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
DWORD bytesTransferred; ULONG_PTR completionKey; LPOVERLAPPED pOverlapped; BOOL result = GetQueuedCompletionStatus( hCompletionPort, &bytesTransferred, &completionKey, &pOverlapped, INFINITE ); if (result) { // 处理完成的 I/O 操作 } else { // 处理错误 } |
注意
完成键
- 完成键是你在调用
CreateIoCompletionPort
时与句柄关联的一个值 - 这个值在
I/O
操作完成时被传递给GetQueuedCompletionStatus
,可以用来识别不同的I/O
操作源
1 2 3 |
HANDLE hFile = CreateFile(...); ULONG_PTR completionKey = (ULONG_PTR)hFile; // 使用文件句柄作为完成键 CreateIoCompletionPort(hFile, hCompletionPort, completionKey, 0); |
重叠结构
- 每个异步
I/O
操作需要一个OVERLAPPED
结构。这个结构不能被重用,直到操作完成- 确保正确管理
OVERLAPPED
结构的生命周期
- 确保正确管理
1 2 3 4 5 6 |
OVERLAPPED ol = {0}; if (!ReadFile(hFile, buffer, sizeof(buffer), NULL, &ol)) { if (GetLastError() != ERROR_IO_PENDING) { std::cerr << "ReadFile failed: " << GetLastError() << std::endl; } } |
线程池和并发性
IOCP
可以自动调节并发性,通过CreateIoCompletionPort
的最后一个参数指定并发线程数- 这个参数通常设为
0
,让系统自动选择合理的值
1 |
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); |
- 创建的工作线程数应该适应系统的并发能力,通常与
CPU
核心数相关
错误处理
- 处理
I/O
操作的错误非常重要- 在调用异步
I/O
操作后,检查返回值和GetLastError
- 在
GetQueuedCompletionStatus
返回FALSE
时,同样需要检查错误码
- 在调用异步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
DWORD bytesTransferred; ULONG_PTR completionKey; LPOVERLAPPED pOverlapped; BOOL result = GetQueuedCompletionStatus( hCompletionPort, &bytesTransferred, &completionKey, &pOverlapped, INFINITE ); if (!result) { DWORD error = GetLastError(); if (pOverlapped == NULL) { // 错误发生在调用 GetQueuedCompletionStatus 之前 } else { // 错误发生在 I/O 操作完成时 } } |
超时和取消
GetQueuedCompletionStatus
可以设置超时参数,通过设定合理的超时值,避免线程永久阻塞- 此外,使用
CancelIo
或CancelIoEx
取消未完成的I/O
操作
1 2 3 4 5 6 7 8 9 10 11 |
BOOL result = GetQueuedCompletionStatus( hCompletionPort, &bytesTransferred, &completionKey, &pOverlapped, 5000 // 设置超时时间为5000毫秒 ); if (!result && GetLastError() == WAIT_TIMEOUT) { std::cerr << "I/O operation timed out." << std::endl; } |
处理半关闭状态
- 对于网络套接字,处理半关闭(
half-closed
)状态时需要特别注意- 确保正确处理读写操作的结束情况
资源清理
- 确保正确关闭句柄和完成端口,以避免资源泄漏
1 2 |
CloseHandle(hFile); CloseHandle(hCompletionPort); |
队列深度和负载均衡
- 根据负载情况调整队列深度和并发线程数
- 过多的线程和未完成的
I/O
操作可能导致系统性能下降- 通过监控和调优,确保系统在高效工作
PostQueuedCompletionStatus
- 可以使用
PostQueuedCompletionStatus
手动向完成端口发送自定义的完成包,用于通知工作线程执行非I/O
任务
1 |
PostQueuedCompletionStatus(hCompletionPort, 0, completionKey, NULL); |
异步 I/O 与同步 I/O 的混合使用
- 在某些情况下,可以将异步
I/O
和同步I/O
混合使用,灵活应对不同的I/O
需求
内核对象
文件句柄
- 任何支持重叠
I/O
操作的文件句柄都可以与IOCP
关联 - 这包括标准文件、设备文件等。通过
ReadFile
和WriteFile
等函数,可以对这些文件句柄执行异步I/O
操作
套接字
- 套接字是最常用的与
IOCP
一起工作的内核对象之一 Windows Sockets API
(Winsock
)支持重叠I/O
操作,允许通过WSARecv
和WSASend
等函数执行异步网络I/O
操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
SOCKET s = socket(AF_INET, SOCK_STREAM, 0); HANDLE hCompletionPort = CreateIoCompletionPort((HANDLE)s, NULL, 0, 0); OVERLAPPED ol = {0}; WSABUF buffer; char data[1024]; buffer.buf = data; buffer.len = sizeof(data); DWORD bytesReceived; DWORD flags = 0; if (WSARecv(s, &buffer, 1, &bytesReceived, &flags, &ol, NULL) == SOCKET_ERROR) { if (WSAGetLastError() != WSA_IO_PENDING) { std::cerr << "WSARecv failed: " << WSAGetLastError() << std::endl; } } |
管道
- 命名管道(
Named Pipes
)和匿名管道(Anonymous Pipes
)也可以与IOCP
一起使用 - 通过重叠
I/O
操作,可以高效地管理跨进程通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
HANDLE hPipe = CreateNamedPipe( "\\\\.\\pipe\\mypipe", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024 * 16, 1024 * 16, 0, NULL ); HANDLE hCompletionPort = CreateIoCompletionPort(hPipe, NULL, 0, 0); OVERLAPPED ol = {0}; char buffer[1024]; DWORD bytesRead; if (!ReadFile(hPipe, buffer, sizeof(buffer), NULL, &ol)) { if (GetLastError() != ERROR_IO_PENDING) { std::cerr << "ReadFile failed: " << GetLastError() << std::endl; } } |
串口
- 串口设备支持重叠
I/O
操作,允许与IOCP
结合使用以实现高效的串口通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
HANDLE hSerial = CreateFile( "COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ); HANDLE hCompletionPort = CreateIoCompletionPort(hSerial, NULL, 0, 0); OVERLAPPED ol = {0}; char buffer[1024]; DWORD bytesRead; if (!ReadFile(hSerial, buffer, sizeof(buffer), NULL, &ol)) { if (GetLastError() != ERROR_IO_PENDING) { std::cerr << "ReadFile failed: " << GetLastError() << std::endl; } } |
邮件槽
- 邮件槽是一种简单的消息传递机制,支持重叠 I/O 操作,可以与 IOCP 一起使用
其他支持重叠IO的设备
- 任何支持重叠 I/O 操作的设备句柄都可以与 IOCP 关联。这包括一些特殊设备和驱动程序
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
#include <windows.h> #include <iostream> DWORD WINAPI WorkerThread(LPVOID lpParam) { HANDLE hCompletionPort = (HANDLE)lpParam; DWORD bytesTransferred; ULONG_PTR completionKey; LPOVERLAPPED pOverlapped; while (true) { BOOL result = GetQueuedCompletionStatus( hCompletionPort, &bytesTransferred, &completionKey, &pOverlapped, INFINITE ); if (result) { // 处理完成的 I/O 操作 std::cout << "I/O completed: " << bytesTransferred << " bytes transferred." << std::endl; } else { // 处理错误 std::cerr << "GetQueuedCompletionStatus failed: " << GetLastError() << std::endl; } } return 0; } int main() { // 创建完成端口 HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (hCompletionPort == NULL) { std::cerr << "CreateIoCompletionPort failed: " << GetLastError() << std::endl; return 1; } // 创建文件 HANDLE hFile = CreateFile( "example.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL ); if (hFile == INVALID_HANDLE_VALUE) { std::cerr << "CreateFile failed: " << GetLastError() << std::endl; return 1; } // 将文件句柄与完成端口关联 if (CreateIoCompletionPort(hFile, hCompletionPort, (ULONG_PTR)hFile, 0) == NULL) { std::cerr << "CreateIoCompletionPort failed: " << GetLastError() << std::endl; return 1; } // 启动异步 I/O 操作 char buffer[1024] = {0}; OVERLAPPED ol = {0}; if (!ReadFile(hFile, buffer, sizeof(buffer), NULL, &ol)) { if (GetLastError() != ERROR_IO_PENDING) { std::cerr << "ReadFile failed: " << GetLastError() << std::endl; return 1; } } // 创建工作线程 HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hCompletionPort, 0, NULL); if (hThread == NULL) { std::cerr << "CreateThread failed: " << GetLastError() << std::endl; return 1; } // 等待工作线程完成 WaitForSingleObject(hThread, INFINITE); // 清理 CloseHandle(hFile); CloseHandle(hCompletionPort); return 0; } |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ WindowsETW进程监控相关03/17
- ♥ Soui八06/20
- ♥ X86_64汇编学习记述三08/08
- ♥ 关于异常的捕获和dump文件的生成07/05
- ♥ Dump分析:重复释放堆内存,死锁03/17
- ♥ Windbg:命令实践详解一03/27