网络模型之IOCP与扩展函数
Posted CPP编程客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络模型之IOCP与扩展函数相关的知识,希望对你有一定的参考价值。
上次我们用IOCP实现了一个简单的服务器,在处理消息方面性能已经不错了,但是接爱请求函数却依旧使用的是accept函数,所以这部分性能并不够,而Windows在扩展函数中为我们提供了一些选择,本篇就来介绍这些函数。
0x0. AcceptEx
这个函数用来异步地投递一个调用来接受客户端连接,其原型如下:
1BOOL AcceptEx (
2 SOCKET sListenSocket, // 监听套接字
3 SOCKET sAcceptSocket, // 分配给待连接客户端的套接字
4 PVOID lpOutputBuffer, // 用来接收用户第一份数据的缓冲区
5 DWORD dwReceiveDataLength, // 缓冲区的字节数
6 DWORD dwLocalAddressLength, // 本地套接字地址结构大小
7 DWORD dwRemoteAddressLength, // 远程套接字地址结构大小
8 LPDWORD lpdwBytesReceived, // 新建的客户机连接上所收到的字节数
9 LPOVERLAPPED lpOverlapped // 重叠结构
10);
这个函数稍微比accept函数麻烦了一点,那么在这里我们列出accept的原型来对比着展开介绍:
1SOCKET accept (
2 SOCKET s,
3 struct sockaddr FAR* addr,
4 int FAR* addrlen
5);
AcceptEx函数第一个参数也是监听套接字,而第二个参数是客户端的套接字,这说明accept函数是在接受客户端连接后才为其创建套接字的,而AcceptEx函数却是先创建好套接字,在客户端连接进来时直接给它用。这就是这个函数效率高之所在,因为创建套接字花费颇高,当用户连接时才为其创建套接字会导致处理变慢。
第三个参数是用来接收客户端的第一份数据的,在客户端连接后AcceptEx函数就顺便将其第一份数据接收了,就保存在这里,而第四个参数就是接收这份数据的缓冲区大小。而我们若是想像accept函数那样只处理接受请求,后面再接收第一份数据,我们需要将缓冲区的长度设置为0,即第四个参数置0。
The number of bytes reserved for the remote address information. This must be at least 16 bytes more than the maximum address length for the transport protocol in use.
这两个参数是保存在缓冲区中的,所以实际我们的缓冲区大小应该减去这字节的大小,所以dwReceiveDataLength应该传入:len - (sizeof(SOCKADDR_IN) + 16) * 2。
lpdwBytesReceived用来保存所接收第一份数据的实际大小,最后一个参数是重叠参数,我们在重叠IO中就说过,一个函数若有此参数,即表明它是异步的,所以AcceptEx函数就是异步的。
0x1. GetAcceptExSockaddrs
1VOID GetAcceptExSockaddrs (
2 PVOID lpOutputBuffer, // 缓冲区
3 DWORD dwReceiveDataLength, // 缓冲区字节数
4 DWORD dwLocalAddressLength, // 本地地址长度
5 DWORD dwRemoteAddressLength, // 远程地址长度
6 LPSOCKADDR *LocalSockaddr, // 本地socket地址
7 LPINT LocalSockaddrLength, // 本地socket地址长度
8 LPSOCKADDR *RemoteSockaddr, // 远程socket地址
9 LPINT RemoteSockaddrLength // 远程socket地址长度
10);
0x2.加载扩展函数
AcceptEx和GetAcceptExSockaddrs都包含在< MSWSock.h>中,可以直接通过链接mswsock库来使用这两个函数,但是因为扩展函数并不存在于Winsock2的结构体系中,所以这种方式消耗较大,这里我将介绍动态获取这些函数的方法。
Winsock2提供了WSAIoctl函数用于对Windows套接字提供IO控制,之前我们还用过Winsock1中的ioctlsocket函数,只是WSAIoctl提供了更多的选项(ctl是control的缩写),我们可以通过这个函数来动态获取到扩展函数。其原型如下:
1int WSAIoctl (
2 SOCKET s, // IO操作的套接字
3 DWORD dwIoControlCode, // IO控制命令
4 LPVOID lpvInBuffer, // 指向传入数据缓冲区
5 DWORD cbInBuffer, // 数据的长度
6 LPVOID lpvOUTBuffer, // 指向返回的数据缓冲区
7 DWORD cbOUTBuffer, // 返回数据的长度
8 LPDWORD lpcbBytesReturned, // 实际返回的字节数
9 LPWSAOVERLAPPED lpOverlapped, // 重叠结构
10 LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE // 例行程序
11);
要获取扩展函数,dwIoControlCode应该指定SIO_GET_EXTENSION_FUNCTION_POINTER控制命令。
1LPFN_ACCEPTEX lpfnAcceptEx; // AcceptEx函数指针
2LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs; // GetAcceptExSockaddrs函数指针
3GUID guidAcceptEx = WSAID_ACCEPTEX; // AcceptEx数据
4GUID guidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; // GetAcceptExSockaddrs数据
5DWORD dwBytes;
6
7// 加载AcceptEx函数
8WSAIoctl(servSock,
9 SIO_GET_EXTENSION_FUNCTION_POINTER,
10 &guidAcceptEx, sizeof(guidAcceptEx),
11 &lpfnAcceptEx, sizeof(lpfnAcceptEx),
12 &dwBytes, NULL, NULL
13);
14
15// 加载GetAcceptExSockaddrs函数
16WSAIoctl(servSock,
17 SIO_GET_EXTENSION_FUNCTION_POINTER,
18 &guidGetAcceptExSockaddrs, sizeof(guidGetAcceptExSockaddrs),
19 &lpfnGetAcceptExSockaddrs, sizeof(lpfnGetAcceptExSockaddrs),
20 &dwBytes, NULL, NULL
21);
大家可以看到,获取扩展函数其实并不麻烦。传入要获取的数据,WSAIoctl返回该数据的函数指针,GUID类型描述了全局唯一的一个ID,需要用它来指定要获取的数据,具体代码中已经很清晰了,便不多说了,若还有哪里不明白可以留言。
具体使用就放到下篇了,网络编程已经由浅入深地写了好长时间了,不知大家都理解了多少呢?可能这里好多人并不是搞服务器的(我也不是),这些大多其实也都不是C++的东西,而是关于操作系统的,所以我有时间就快点把网络编程这一系列结束吧。我前段时间抽时间学习了下Linux的epoll模型,感觉比iocp实现起来要简单,说起来都是对select模型的改进,再把iocp用一篇写完就顺便说下epoll模型,以全面的介绍Windows和Linux上主要的网络模型,让大家可以对比着参考理解,接着把asio说了就返回写C++的东西了。
以上是关于网络模型之IOCP与扩展函数的主要内容,如果未能解决你的问题,请参考以下文章