(叠甲时间:IOCP 作为 Windows 下一款非常高效的异步IO模型,它有多高效就有多复杂。本文在这里旨在起到一个抛砖引玉的作用,希望大家可以倾情指出不足。一方面为了基础不好的兄弟也能良好的了解 IOCP ,另一方面作者本人基础也不太好,所以本文会尽量的浅。)
IOCP的简单介绍
异步IO: 讲IOCP就肯定要先讲这个啊。在平时我们用到的基本都是同步IO操作,也就是我们在进行读写的时候,会直接阻塞住,直到我们的读写完成。以普通的recv为例,如果对应的套接字一直没发消息,我们就一直阻塞在这里一直等,肥肠难受。这时候异步IO应运而生,从此解放双手,其实是解放当前线程,我们的IO操作不再需要阻塞,而是异步的进行,再做好了之后会通知我们去直接拿到操作的结果。
IOCP: 全称 Input/Output Completion Ports。也就是输入输出的完成端口。顾名思义,它主要是在异步IO的基础上,为我们提供了一个获取已经完成的IO操作的端口。其实它的功能和消息队列是类似的,后台完成IO操作之后会把完成的结果置入这个队列中,我们可以从这个队列中直接获取结果。
IOCP的相关函数
CreateIoCompletionPort: 用于创建IOCP句柄,和将文件描述符绑定到现有的IOCP上面。两种用法主要靠填的参数情况不同进行区分。
1.当创建IOCP的句柄时:
HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
此时前三个参数时固定的。第四个参数的含义为可以访问此端口的线程数量,暂时不需要深究。反正就是IOCP后台会根据这个值控制可以访问本端口的数量。
2.当要将文件描述符绑定到现有的IOCP上面时:
CreateIoCompletionPort(hFile, hCompletionPort, NULL, 0);
第一个参数(hFile):为需要绑定的文件描述符。(文件描述符:包括但不限于套接字,其他的支持重叠IO的文件描述符也可以在这里绑定)
第二个参数(hCompletionPort):为已经创建完成的完成端口句柄。若为NULL,则会创建一个新的句柄,并且同样会将 hFIle 绑定上去。
第三个参数:大家在网上可能找到很多说法,能看懂的也不用看我说。看不懂的你就信我的,你就把它当个参数用。这个参数在你从完成端口里获取到这个文件描述符的时候,会作为传出参数获得到。下文会提到。
第四个参数:在非创建完成端口时,此参数无作用。
GetQueuedCompletionStatus: 此函数用于从完成端口中获取完成事件。
BOOLI GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytes,
PULONG_PTR lpCompletionKey,
LPOVERLAPPED* lpOverlapped,
DWORD dwMilliseconds
);
第一个参数(CompletionPort):传入参数,需要获取的完成端口句柄。
第二个参数(lpNumberOfBytes):传出参数,内容为本次IO操作中传输或接收了多少字节。类似于传统的 send 和 recv 函数的返回值。
第三个参数(lpCompletionKey):传出参数,上文提到的在绑定文件描述符的时候传入的参数,可以根据实际情况灵活运用。
第四个参数(lpOverlapped):传出参数,是在 调用 WSARecv 和 WSASend 等异步操作时传入的重叠结构,也可以灵活运用。
第五个参数(dwMilliseconds):传入参数,本次获取事件的超时时间。
WSARecv: 提前或即时的对某个文件描述符的进行读操作。
int WSARecv(
SOCKET socket,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
第一个参数(socket):传入参数,要读的套接字。
第二个参数(lpBuffers):传出参数,一个关于 WSABUF 的缓冲区数组,用于存储读到的数据。
第三个参数(dwBufferCount):上述数组的缓冲区数量 。
第四个参数(lpNumberOfBytesRecvd):传出参数,可以理解为它和第六个参数(lpOverlapped)两个是互斥的。只能存在一个,对应两种模式。此参数会得到接收到的字节数。
第五个参数(lpFlags):用于修改 WSARecv 函数调用行为的标志,这里不细说了,我也不太了解。
第六个参数(lpOverlapped):传入参数,当I/O操作完成时,我们可以通过通过这个结构来保存和传递相关的完成信息,就是也可以传参数用。
第七个参数(lpCompletionRoutine):传入参数,简单来说就是回调函数的指针,在IO操作完成后会执行这个函数,这里不做更深入的解释。
(值得一提的是,当后两个参数都为 NULL 时,此函数与普通的 recv 无异。)
WSASend:就是发送用的。
参数的作用与 WSARecv 相似,只是缓冲区变成了写入要用的缓冲区,这里不做赘述了。
IOCP基本流程:
大概就是如下这样:
1.创建一个 IOCP 句柄
2.将套接字绑定到IOCP上面,调用 WSARecv 绑定读事件
3.需要发消息的时候调用 WSASend
4.起一个(或者若干个)工作线程循环调用 GetQueuedCompletionStatus 拿到处理完的事件,并且进行自己需要的处理