Winsock: перекрывающийся AcceptEx указывает на новое соединение при отсутствии подключения клиента - PullRequest
1 голос
/ 06 сентября 2011

В моей программе я использую перекрывающуюся версию AcceptEx() для принятия новых подключений. После того, как новое соединение было принято, программа инициирует еще один перекрывающийся вызов на AcceptEx() для принятия большего количества соединений. Это прекрасно работает, и я могу успешно подключить несколько клиентов к серверу.

Но если я просто подключу одного клиента и позволю серверному приложению вызвать WSARecv (перекрытый) для этого сокета, AcceptEx() волшебным образом примет новое "призрачное" соединение (первый клиент, который работает, ничего не делает). Конечно, когда я звоню WSARecv, это выдает ошибку.

Программа включает в себя порт завершения ввода / вывода для всех перекрывающихся вызовов.

Я не знаю, откуда исходит ложная связь. Но это, кажется, ошибка в моем коде, которую я не могу найти.

Вещи, которые я могу определенно исключить из причины ошибок: 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;

Ответы [ 3 ]

4 голосов
/ 06 сентября 2011

Я работал с AcceptEx и IOCP, и никогда не видел такой проблемы.

О вашем коде. Трудно сказать, что именно не так в этом, так как это не полный. Но я почти уверен, что проблема в этом.

Одна проблема, которую я вижу, состоит в том, что третий параметр, который вы указываете AcceptEx, это локальный буфер. Это неправильно, потому что этот буфер должен оставаться действительным в течение всей операции принятия. То, что вы сделали, может легко привести к повреждению стековой памяти.

Но ваша проблема "поддельного принятия", вероятно, вызвана чем-то другим. И я подозреваю, что знаю, в чем проблема. Дай угадаю:

  1. Вы используете один и тот же IOCP как для прослушивающего, так и для принятого (клиентского) сокета. Это разумно, нет необходимости иметь более 1 IOCP.
  2. Когда вы выводите завершение из IOCP, вы автоматически переводите его на WORK_ACCEPT и звоните HandleAcceptedConnection. Не так ли?

Если так - проблема очевидна. Вы вызываете WSARecv на клиентском сокете. Это завершается, и завершение ставится в очередь IOCP. Вы получаете это, однако вы рассматриваете это как законченное принятие. Вы приводите его к WORK_ACCEPT, что выглядит неуклюже (просто потому, что это не WORK_ACCEPT структура).

Если это так - вы должны добавить способ различения различных типов завершения. Например, вы можете объявить базовую структуру (от которой наследуются все дополнения), которая будет иметь член типа, который будет идентифицировать тип завершения.

0 голосов
/ 11 сентября 2011

Очистил и переписал выданную часть кода: Работает сейчас ....

Но самое смешное: сравнивая два "кода" друг с другом ... вызовы и т. Д.все те же ...

0 голосов
/ 06 сентября 2011

Я ожидаю, что у вас есть ошибка в вашем коде, но трудно сказать, где, поскольку вы не показываете реальные вызовы базового API; Я понятия не имею, что делает ваш код-обертка ...

Возможно, вам будет полезно взглянуть на работающий сервер AcceptEx (). В моей бесплатной инфраструктуре IOCP-сервера есть один: http://www.serverframework.com/products---the-free-framework.html

...