对可伸缩的TCP/IP服务器而言,最有用的扩展API也就算AcceptEx了。利用这个函数,服务器可以投递一个异步调用,该调用将接受下一个传入的客户机连接。
- 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
- );
sListenSocket是监听套接字,sAcceptSocket是一个有效的,为绑定的套接字句柄,将被分配到下一个客户机连接上。因此需在投递AcceptEx调用前创建客户机的套接字句柄。因为套接字创建开销比较大,这个步骤很必要,如果一个服务器希望尽可能快的处理客户机连接,它就需要一个已创建的套接字库,以便把新的连接分配进去。
sAcceptSocket之后紧随的4个参数相互关联。lpOutputBuffer参数含有用于客户机连接的本地或远程地址,还有一个可选的缓冲区,该缓冲区用来接收来自客户机的第一个数据块。dwReceiveDataLength指明所提供的缓冲区需要多少字节数,该缓冲区用来接收客户机发送的数据。应用程序如果不接收数据,可将其置为0。dwLocalAddressLength指定套接字地址结构大小,它相当于客户机套接字的地址族加上16个字节。客户机套接字连接的本地地址放在lpOutputBuffer参数中紧随接收数据之后的地方。dwRemoteAddresslength与lpOutputBuffer参数一样。客户机连接的远程地址将被写入前一个参数中,紧随接收数据及本地地址之后。应注意, dwReceiveDataLength参数可以是0,但是dwLocalAddressLength和 dwRemoteAddresslength参数均不能为0。
lpdwBytesReceived指明操作立刻成功时,新建的客户机连接上所收到的字节数。lpOverlapped用于这个重叠操作的WSAOVERLAPPED结构,该参数是必须的---如果想执行一个阻塞的接收调用,使用accept或WSAAccept就行了。
示例代码,创建一个IPv4套接字并投递一个单一的AcceptEx:
- SOCKET s,sClient;
- HANDLE hComPort;
- LPFN_ACCEPTEX lpfnAcceptEx = NULL;
- GUID GuidAcceptEx = WSAID_ACCEPTEX;
- WSAOVERLAPPEDPLUS ol;
- SOCKADDR_IN salocal;
- DWORD dwBytes;
- char buf[1024];
- int buflen = 1024;
- //创建完成端口
- hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,(ULONG_PTR)0,0);
- //创建监听套接字
- s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
- //将套接字和完成端口关联起来
- CreateIoCompletionPort((HANDLE)s,hComPort,(ULONG_PTR)0,0);
- salocal.sin_family = AF_INET;
- salocal.sin_port = htons(5050);
- salocal.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(s, (SOCKADDR*)&salocal, sizeof(SOCKADDR_IN));
- listen(s, 200);
- //加载AcceptEx函数
- WSAIoctl(s,
- SIO_GET_EXTENSION_FUNCTION_POINTER,
- &GuidAcceptEx,
- sizeof(GuidAcceptEx),
- &lpfnAcceptEx,
- sizeof(lpfnAcceptEx),
- &dwBytes,
- NULL,
- NULL);
- //为已接收的连接创建客户机套接字
- sClient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
- //初始化扩展的重叠结构
- memset(&ol, 0 ,sizeof(ol));
- ol.operation = OP_ACCEPTEX;
- ol.client = sClient;
- lpfnAcceptEx(s,
- sClient,
- buf,
- buflen-((sizeof(SOCKADDR_IN)+16)*2),
- sizeof(SOCKADDR_IN)+16,
- sizeof(SOCKADDR_IN)+16,
- &dwBytes,
- &ol.overlapped);
- //在完成函数内调用GetQueuedCompletionStatus函数
- //在AcceptEx操作完成后,将已接收的客户机套接字和完成端口关联起来
- ...
本段代码演示如何加载AcceptEx函数。
同时注意到,因为AcceptEx的高性能特性,监听套接字的套接字属性不会自动被客户机套接字继承,要继承属性,服务器必须用SO_UPDATE_ACCEPT_CONTEXT及客户机套接字句柄调用setsockopt。
另外一点要清楚,如果为AcceptEx指定了一个接收缓冲区,则重叠操作只有在连接上收到至少一个字节的数据之后才能完成。