摘要:输入输出完成端口(Input/Output Completion Port,IOCP), 是Windows下支持多个同时发生的异步I/O操作的应用程序编程接口,用于解决网络编程中的连接
输入输出完成端口(Input/Output Completion Port,IOCP), 是Windows下支持多个同时发生的异步I/O操作的应用程序编程接口,用于解决网络编程中的连接建立、连接断开、数据发送和数据接收四个问题。
下面从几个问题来解释iocp
1. 常见的网络I/O模型
阻塞I/O(Blocking I/O):在执行I/O操作时线程会被阻塞,直到操作完成。非阻塞I/O(Non-blocking I/O):执行I/O操作时如果io未就绪则立即返回,线程不会被阻塞。I/O多路复用(I/O Multiplexing):这种模型使用select、poll或epoll等系统调用,允许应用程序同时监控多个文件描述符,以查看是否有数据可读或可写。
I/O多路复用经常与reactor模式结合,使用io多路复用,负责解决检测多路io是否就绪的问题,io可操作后,通知用户态,由用户态执行对应的io操作,从而解耦io检测和io操作。
信号驱动I/O(Signal-driven I/O)应用程序通过注册信号处理函数来响应I/O事件。当数据准备好时,内核会发送一个信号通知应用程序。异步I/O(Asynchronous I/O):应用程序发起I/O操作后,不等待执行结果,I/O检测和操作都由内核完成,当I/O操作完成时,内核会通知应用程序。IOCP是一种异步I/O机制。
2. IOCP基本流程
IOCP工作流程与Linux下的io_uring类似,创建IOCP后,只投递请求,异步地获取结果
socket绑定到IOCP中投递具体I/O操作请求io检测和io操作在iocp中执行完成后,通知用户态操作结果
3. IOCP的基本接口:
CreateIoCompletionPort
CreateIoCompletionPort 函数用于创建一个新的 I/O 完成端口或者将一个文件句柄与一个现有的 I/O 完成端口关联。
HANDLE CreateIoCompletionPort(
[in] HANDLE FileHandle,
[in, optional] HANDLE ExistingCompletionPort,
[in] ULONG_PTR CompletionKey,
[in] DWORD NumberOfConcurrentThreads
);
AcceptEx/WSArecv/WSAsend/ConnectEx
向iocp投递对应的异步请求,io具体操作与Linux下Posix API类似,recv操作不可多次投递,必须一次接收完成后,再进行下一次投递,因为在取出完成队列时会存在多线程问题,对于connect/accept/send可多次投递
BOOL AcceptEx(
[in] SOCKET sListenSocket,
[in] SOCKET sAcceptSocket,
[in] PVOID lpOutputBuffer,
[in] DWORD dwReceiveDataLength,
[in] DWORD dwLocalAddressLength,
[in] DWORD dwRemoteAddressLength,
[out] LPDWORD lpdwBytesReceived,
[in] LPOVERLAPPED lpOverlapped
);
BOOL ConnectEx(
[in] SOCKET s,
[in] const sockaddr *name,
[in] int namelen,
[in, optional] PVOID lpSendBuffer,
[in] DWORD dwSendDataLength,
[out] LPDWORD lpdwBytesSent,
[in] LPOVERLAPPED lpOverlapped
);
int WSAAPI WSARecv(
[in] SOCKET s,
[in, out] LPWSABUF lpBuffers,
[in] DWORD dwBufferCount,
[out] LPDWORD lpNumberOfBytesRecvd,
[in, out] LPDWORD lpFlags,
[in] LPWSAOVERLAPPED lpOverlapped,
[in] LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
int WSAAPI WSASend(
[in] SOCKET s,
[in] LPWSABUF lpBuffers,
[in] DWORD dwBufferCount,
[out] LPDWORD lpNumberOfBytesSent,
[in] DWORD dwFlags,
[in] LPWSAOVERLAPPED lpOverlapped,
[in] LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
GetQueuedCompletionStatus
GetQueuedCompletionStatus 函数用于从 I/O 完成端口检索完成的 I/O 操作的状态。线程首次调用某个iocp的接口时,该线程会与iocp进行绑定,从而将iocp的完成队列中的元素取出,每次接口返回一个通知
BOOL GetQueuedCompletionStatus(
[in] HANDLE CompletionPort,
LPDWORD lpNumberOfBytesTransferred,
[out] PULONG_PTR lpCompletionKey,
[out] LPOVERLAPPED *lpOverlapped,
[in] DWORD dwMilliseconds
);
4. 重叠I/O:
无需等待上一个io操作完成就能够投递下一个操作请求,多个io操作的请求投递能够堆叠在一起,实现性能的提升,重叠I/O在IOCP中定义为LPOVERLAPPED,结构体定义如下:
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
LPOVERLAPPED除了能够表示I/O堆叠的异步操作,还能够用于I/O操作完成后返回的元素与投递的请求相关联。
5. 返回结果与投递请求的关联:
完成队列中返回元素需要与投递的请求相关联,用来确定是哪个事件返回,以Linux中epoll为例,通常步骤是定义epoll_event ev,使用ev.data.fd来确定fd,也可以使用用户态传入的指针ev.data.ptr来确定,同样传入epoll的和返回的ptr一致;在io_uring中,使用的是io_uring_sqe中的user_data字段;在IOCP中,返回结果与投递的请求是通过完成键(Completion Key)和重叠结构(Overlapped Structure)相关联的。
使用LPOVERLAPPED做关联时,需要用户态自定义一个新的结构体传入,结构体中前两个字段需要为OVERLAPPED和SOCKET类型,因为内核会对其进行修改,其他字段可自定义,包括io操作类型,缓冲区等,以此对返回的事件进行区分。
最后,推荐一个Linux c/c++内容学习平台
https://github.com/0voice
