Windows下异步I/O机制IOCP介绍

admin 9917次浏览

摘要:输入输出完成端口(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

相关文章
友情链接