我有一个基于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
,那么DisconnectEx
因ERROR_DUP_NAME
而发生(请参阅this进行详细分析)。所以最重要的是我不应该尝试在这种情况下重用套接字。