异步IO
Windows上使用的高效异步IO模型。
异步:调用函数后会立刻返回,但当函数返回时,当前IO操作并没有完成,只是投递了一个请求(投递到 IOCP中),IOCP会建立连接(创建一个线程),来通知完成的结果。
- 连接的建立:AcceptEx、ConnectEx
- 连接的断开:closesocket(同步)、DisconnectEx(异步)
- 数据接收:WSARecv
- 数据发送:WSASend
完成端口
- 完成:应用程序向系统发起一个 IO 操作,系统会在操作结束后将IO操作完成的结果通知应用程序。
- 端口:抽象概念,是一种机制。
重叠IO
Overlapped,针对一个socket可以发起多个 IO 操作,无需等待上一个 IO 完成。
(尽管调用 IO 操作是按顺序的,但 IO 操作完成通知是随机的无序的)
编程
//创建重叠socket
SOCKET listensock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
bind(...);
listen(...);
//创建IOCP
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
//绑定
CreateIoCompletionPort((HANDLE)listensock, iocp, NULL, 0);
CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads):
- FileHandle:创建的重叠socket
- ExistingCompletionPort:创建的iocp
- CompletionKey:投递请求时作为参数投递到内核,用户层调用GetQueuedCompletionStatus时又会得到CompletionKey,根据该参数可以跟具体的io请求进行关联,判断到底是那一次投递的io请求,重叠结构也是起类似的作用。
- NumberOfConcurrentThreads:系统允许在完成端口上并发处理IO完成包的最大线程数量。
enum class IO_OP_TYPE{
IO_ACCEPT,
IO_SEND,
IO_RECV,
IO_CONNECT,
IO_DISCONNECT
};
//创建重叠结构
typedef struct OverlappedPerIO{
OVERLAPPED overlapped; //与OverlappedPerIO起始地址是一样的,只要类型转换就可以获取到
SOCKET socket;
WSABUF wsaBuf;
IO_OP_TYPE type;
char buffer[BUFFER_SIZE];
};
{
SOCKET sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
OverlappedPerIO overlap = new OverlappedPerIO;
ZeroMemory(overlap, sizeof(OverlappedPerIO);
overlap->socket = sock;
overlap->wsaBuf.buf = overlap->buffer;
overlap->type = IO_OP_TYPE::IO_ACCEPT;
DWORD dwByteRecv = 0;
//通常要投递多个请求
for(;;){
AcceptEx(listensock, sock, overlap->wsaBuf.buf, 0,
sizeof(SOCKADDR_IN) + 16,
sizeof(SOCKADDR_IN) + 16,
&dwByteRecv,
(LPOVERLAPPED)overlap
);
}
//创建线程,线程函数中调用GetQueuedCompletionStatus函数
while(true){
//不断接收完成队列中的完成通知
GetQueuedCompletionStatus(...);
switch(overlp->type){
case IO_ACCEPT:
setsockopt(...);
//重新设置overlap的type等
...
CreateIoCompletionPort((HANDLE)listensock, iocp, NULL, 0);
WSARecv(...);//投递接收消息的请求
break;
case IO_RECV:
//取出读到的数据
cout << overlap->buffer << endl;
WSASend(...);//投递发送消息的请求
break;
}
}
//清理
}