什么是IOCP完成端口?
我们可以把IOCP完成端口(以下统称IOCP)理解为Windows下性能最高的网络编程技术,本文讲解该技术的基本知识,以及如何使用该技术开发高性能的网络软件。 IOCP本质上就是Windows内核提供的一种请求队列+通知队列,我们把各种耗时的网络操作请求投递到请求队列,IOCP具体怎么去完成这些网络操作我们不管,IOCP完成后会把结果放到通知队列里,我们就去通知队列里获取结果然后处理。
什么是同步和异步?
同步就是,你跟朋友聊天,你发完一句话后,一直盯着这个聊天窗口等待朋友回复,这样你就只能同时跟一个朋友聊天。如果有10个朋友同时找你,要么你就只能让后面9个朋友排队等着,等你跟第一个朋友聊完了再聊下一个,要么就需要再请9个人帮你聊天。 异步就是,你跟朋友聊天,你发完一句话后,就去做其他事情,等听到消息通知后,再去回复朋友。这样即使10个朋友同时找你,你一个人也能聊得过来。所以异步的效率是不是比同步高很多? 我们最开始接触网络编程时,一般都是用同步模式。比如我要发起TCP连接,就会调用connect函数,调用过程中会阻塞等待,直到连接成功或者失败才会返回,然后判断函数的返回值。这就是同步模式,最简单但是效率也最低。 IOCP是一种异步模式,我调用connect函数后,调用会马上返回而且没有结果。我就可以先去执行其他任务,直到我得到通知说connect请求已经有结果了,我才去处理。
IOCP的使用步骤
虽然要完全掌握IOCP比较难,但它的使用步骤其实非常简单,关键就这么几步:创建IOCP,关联SOCKET,投递网络操作请求,获取IO通知。我们以发起TCP连接为例,详细讲解这些步骤。
1.创建IOCP
我们先使用WSAStartup启动套接字库。
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
然后我们创建一个IOCP实例,后续的操作将使用这个实例的句柄。
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
2.关联SOCKET
进行网络操作肯定要先创建SOCKET,但跟平时不同的是,我们需要创建一个支持异步模式的特殊SOCKET,即给SOCKET加上重叠标志WSA_FLAG_OVERLAPPED。
SOCKET socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
然后我们把这个SOCKET关联到IOCP中去,这样以后对该SOCKET发起的网络操作都会由IOCP处理。
CreateIoCompletionPort((HANDLE)socket, hIOCP, socket, 0);
3.投递网络操作请求
每个投递到IOCP中的请求都需要包含一个编号信息,IOCP完成请求后在通知里也会包含这个编号信息。通过这个编号信息才能知道哪个通知对应哪个请求,这个编号信息就保存在重叠结构里。 我们不能调用普通的connect函数来发起连接,而是需要调用一个支持重叠结构的ConnectEx函数。但是这个函数默认是没有加载的,我们需要先加载这个函数。
GUID guidConn = WSAID_CONNECTEX;
LPFN_CONNECTEX pfnConnectEx = NULL;
DWORD dw = 0;
WSAIoctl(socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidConn, sizeof(guidConn), &pfnConnectEx, sizeof(pfnConnectEx), &dw, NULL, NULL);
跟调用connect函数发起连接不同的是,调用ConnextEx函数发起连接前,需要先把SOCKET绑定好本地端口。我们可以绑定0,这样系统就会自动分配本地端口。
sockaddr_in addrBind = {0};
addrBind.sin_family = AF_INET;
addrBind.sin_addr.s_addr = htonl(INADDR_ANY);
addrBind.sin_port = htons(0);
bind(socket ,(SOCKADDR *)&addrBind, sizeof(addrBind));
然后调用ConnectEx函数发起连接,注意最后一个参数就是重叠结构。这时连接请求和相应的重叠结构,就一起投递到IOCP里去了。
sockaddr_in addrConn = {0};
addrConn.sin_family = AF_INET;
addrConn.sin_addr.S_un.S_addr = inet_addr("112.53.42.52");
addrConn.sin_port = htons(80);
OVERLAPPED overlap = {0};
pfnConnectEx(socket, (sockaddr*)&addrConn, sizeof(addrConn), NULL, 0, NULL, &overlap);
4.获取IO通知
最后我们向IOCP获取通知,如果IOCP通知队列里暂时还没有通知,则会阻塞等待。
DWORD dwBytes;
SOCKET socketGet;
OVERLAPPED* overlapGet;
BOOL bIOSucc = GetQueuedCompletionStatus(hIOCP, &dwBytes, (PULONG_PTR)&socketGet, (LPOVERLAPPED*)&overlapGet, INFINITE);
获取到通知后,我们可以根据bIOSucc来判断是否连接成功,其他参数怎样使用,我们后续再详细讲解。
(未完待续)
|