使用TF_REUSE_SOCKET调用DisconnectEx后,ConnectEx因WSAEISCONN而失败

时间:2016-05-11 17:53:28

标签: c++ winapi iocp

我有一个基于IOCP的客户端,我想以下列方式实现HTTP重定向:

1)遇到重定向到其他主持人时DisconnectEx TF_REUSE_SOCKET

2)等待重叠完成,然后调用ConnectEx

但是,我的代码在调用WSAEISCONN时会收到ConnectEx返回码,即使我在GetLastError()的重叠结果返回时检查DisconnectEx,它也会给出0。

在这种情况下,MVCE会非常大,但如果没有基于经验的建议,我会发布一个。

更新

我试图制作一个MVCE,但遇到了不同的症状:

#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN

#include <WinSock2.h>
#include <MSWSock.h>
#include <Windows.h>
#include <Ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

#include <stdexcept>
#include <iostream>
#include <sstream>
#include <map>

static inline std::string ErrorMessage(const char* pErrorMessage, ...)
{
    std::string sFormattedMessage;
    va_list VariableArgumentList;
    va_start(VariableArgumentList, pErrorMessage);
    sFormattedMessage.resize(_vscprintf(pErrorMessage, VariableArgumentList) + 1);
    vsnprintf_s(const_cast<char*>(sFormattedMessage.c_str()), sFormattedMessage.size(), sFormattedMessage.size(), pErrorMessage, VariableArgumentList);
    va_end(VariableArgumentList);
    return sFormattedMessage;
}

#define CHECK(x, format, ...) { if ((x) == false)  throw std::runtime_error(ErrorMessage("%s(%d): "format, __FILE__, __LINE__, __VA_ARGS__)); }

template<typename T>
bool LoadWinsockExtensionFunction(SOCKET Socket, GUID Guid, T* pFunction)
{
    DWORD nBytesReturned = 0;
    return WSAIoctl(Socket, SIO_GET_EXTENSION_FUNCTION_POINTER, &Guid, sizeof(Guid), pFunction, sizeof(T), &nBytesReturned, NULL, NULL) == 0 && nBytesReturned == sizeof(T);
}

int main(int argc, char** argv)
{
    try
    {
        WORD nRequestedWinsockVersion = MAKEWORD(2, 2);
        WSADATA WsaData;
        CHECK(WSAStartup(nRequestedWinsockVersion, &WsaData) == 0, "WSAStartup failed");
        auto hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);
        CHECK(hCompletionPort != NULL, "CreateIoCompletionPort failed(%d)", GetLastError());
        auto Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
        CHECK(Socket != INVALID_SOCKET, "WSASocket failed(%d)", WSAGetLastError());
        CHECK(CreateIoCompletionPort(reinterpret_cast<HANDLE>(Socket), hCompletionPort, NULL, 0), "CreateIoCompletionPort failed(%d)", GetLastError());
        sockaddr_in LocalAddress;
        ZeroMemory(&LocalAddress, sizeof(LocalAddress));
        LocalAddress.sin_family = AF_INET;
        LocalAddress.sin_addr.s_addr = INADDR_ANY;
        LocalAddress.sin_port = 0;
        CHECK(bind(Socket, reinterpret_cast<SOCKADDR*>(&LocalAddress), sizeof(LocalAddress)) == 0, "bind failed(%d)", WSAGetLastError());
        LPFN_CONNECTEX pConnectEx = nullptr;
        CHECK(LoadWinsockExtensionFunction(Socket, WSAID_CONNECTEX, &pConnectEx), "WSAIoctl failed to load ConnectEx(%d)", WSAGetLastError());
        LPFN_DISCONNECTEX pDisconnectEx = nullptr;
        CHECK(LoadWinsockExtensionFunction(Socket, WSAID_DISCONNECTEX, &pDisconnectEx), "WSAIoctl failed to load DisconnectEx(%d)", WSAGetLastError());
        addrinfo Hint;
        ZeroMemory(&Hint, sizeof(Hint));
        Hint.ai_family = AF_INET;
        Hint.ai_protocol = IPPROTO_TCP;
        Hint.ai_socktype = SOCK_STREAM;
        std::map<std::string, PADDRINFOA> Hosts;
        // Scenarios:
        // one host - failure to connect on the second try to the first host with 52
        // two distinct hosts - failure to connect on the second try to the first host with 52
        Hosts.emplace("www.google.com", nullptr);
        Hosts.emplace("www.facebook.com", nullptr);
        for (auto& Host : Hosts)
        {
            auto nGetAddressInfoResult = getaddrinfo(Host.first.c_str(), "http", &Hint, &Host.second);
            CHECK(nGetAddressInfoResult == 0 && &Host.second, "getaddrinfo failed(%d)", nGetAddressInfoResult);
        }
        auto Host = Hosts.begin();
        WSAOVERLAPPED Overlapped;
        ZeroMemory(&Overlapped, sizeof(Overlapped));
        while (true)
        {
            if ((*pConnectEx)(Socket, Host->second->ai_addr, Host->second->ai_addrlen, nullptr, 0, nullptr, &Overlapped) == FALSE)
            {
                auto nWSAError = WSAGetLastError();
                CHECK(nWSAError == ERROR_IO_PENDING, "ConnectEx failed(%d)", nWSAError);
                DWORD nBytesTransferred = 0;
                ULONG_PTR pCompletionKey = 0;
                LPOVERLAPPED pOverlapped = nullptr;
                CHECK(GetQueuedCompletionStatus(hCompletionPort, &nBytesTransferred, &pCompletionKey, &pOverlapped, INFINITE), "overlapped operation failed(%d)", GetLastError());
            }
            CHECK(setsockopt(Socket, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0) == 0, "setsockopt failed(%d)", WSAGetLastError());
            CHECK(shutdown(Socket, SD_BOTH) == 0, "shutdown failed(%d)", WSAGetLastError());
            if ((*pDisconnectEx)(Socket, &Overlapped, TF_REUSE_SOCKET, 0) == FALSE)
            {
                auto nWSAError = WSAGetLastError();
                CHECK(nWSAError == ERROR_IO_PENDING, "ConnectEx failed(%d)", nWSAError);
                DWORD nBytesTransferred = 0;
                ULONG_PTR pCompletionKey = 0;
                LPOVERLAPPED pOverlapped = nullptr;
                CHECK(GetQueuedCompletionStatus(hCompletionPort, &nBytesTransferred, &pCompletionKey, &pOverlapped, INFINITE), "overlapped operation failed(%d)", GetLastError());
            }
            if (++Host == Hosts.end())
            {
                Host = Hosts.begin();
            }
        }
        closesocket(Socket);
        CloseHandle(hCompletionPort);
        for (auto& Host : Hosts)
        {
            freeaddrinfo(Host.second);
        }
        WSACleanup();
    }
    catch (std::exception& Exception)
    {
        OutputDebugStringA(Exception.what());
        std::cout << Exception.what();
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

有了这个片段,我会在第二次尝试连接到同一主机(ERROR_DUP_NAME可能吗?)时得到TIME_WAIT。我假设如果我可以重新绑定套接字(但我不能,因为第二个bind调用失败,WSAEINVAL这是正确的,因为套接字已经绑定了)这甚至可以正常工作。

我原始代码中的内容是从localhost重定向到界面的实际地址 - 可能只有一条路径在这种情况下会发出WSAEISCONN而不是{{1} }?我不能为原始代码发布MVCE,因为那时需要另一段接受连接的代码(也许我会做一个)。

我实际上发现,如果客户端调用了ERROR_DUP_NAME,那么DisconnectExERROR_DUP_NAME而发生(请参阅this进行详细分析)。所以最重要的是我不应该尝试在这种情况下重用套接字。

0 个答案:

没有答案