Winsock: Overlapped AcceptEx 表示没有客户端连接时有新连接
在我的程序中,我使用 AcceptEx()
的重叠版本来接受新连接。 接受新连接后,程序会启动另一个重叠调用 AcceptEx()
以接受更多连接。这工作正常,我可以成功地将多个客户端连接到服务器。
但是,如果我只连接一个客户端并让服务器应用程序在此套接字上调用 WSARecv(重叠),AcceptEx() 会神奇地接受一个新的“幽灵”连接(第一个客户端不执行任何操作) )。当然,当我对此调用 WSARecv
时,它会给出错误。
该程序包含一个用于所有重叠调用的I/O 完成端口。
我不知道假连接从何而来。但这似乎是我的代码中的一个错误,我无法找到。
我可以明确排除错误原因的事情: 1.我使用的重叠结构和铸造参数正确。 2. IOCP 包装类。
以下是相关代码(在我看来) - 如果您需要更多,请告诉我:)
//schematic
main()
{
Server.Init(...);
Server.Start(); //Run-loop
}
CServer::Init(/*...*/)
{
[...]
//Create the listen socket...
Ret = InitAcceptorSocket(strLocalAddress, strListenPort, nBacklog);
if(Ret != Inc::INC_OK)
return Ret;
//...Associate it with the IOCP
if(!m_pIOCP->AssociateHandle((HANDLE) m_pListenSocket->operator size_t(), 2))
return Inc::INC_FATAL;
[...]
}
CServer::InitAcceptorSocket(const std::wstring& strLocalAddress, const std::wstring& strListenPort, int nBacklog)
{
//Create the socket
m_pListenSocket.reset(new Inc::CSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
//Bind to specific port
if(!m_pListenSocket->Bind(Inc::WStringToString(strLocalAddress), Inc::WStringToString(strListenPort))) //Works as bind just calls getadrrinfo within itself
//Put the socket into listen mode
if(!m_pListenSocket->Listen(nBacklog)) //simple listen-wrapper: just calls the function and returns status indication
}
//Starts the server's work-cycle
CServer::Start(/**/)
{
//Call accept
DoCallAccept(m_pListenSocket.get());
//Resume the threads
//std::for_each(m_vecThreadHandles.begin(), m_vecThreadHandles.end(), [] (HANDLE hThread) {::ResumeThread(hThread);} );
//TEST: Enter the Loop, too
ServerMainWorkerThreadProc(this);
return Inc::INC_OK;
}
//Worker thread proc
uintptr_t WINAPI ServerMainWorkerThreadProc(void* pvArgs)
{
CServer* pServer = (CServer*)pvArgs;
bool bLooping = true;
try
{
while(bLooping)
{
bLooping = pServer->DoWork();
};
}
catch(Inc::CException& e)
{
DebugBreak();
}
return 0;
}
bool CServer::DoWork()
{
DWORD dwBytes = 0;
ULONG_PTR ulKey = 0;
OVERLAPPED* pOverlapped = nullptr;
//Dequeue a completion packet
if(!m_pIOCP->GetCompletionStatus(&dwBytes, &ulKey, &pOverlapped, INFINITE))
{
//error stuff
}
//Check for termination request:
if(!dwBytes && !ulKey && !pOverlapped)
return false;
//Convert the Overlapped and check which work has to be done
switch(((MYOVERLAPPED*)pOverlapped)->WorkType)
{
case WT_ACCEPT: //A new connection has been accepted
HandleAcceptedConnection((WORK_ACCEPT*)pOverlapped);
break;
case WT_SEND: //Send data
//HandleSendRequest((WORK_SEND*)pOverlapped);
break;
case WT_RECV: //Data has been received
//HandleReceivedData((WORK_RECV*)pOverlapped);
break;
[...]
return true;
}
//New connection has been accepted
bool CServer::HandleAcceptedConnection(WORK_ACCEPT* pWork)
{
//Create a new client socket object
std::unique_ptr<Inc::CSocket> pSocket(new Inc::CSocket(pWork->SocketNewConnection)); //obtains the nescessary information (like AF_INET , etc by calls to getsockopt - works fine)
//Associate with the IOCP
if(!m_pIOCP->AssociateHandle((HANDLE)((SOCKET)(*(pSocket.get()))), 2))
{
//Report the error
}
//Queue a recv-packet
if(!DoCallRecv(pSocket.get()))
{
//Report the error
}
//Release the client-socket-object
pSocket.release();
//Call accept another time
DoCallAccept(pWork->pListenSocket);
//Cleanuo
delete pWork;
return true;
}
//Call Recv on the socket
bool CServer::DoCallRecv(Inc::CSocket* pSocket)
{
//Create the work object for receiving data
std::unique_ptr<WORK_RECV> pWorkRecv(new WORK_RECV);
memset((OVERLAPPED*)pWorkRecv.get(), 0, sizeof(OVERLAPPED));
pWorkRecv->pSocket = pSocket;
//Call Recv
std::string strRecvBuffer; //temporary receive buffer for immediate completion
short sRet = pSocket->Recv(strRecvBuffer, pWorkRecv->pTestWSABuf, 2048, (OVERLAPPED*)pWorkRecv.get());
[...]
if(sRet == Inc::REMOTETRANSACTION_PENDING)
{
//release the work item so it is still on the heap when the overlapped operation completes
pWorkRecv.release();
}
return true;
}
//Queue a call to accept
bool CServer::DoCallAccept(Inc::CSocket* pListenSocket)
{
//Create the overlapped-structure
std::unique_ptr<WORK_ACCEPT> pWork(new WORK_ACCEPT);
memset((OVERLAPPED*)pWork.get(), 0, sizeof(OVERLAPPED));
pWork->pListenSocket = pListenSocket;
pWork->pSocket = m_pListenSocket.get();
//Call accept
pWork->SocketNewConnection = m_pListenSocket->Accept(nullptr, nullptr, (OVERLAPPED*)pWork.get());
//Release the work object
pWork.release();
return true;
}
//The accept function for My custom socket-wrapper-class
SOCKET Inc::CSocket::Accept(sockaddr_storage* pAddr, int* pAddrLen, OVERLAPPED* pOverlapped)
{
[...]
else //Overlapped
{
//create the client socket
SOCKET ClientSock = socket(m_SocketAF, SOCK_STREAM, 0);
if(ClientSock == INVALID_SOCKET)
throw(Inc::CException(WSAGetLastError(), "Socket creation failed."));
//address structure & size
sockaddr_storage *ClientAddress = {0}; DWORD dwClientAddressSize = sizeof(sockaddr_storage);
//output buffer
//char acOutputBuffer[(2 * sizeof(sockaddr_storage)) + 32] = "";
//received bytes
DWORD dwBytes = 0;
if(m_lpfnAcceptEx(m_Socket, ClientSock, (PVOID)m_acOutputBuffer, 0, (dwClientAddressSize + 16), (dwClientAddressSize + 16), &dwBytes, pOverlapped) == FALSE)
{
int nError = WSAGetLastError();
if(nError != WSA_IO_PENDING)
throw(Inc::CException(nError, "AcceptEx failed."));
return ClientSock;
}
//if immidiately & successfully connected, get the client address
[...]
return ClientSock;
}
}
//The receive function
short Inc::CSocket::RecvHelper(std::string& strIncomingDataBuffer, WSABUF*& pWSABuf, unsigned int nBytesToRecv, OVERLAPPED* pOverlapped)
{
int iRet = 0; //ret code
DWORD dwReceived = 0, dwFlags = 0;
//Clear the Buffer
strIncomingDataBuffer.clear();
//create the receiving buffer
std::unique_ptr<char[]> pcBuf(new char[nBytesToRecv]);
//create the WSABUF
std::unique_ptr<WSABUF> pWSABufBuf (new WSABUF);
pWSABufBuf->len = nBytesToRecv;
pWSABufBuf->buf = pcBuf.get();
iRet = WSARecv(m_Socket, pWSABufBuf.get(), 1, pOverlapped ? NULL : (&dwReceived), &dwFlags, pOverlapped, NULL);
if(iRet == 0)
{
//closed (gracefully) by the client (indicated by zero bytes returned)
if(dwReceived == 0 && (!pOverlapped))
return REMOTECONNECTION_CLOSED; //return
//successfull received
strIncomingDataBuffer.assign(pWSABufBuf->buf, dwReceived);
return SUCCESS;
}
if(iRet == SOCKET_ERROR)
{
int nError = WSAGetLastError();
//Overlapped transaction initiated successfully
//waiting for completion
if(nError == WSA_IO_PENDING)
{
//release the buffers
pcBuf.release();
pWSABuf = pWSABufBuf.release(); //hand it over to the user
return REMOTETRANSACTION_PENDING; //return "transaction pending"-status
}
//forced closure(program forced to exit)
if(nError == WSAECONNRESET)
{
[...]
}
编辑:编写了一个运行良好的测试服务器
//Accept a new connection
ACCEPTLAPPED* pOverAccept = new ACCEPTLAPPED;
pOverAccept->pSockListen = &SockListen;
pOverAccept->pSockClient = new Inc::CSocket(SockListen.Accept(nullptr, nullptr, pOverAccept));
//Main loop
DWORD dwBytes = 0, dwFlags = 0;
ULONG_PTR ulKey = 0;
OVERLAPPED* pOverlapped = nullptr;
while(true)
{
dwBytes = 0; dwFlags = 0; ulKey = 0; pOverlapped = nullptr;
//Dequeue a packet
pIOCP->GetCompletionStatus(&dwBytes, &ulKey, &pOverlapped, INFINITE);
switch(((BASELAPPED*)pOverlapped)->Type)
{
case 1: //Accept
{
//ASsociate handle
ACCEPTLAPPED* pOld = (ACCEPTLAPPED*)pOverlapped;
pIOCP->AssociateHandle((HANDLE)(pOld->pSockClient)->operator SOCKET(),2);
//call recv
RECVLAPPED* pRecvLapped = new RECVLAPPED;
pRecvLapped->pSockClient = pOld->pSockClient;
short sRet = (pRecvLapped->pSockClient)->Recv(pRecvLapped->strBuf, pRecvLapped->pBuf, 10, pRecvLapped);
//Call accept again
ACCEPTLAPPED* pNewAccLapp = new ACCEPTLAPPED;
pNewAccLapp->pSockListen = ((ACCEPTLAPPED*)pOverlapped)->pSockListen;
pNewAccLapp->pSockClient = new Inc::CSocket((pNewAccLapp->pSockListen)->Accept(nullptr, nullptr, pNewAccLapp));
delete pOverlapped;
};
break;
case 2: //Recv
{
RECVLAPPED* pOld = (RECVLAPPED*)pOverlapped;
if(!pOverlapped->InternalHigh)
{
delete pOld->pSockClient;
Inc::CSocket::freewsabufpointer(&(pOld->pBuf));
delete pOld;
break;
};
cout << std::string(pOld->pBuf->buf, pOld->pBuf->len) <<endl;
In my program I am using the overlapped version of AcceptEx()
to accept new connections.
After a new connection has been accepted, the programm initiates another overlapped call to AcceptEx()
for accepting more connections. This works fine and I can connect multiple clients to the server successfully.
But if I just connect one client and let the server-application call WSARecv (overlapped) on this socket, AcceptEx()
magically accepts a new "ghost"-connection (There is the first client running doing nothing). When I call WSARecv
on that, of course - it gives an error.
The program incorporates an I/O-Completion Port for all the overlapped calls.
I don´t know where the fake connection comes from. But that it seems to be a bug in my code that I am unable to find.
Things i can definetly exclude from being the errors reason:
1.The overlapped-structures i use and the parameter for casting works correct.
2. The IOCP-wrapper class.
Following is the relevant code (in my opinion) - if You need more, tell me, please :)
//schematic
main()
{
Server.Init(...);
Server.Start(); //Run-loop
}
CServer::Init(/*...*/)
{
[...]
//Create the listen socket...
Ret = InitAcceptorSocket(strLocalAddress, strListenPort, nBacklog);
if(Ret != Inc::INC_OK)
return Ret;
//...Associate it with the IOCP
if(!m_pIOCP->AssociateHandle((HANDLE) m_pListenSocket->operator size_t(), 2))
return Inc::INC_FATAL;
[...]
}
CServer::InitAcceptorSocket(const std::wstring& strLocalAddress, const std::wstring& strListenPort, int nBacklog)
{
//Create the socket
m_pListenSocket.reset(new Inc::CSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
//Bind to specific port
if(!m_pListenSocket->Bind(Inc::WStringToString(strLocalAddress), Inc::WStringToString(strListenPort))) //Works as bind just calls getadrrinfo within itself
//Put the socket into listen mode
if(!m_pListenSocket->Listen(nBacklog)) //simple listen-wrapper: just calls the function and returns status indication
}
//Starts the server's work-cycle
CServer::Start(/**/)
{
//Call accept
DoCallAccept(m_pListenSocket.get());
//Resume the threads
//std::for_each(m_vecThreadHandles.begin(), m_vecThreadHandles.end(), [] (HANDLE hThread) {::ResumeThread(hThread);} );
//TEST: Enter the Loop, too
ServerMainWorkerThreadProc(this);
return Inc::INC_OK;
}
//Worker thread proc
uintptr_t WINAPI ServerMainWorkerThreadProc(void* pvArgs)
{
CServer* pServer = (CServer*)pvArgs;
bool bLooping = true;
try
{
while(bLooping)
{
bLooping = pServer->DoWork();
};
}
catch(Inc::CException& e)
{
DebugBreak();
}
return 0;
}
bool CServer::DoWork()
{
DWORD dwBytes = 0;
ULONG_PTR ulKey = 0;
OVERLAPPED* pOverlapped = nullptr;
//Dequeue a completion packet
if(!m_pIOCP->GetCompletionStatus(&dwBytes, &ulKey, &pOverlapped, INFINITE))
{
//error stuff
}
//Check for termination request:
if(!dwBytes && !ulKey && !pOverlapped)
return false;
//Convert the Overlapped and check which work has to be done
switch(((MYOVERLAPPED*)pOverlapped)->WorkType)
{
case WT_ACCEPT: //A new connection has been accepted
HandleAcceptedConnection((WORK_ACCEPT*)pOverlapped);
break;
case WT_SEND: //Send data
//HandleSendRequest((WORK_SEND*)pOverlapped);
break;
case WT_RECV: //Data has been received
//HandleReceivedData((WORK_RECV*)pOverlapped);
break;
[...]
return true;
}
//New connection has been accepted
bool CServer::HandleAcceptedConnection(WORK_ACCEPT* pWork)
{
//Create a new client socket object
std::unique_ptr<Inc::CSocket> pSocket(new Inc::CSocket(pWork->SocketNewConnection)); //obtains the nescessary information (like AF_INET , etc by calls to getsockopt - works fine)
//Associate with the IOCP
if(!m_pIOCP->AssociateHandle((HANDLE)((SOCKET)(*(pSocket.get()))), 2))
{
//Report the error
}
//Queue a recv-packet
if(!DoCallRecv(pSocket.get()))
{
//Report the error
}
//Release the client-socket-object
pSocket.release();
//Call accept another time
DoCallAccept(pWork->pListenSocket);
//Cleanuo
delete pWork;
return true;
}
//Call Recv on the socket
bool CServer::DoCallRecv(Inc::CSocket* pSocket)
{
//Create the work object for receiving data
std::unique_ptr<WORK_RECV> pWorkRecv(new WORK_RECV);
memset((OVERLAPPED*)pWorkRecv.get(), 0, sizeof(OVERLAPPED));
pWorkRecv->pSocket = pSocket;
//Call Recv
std::string strRecvBuffer; //temporary receive buffer for immediate completion
short sRet = pSocket->Recv(strRecvBuffer, pWorkRecv->pTestWSABuf, 2048, (OVERLAPPED*)pWorkRecv.get());
[...]
if(sRet == Inc::REMOTETRANSACTION_PENDING)
{
//release the work item so it is still on the heap when the overlapped operation completes
pWorkRecv.release();
}
return true;
}
//Queue a call to accept
bool CServer::DoCallAccept(Inc::CSocket* pListenSocket)
{
//Create the overlapped-structure
std::unique_ptr<WORK_ACCEPT> pWork(new WORK_ACCEPT);
memset((OVERLAPPED*)pWork.get(), 0, sizeof(OVERLAPPED));
pWork->pListenSocket = pListenSocket;
pWork->pSocket = m_pListenSocket.get();
//Call accept
pWork->SocketNewConnection = m_pListenSocket->Accept(nullptr, nullptr, (OVERLAPPED*)pWork.get());
//Release the work object
pWork.release();
return true;
}
//The accept function for My custom socket-wrapper-class
SOCKET Inc::CSocket::Accept(sockaddr_storage* pAddr, int* pAddrLen, OVERLAPPED* pOverlapped)
{
[...]
else //Overlapped
{
//create the client socket
SOCKET ClientSock = socket(m_SocketAF, SOCK_STREAM, 0);
if(ClientSock == INVALID_SOCKET)
throw(Inc::CException(WSAGetLastError(), "Socket creation failed."));
//address structure & size
sockaddr_storage *ClientAddress = {0}; DWORD dwClientAddressSize = sizeof(sockaddr_storage);
//output buffer
//char acOutputBuffer[(2 * sizeof(sockaddr_storage)) + 32] = "";
//received bytes
DWORD dwBytes = 0;
if(m_lpfnAcceptEx(m_Socket, ClientSock, (PVOID)m_acOutputBuffer, 0, (dwClientAddressSize + 16), (dwClientAddressSize + 16), &dwBytes, pOverlapped) == FALSE)
{
int nError = WSAGetLastError();
if(nError != WSA_IO_PENDING)
throw(Inc::CException(nError, "AcceptEx failed."));
return ClientSock;
}
//if immidiately & successfully connected, get the client address
[...]
return ClientSock;
}
}
//The receive function
short Inc::CSocket::RecvHelper(std::string& strIncomingDataBuffer, WSABUF*& pWSABuf, unsigned int nBytesToRecv, OVERLAPPED* pOverlapped)
{
int iRet = 0; //ret code
DWORD dwReceived = 0, dwFlags = 0;
//Clear the Buffer
strIncomingDataBuffer.clear();
//create the receiving buffer
std::unique_ptr<char[]> pcBuf(new char[nBytesToRecv]);
//create the WSABUF
std::unique_ptr<WSABUF> pWSABufBuf (new WSABUF);
pWSABufBuf->len = nBytesToRecv;
pWSABufBuf->buf = pcBuf.get();
iRet = WSARecv(m_Socket, pWSABufBuf.get(), 1, pOverlapped ? NULL : (&dwReceived), &dwFlags, pOverlapped, NULL);
if(iRet == 0)
{
//closed (gracefully) by the client (indicated by zero bytes returned)
if(dwReceived == 0 && (!pOverlapped))
return REMOTECONNECTION_CLOSED; //return
//successfull received
strIncomingDataBuffer.assign(pWSABufBuf->buf, dwReceived);
return SUCCESS;
}
if(iRet == SOCKET_ERROR)
{
int nError = WSAGetLastError();
//Overlapped transaction initiated successfully
//waiting for completion
if(nError == WSA_IO_PENDING)
{
//release the buffers
pcBuf.release();
pWSABuf = pWSABufBuf.release(); //hand it over to the user
return REMOTETRANSACTION_PENDING; //return "transaction pending"-status
}
//forced closure(program forced to exit)
if(nError == WSAECONNRESET)
{
[...]
}
EDIT: Wrote a test-server which works just fine
//Accept a new connection
ACCEPTLAPPED* pOverAccept = new ACCEPTLAPPED;
pOverAccept->pSockListen = &SockListen;
pOverAccept->pSockClient = new Inc::CSocket(SockListen.Accept(nullptr, nullptr, pOverAccept));
//Main loop
DWORD dwBytes = 0, dwFlags = 0;
ULONG_PTR ulKey = 0;
OVERLAPPED* pOverlapped = nullptr;
while(true)
{
dwBytes = 0; dwFlags = 0; ulKey = 0; pOverlapped = nullptr;
//Dequeue a packet
pIOCP->GetCompletionStatus(&dwBytes, &ulKey, &pOverlapped, INFINITE);
switch(((BASELAPPED*)pOverlapped)->Type)
{
case 1: //Accept
{
//ASsociate handle
ACCEPTLAPPED* pOld = (ACCEPTLAPPED*)pOverlapped;
pIOCP->AssociateHandle((HANDLE)(pOld->pSockClient)->operator SOCKET(),2);
//call recv
RECVLAPPED* pRecvLapped = new RECVLAPPED;
pRecvLapped->pSockClient = pOld->pSockClient;
short sRet = (pRecvLapped->pSockClient)->Recv(pRecvLapped->strBuf, pRecvLapped->pBuf, 10, pRecvLapped);
//Call accept again
ACCEPTLAPPED* pNewAccLapp = new ACCEPTLAPPED;
pNewAccLapp->pSockListen = ((ACCEPTLAPPED*)pOverlapped)->pSockListen;
pNewAccLapp->pSockClient = new Inc::CSocket((pNewAccLapp->pSockListen)->Accept(nullptr, nullptr, pNewAccLapp));
delete pOverlapped;
};
break;
case 2: //Recv
{
RECVLAPPED* pOld = (RECVLAPPED*)pOverlapped;
if(!pOverlapped->InternalHigh)
{
delete pOld->pSockClient;
Inc::CSocket::freewsabufpointer(&(pOld->pBuf));
delete pOld;
break;
};
cout << std::string(pOld->pBuf->buf, pOld->pBuf->len) <<endl;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我一直在使用AcceptEx和IOCP,但我从未见过这样的问题。
关于你的代码。很难说它到底出了什么问题,因为它不完整。但我 我很确定问题就在那里。
我看到的一个问题是,您提供给 AcceptEx 的第三个参数是一个本地缓冲区,这是错误的,因为该缓冲区应该保持有效。在接受期间你所做的可能很容易导致堆栈内存损坏,
但你的“虚假接受”问题可能是由其他原因引起的,让我猜猜:
HandleAcceptedConnection
不是吗?如果是的话,问题就很明显了。它完成,并在 IOCP 中对完成进行排队。您获取它,但是您将其视为已完成的接受。您将其转换为
WORK_ACCEPT
,这看起来很垃圾(只是因为它不是WORK_ACCEPT
结构)。如果是这种情况 - 您必须添加一种方法来区分不同的完成类型。例如,您可以声明一个基本结构(所有完成都将从该结构继承),该结构将具有一个类型成员,该成员将标识完成类型。
I"ve been working with
AcceptEx
and IOCP, and I never-ever saw such a problem.About your code. It's hard to say what's wrong exactly in it, since it's not complete. But I'm pretty sure the problem is there.
One problem that I see is that the 3rd parameter you supply to
AcceptEx
is a local buffer. This is wrong, because this buffer should remain valid for the duration of the accept operation. What you did may easily lead to the stack memory corruption of whatever.But your "spurious accept" problem is probably caused by something else. And I suspect that I know what's the problem. Let me guess:
WORK_ACCEPT
and callHandleAcceptedConnection
. Don't you?If so - the problem is obvious. You call the
WSARecv
on the client socket. It completes, and the completion is queued the the IOCP. You fetch it, however you treat it as a completed accept. You cast it toWORK_ACCEPT
, which looks junky (simply becase it is not aWORK_ACCEPT
structure).If this is the case - you must add a way to distinguish different completion types. For instance you may declare a base struct (from which all the completions would inherit), which would have a type member, which will identify the completion type.
我预计您的代码中有一个错误,但很难说在哪里,因为您没有显示对底层 API 的实际调用;我不知道你的包装器代码在做什么......
你可能会发现查看一个工作的基于 AcceptEx() 的服务器很有用。我的免费 IOCP 服务器框架中有一个可用: http://www.serverframework .com/products---the-free-framework.html
I expect you have a bug in your code but it's hard to say where as you don't show the actual calls to the underlying API; I've no idea what your wrapper code is doing...
You might find it useful to look at a working AcceptEx() based server. There's one available in my free IOCP server framework: http://www.serverframework.com/products---the-free-framework.html
是否清理并重写了代码的已发布部分:现在可以使用......
但有趣的是:在相互比较两个“代码”时......调用等仍然相同......
Did clean up and rewrite the issued part of the code: Works now....
But the funny thing is: while comparing the two "codes" with each other... the the calls etc are still the same...