7. sizeof(m_lpfnGetAcceptExSockAddrs), 8. &dwBytes, 9. NULL, 10. NULL);
WSAIoctl(m_pListenContexSIO_GET_EXTENSI&GuidGetAcceptEsizeof(GuidGetA&m_lpfnGetAccep 和导出AcceptEx一样一样的,同样是需要用其GUID来获取对应的函数指针 m_lpfnGetAcceptExSockAddrs 。 说了这么多,这个函数究竟是干嘛用的呢?它是名副其实的“AcceptEx之友”,为什么这么说呢?因为我前面提起过AcceptEx有个很神奇的功能,就是附带一个神奇的缓冲区,这个缓冲区厉害了,包括了客户端发来的第一组数据、本地的地址信息、客户端的地址信息,三合一啊,你说神奇不神奇? 这个函数从它字面上的意思也基本可以看得出来,就是用来解码这个缓冲区的,是的,它不提供别的任何功能,就是专门用来解析AcceptEx缓冲区内容的。例如如下代码: [cpp] view plaincopyprint? 1.
2. PER_IO_CONTEXT* pIoContext = 本次通信用的I/O Context 3.
4. SOCKADDR_IN* ClientAddr = NULL; 5. SOCKADDR_IN* LocalAddr = NULL;
6. int remoteLen = sizeof(SOCKADDR_IN), localLen = sizeof(SOCKADDR_IN); 7.
8. m_lpfnGetAcceptExSockAddrs(pIoContext->m_wsaBuf.buf, pIoContext->m_ws
aBuf.len - ((sizeof(SOCKADDR_IN)+16)*2), sizeof(SOCKADDR_IN)+16, sizeof(SOCKADDR_IN)+16, (LPSOCKADDR*)&LocalAddr, &localLen, (LPSOCKADDR*)&ClientAddr, &remoteLen);
PER_IO_CONTEXT* pIoContext = 本SOCKADDR_IN* ClientAddr = NULL;SOCKADDR_IN* LocalAddr = NULL; int remoteLen = sizeof(SOCKADDR 解码完毕之后,于是,我们就可以从如下的结构体指针中获得很多有趣的地址信息了: inet_ntoa(ClientAddr->sin_addr) 是客户端IP地址 ntohs(ClientAddr->sin_port) 是客户端连入的端口 inet_ntoa(LocalAddr ->sin_addr) 是本地IP地址 ntohs(LocalAddr ->sin_port) 是本地通讯的端口 pIoContext->m_wsaBuf.buf 是存储客户端发来第一组数据的缓冲区 自从用了“AcceptEx之友”,一切都清净了…. 【第七步】当收到Recv通知时, _DoRecv() 在讲解如何处理Recv请求之前,我们还是先讲一下如何投递WSARecv请求的。 WSARecv大体的代码如下,其实就一行,在代码中我们可以很清楚的看到我们用到了很多新建的PerIoContext的参数,这里再强调一下,注意一定要是自己另外新建的啊,一定不能是Worker线程里传入的那个PerIoContext,因为那个是监听Socket的,别给人弄坏了……: [cpp] view plaincopyprint? 1. int nBytesRecv = WSARecv(pIoContext->m_Socket, pIoContext ->p_wbuf, 1, &
dwBytes, 0, pIoContext->p_ol, NULL);
int nBytesRecv = WSARecv(p 这里,我再把WSARev函数的原型再给各位讲一下 [cpp] view plaincopyprint? 1.
2. int WSARecv(
3. SOCKET s, // 当然是投递这个操作的套接字 4. LPWSABUF lpBuffers, // 接收缓冲区
5. // 这里需要一个由WSABUF结构构成的数组 6. DWORD dwBufferCount, // 数组中WSABUF结构的数量,设置为1即
可
7. LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,这里会返回
函数调用所接收到的字节数
8. LPDWORD lpFlags, // 说来话长了,我们这里设置为0 即可 9. LPWSAOVERLAPPED lpOverlapped, // 这个Socket对应的重叠结构 10. NULL // 这个参数只有完成例程模式才会用到, 11. // 完成端口中我们设置为NULL即可 12. );
int WSARecv( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, 其实里面的参数,如果你们熟悉或者看过我以前的重叠I/O的文章,应该都比较熟悉,只需要注意其中的两个参数: ? LPWSABUF lpBuffers;
这里是需要我们自己new 一个 WSABUF 的结构体传进去的;
如果你们非要追问 WSABUF 结构体是个什么东东?我就给各位多说两句,就是在ws2def.h中有定义的,定义如下: [cpp] view plaincopyprint?
1.
2. typedef struct _WSABUF {
3. ULONG len; /* the length of the buffer */
4. __field_bcount(len) CHAR FAR *buf; /* the pointer to the buffer */ 5.
6. } WSABUF, FAR * LPWSABUF;
typedef struct _WSABUF { ULONG len; /* th __field_bcount(l } WSABUF, FAR * LPWSABU 而且好心的微软还附赠了注释,真不容易…. 看到了吗?如果对于里面的一些奇怪符号你们看不懂的话,也不用管他,只用看到一个ULONG和一个CHAR*就可以了,这不就是一个是缓冲区长度,一个是缓冲区指针么?至于那个什么 FAR…..让他见鬼去吧,现在已经是32位和64位时代了…… 这里需要注意的,我们的应用程序接到数据到达的通知的时候,其实数据已经被咱们的主机接收下来了,我们直接通过这个WSABUF指针去系统缓冲区拿数据就好了,而不像那些没用重叠I/O的模型,接收到有数据到达的通知的时候还得自己去另外recv,太低端了……这也是为什么重叠I/O比其他的I/O性能要好的原因之一。 ? LPWSAOVERLAPPED lpOverlapped
这个参数就是我们所谓的重叠结构了,就是这样定义,然后在有Socket连接进来的时候,生成并初始化一下,然后在投递第一个完成请求的时候,作为参数传递进去就可以, [cpp] view plaincopyprint?
1. OVERLAPPED* m_pol = new OVERLAPPED; 2.
3. eroMemory(m_pol, sizeof(OVERLAPPED));
OVERLAPPED* m_pol = new O ZeroMemory(m_pol, sizeof(O 在第一个重叠请求完毕之后,我们的这个OVERLAPPED 结构体里,就会被分配有效的系统参数了,并且我们是需要每一个Socket上的每一个I/O操作类型,都要有一个唯一的Overlapped结构去标识。 这样,投递一个WSARecv就讲完了,至于_DoRecv()需要做些什么呢?其实就是做两件事: (1) 把WSARecv里这个缓冲区里收到的数据显示出来; (2) 发出下一个WSARecv(); Over…… 至此,我们终于深深的喘口气了,完成端口的大部分工作我们也完成了,也非常感谢各位耐心的看我这么枯燥的文字一直看到这里,真是一个不容易的事情!! 【第八步】如何关闭完成端口 休息完毕,我们继续…… 各位看官不要高兴得太早,虽然我们已经让我们的完成端口顺利运作起来了,但是在退出的时候如何释放资源咱们也是要知道的,否则岂不是功亏一篑….. 从前面的章节中,我们已经了解到,Worker线程一旦进入了GetQueuedCompletionStatus()的阶段,就会进入睡眠状态,INFINITE的等待完成端口中,如果完成端口上一直都没有已经完成的I/O请求,那么这些线程将无法被唤醒,这也意味着线程没法正常退出。