系列目录 windows完成端口(一) windows完成端口(二) windows完成端口(三) windows完成端口(四) windows完成端口(五) windows完成端口(六)
1
不知道你是否记得前面中说过每消耗一个预先准备客户端的socket,就要补上一个。这个代码现在看来就应该放在连接成功事件里面了:
DWORD ThreadFunction()
{
OVERLAPPED *pOverlapped = NULL;
PER_SOCKET_CONTEXT *pSocketContext = NULL;
DWORD dwBytesTransfered = 0;
BOOL bReturn = GetQueuedCompletionStatus(m_hIOCompletionPort,
&dwBytesTransfered,
(PULONG_PTR)&pSocketContext,
&pOverlapped, INFINITE);
if (((SOME_STRUCT*)pSocketContext)->s == 侦听socket句柄)
{
/*连接成功后可以做以下事情:
1. 获取对端和本端的ip地址和端口号,即AcceptEx的
第三个参数lpOutputBuffer中拿(这一步,不是必须)
2. 如果对端连接成功后会发数据过来,则可以从初始化时
调用AcceptEx准备的缓冲区里面拿到,即AcceptEx的
第三个参数lpOutputBuffer中拿(这一步不是必须)
3. 再次调用AcceptEx补充一个sAcceptSocket
(这一步是必须的)*/
}
//普通客户端socket收发数据
else
{
//通过pOverlapped结构得到pIOContext
PER_IO_CONTEXT* pIOContext = (PER_IO_CONTEXT*)pOverlapped;
if (pIOContext->Type == 收)
{
//做一些操作2,比如解析数据
}
else if (pIOContext->Type == 发)
{
//做一些操作3,比如显示一条数据发送成功信息
}
}
}
上面连接成功后的伪码,第1步和第2步不是必须的,而第3步是必须的,如果不及时补充的话,等连接数多于准备的socket,可能就会发生故障了。
因为两端的地址信息和对端发过来的第一组数据都在同一个缓冲区里面,再次看下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
);
虽然可以根据dwReceiveDataLength、dwLocalAddressLength、dwRemoteAddressLength、lpdwBytesReceived这几个参数计算出来,但是微软提供了一个函数来帮我们做这个解析动作: GetAcceptExSockaddrs 同理,这个函数最好也要通过WSAIoctl函数来动态获取:
// 获取GetAcceptExSockAddrs函数指针,也是同理
if(SOCKET_ERROR == WSAIoctl(
m_pListenContext->m_Socket,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidGetAcceptExSockAddrs,
sizeof(GuidGetAcceptExSockAddrs),
&m_lpfnGetAcceptExSockAddrs,
sizeof(m_lpfnGetAcceptExSockAddrs),
&dwBytes,
NULL,
NULL))
{
this->_ShowMessage(_T("WSAIoctl 未能获取GuidGetAcceptExSockAddrs函数指针。错误代码: %d\n"),
WSAGetLastError());
this->_DeInitialize();
return false;
}
然后使用返回的函数指针来使用函数GetAcceptExSockaddrs。解析地址信息和第一组数据的代码如下:
<pre code_snippet_id="2472609"
snippet_file_name="blog_20170706_24_1671088"
name="code"
class="cpp">SOCKADDR_IN* ClientAddr = NULL;
SOCKADDR_IN* LocalAddr = NULL;
int remoteLen = sizeof(SOCKADDR_IN);
int localLen = sizeof(SOCKADDR_IN);
///////////////////////////////////////////////////////////////////////////
// 1. 首先取得连入客户端的地址信息
// 这个 m_lpfnGetAcceptExSockAddrs 不得了啊~~~~~~
// 不但可以取得客户端和本地端的地址信息,
//还能顺便取出客户端发来的第一组数据,老强大了...
this->m_lpfnGetAcceptExSockAddrs(pIoContext->m_wsaBuf.buf,
pIoContext->m_wsaBuf.len - ((sizeof(SOCKADDR_IN)+16)*2),
sizeof(SOCKADDR_IN)+16,
sizeof(SOCKADDR_IN)+16,
(LPSOCKADDR*)&LocalAddr,
&localLen,
(LPSOCKADDR*)&ClientAddr,
&remoteLen);
this->_ShowMessage( _T("客户端 %s:%d 连入."),
inet_ntoa(ClientAddr->sin_addr),
ntohs(ClientAddr->sin_port) );
this->_ShowMessage( _T("客户额 %s:%d 信息:%s."),
inet_ntoa(ClientAddr->sin_addr),
ntohs(ClientAddr->sin_port),
pIoContext->m_wsaBuf.buf );
</pre><br><br>
2
以上介绍的是接收新连接成功后的处理,那收数据和发数据的准备工作在哪里做呢?(收取第一组数据可以在调用AcceptEx的地方做)。这个就仁者见仁,智者见智了。比如可以在新连接接收成功之后,立即准备给对端发数据;或者在收到对端数据的时候准备给对端发数据;在发送数据完成后准备收对端数据。伪码如下:
DWORD ThreadFunction()
{
OVERLAPPED *pOverlapped = NULL;
PER_SOCKET_CONTEXT *pSocketContext = NULL;
DWORD dwBytesTransfered = 0;
BOOL bReturn = GetQueuedCompletionStatus(m_hIOCompletionPort,
&dwBytesTransfered,
(PULONG_PTR)&pSocketContext,
&pOverlapped, INFINITE);
if (((SOME_STRUCT*)pSocketContext)->s == 侦听socket句柄)
{
/*连接成功后可以做以下事情:
1. 获取对端和本端的ip地址和端口号,
即AcceptEx的第三个参数lpOutputBuffer中拿(这一步,不是必须)
2. 如果对端连接成功后会发数据过来,则可以从初始化时
调用AcceptEx准备的缓冲区里面拿到,即AcceptEx的
第三个参数lpOutputBuffer中拿(这一步不是必须)
3. 再次调用AcceptEx补充一个sAcceptSocket
(这一步是必须的)
4. 调用WSASend准备发送数据工作或调用WSARecv准备接收数据工作
(这一步,不是必须)*/
}
//普通客户端socket收发数据
else
{
//通过pOverlapped结构得到pIOContext
PER_IO_CONTEXT* pIOContext = (PER_IO_CONTEXT*)pOverlapped;
if (pIOContext->Type == 收)
{
//解析收到的数据
//(这一步,不是必须)
//调用WSASend准备发送数据工作
//(比如应答客户端)(这一步,不是必须)
//继续调用WSARecv准备收取数据工作
//(这一步,不是必须)
}
else if (pIOContext->Type == 发)
{
//调用WSARecv准备收取数据工作(这一步,不是必须)
}
}
}
由于公众号文章字数有限,您可以接着阅读下一篇:《windows完成端口(三)》 系列目录 windows完成端口(一) windows完成端口(二) windows完成端口(三) windows完成端口(四) windows完成端口(五) windows完成端口(六)