Winsock: Overlapped AcceptEx表示在没有客户端连接的情况下有一个新的连接

Winsock: Overlapped AcceptEx indicates a new connection while no client connecting

本文关键字:连接 客户端 情况下 有一个 Overlapped AcceptEx 表示 Winsock      更新时间:2023-10-16

在我的程序中,我使用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;

我一直在使用AcceptEx和IOCP,我从来没有见过这样的问题。

关于你的代码。很难说到底哪里出了问题,因为它并不完整。但我很确定问题就在那里。

我看到的一个问题是,您提供给AcceptEx的第三个参数是本地缓冲区。这是错误的,因为这个缓冲区应该在accept操作期间保持有效。你所做的可能很容易导致堆栈内存损坏。

但你的"虚假接受"问题可能是由其他原因引起的。我想我知道问题出在哪里了。让我猜猜:

    你使用相同的IOCP来监听和接收(客户端)套接字。这是合理的,不需要超过1个IOCP。
  1. 当你从IOCP中取出完成队列时-你自动将其强制转换为WORK_ACCEPT并调用HandleAcceptedConnection。你不?

如果是,问题很明显。调用客户端套接字上的WSARecv。它完成了,并在IOCP中排队完成。您获取它,但是您将其视为已完成的接受。您将其强制转换为WORK_ACCEPT,这看起来很垃圾(仅仅因为它是而不是 WORK_ACCEPT结构)。

如果是这种情况,必须添加一种方法来区分不同的完成类型。例如,你可以声明一个基结构(所有的完成都将从它继承),它将有一个类型成员,它将标识完成类型。

我预计你的代码中有一个bug,但很难说在哪里,因为你没有显示对底层API的实际调用;我不知道你的包装代码在做什么…

您可能会发现查看一个基于AcceptEx()的工作服务器是很有用的。有一个可用在我的免费IOCP服务器框架:http://www.serverframework.com/products---the-free-framework.html

清理并重写了发布的部分代码:现在可以工作了....

但有趣的是:当比较两个"代码"时…