1
我正在編寫一個UPnP客戶端,我的一個測試路由器總是「關閉」連接關閉,而不是在發送響應之後進行正常關機 - 發送。這導致我的recv調用不能獲取數據。當對等體在winsock 2中重置TCP連接時,您如何避免數據丟失?
我知道數據在那裏,因爲我可以在數據包嗅探器中看到它。
如果我的代碼在連接重置之前快速前進以收回數據,那麼我會獲得數據。在許多情況下,對等方在我能夠接收它之前重置連接,導致沒有數據複製到我的接收緩衝區,並且recv發生WSAECONNRESET錯誤。
任何想法如何解決我的問題,以容忍在netgear路由器中寫得不好的UPnP實現?
我嘗試使用WSAEventSelect並使讀取異步,這似乎有所幫助,但它並不總是工作。
// Object that manages reliably sending and receiving, even if the
// peer does stupid things like slamming connection shut at EOF
class Transceiver
{
SOCKET sock;
AutoWSACloseEvent syncEvent;
// Buffer pool
template<size_t bufferSize>
struct Buffer
: public SLIST_ENTRY
, public AutoPool<Buffer<bufferSize>, false>
{
char data[bufferSize];
size_t size() const
{
return bufferSize;
}
};
public:
Transceiver() : sock(INVALID_SOCKET)
{
}
int Init(SOCKET sock);
int SendAll(const std::string &data);
int ReceiveAll(std::string &data);
};
int UpnpNat::Transceiver::Init(SOCKET sock)
{
int err;
this->sock = sock;
syncEvent = WSACreateEvent();
if (!syncEvent)
return ErrorHook(WSAGetLastError());
err = WSAEventSelect(sock, syncEvent, FD_READ | FD_CLOSE);
if (err == SOCKET_ERROR)
return ErrorHook(WSAGetLastError());
return NO_ERROR;
}
int UpnpNat::Transceiver::SendAll(const std::string &request)
{
for (int ofs = 0; ofs < (int)request.length();)
{
auto xferSize = send(sock, &request[ofs], (int)request.length() - ofs, 0);
if (xferSize == SOCKET_ERROR)
return ErrorHook(WSAGetLastError());
ofs += xferSize;
}
return NO_ERROR;
}
int UpnpNat::Transceiver::ReceiveAll(std::string &response)
{
int err = NO_ERROR;
int xferSize;
auto responseBuf = MakeAutoDelete(new Buffer<16384>());
if (!responseBuf)
return ErrorHook(ERROR_OUTOFMEMORY);
bool needRecvWait = false;
for (;;)
{
if (needRecvWait)
{
needRecvWait = false;
if (WSAWaitForMultipleEvents(1, syncEvent.Storage(),
FALSE, 30000, FALSE) != WAIT_OBJECT_0)
{
err = WSAETIMEDOUT;
return ErrorHook(err);
}
WSANETWORKEVENTS wsane;
ZeroInit(&wsane);
err = WSAEnumNetworkEvents(sock, syncEvent, &wsane);
if (err == SOCKET_ERROR)
return ErrorHook(WSAGetLastError());
if (wsane.lNetworkEvents & FD_CLOSE)
{
err = wsane.iErrorCode[FD_CLOSE_BIT];
break;
}
if ((wsane.lNetworkEvents & FD_READ) == 0)
{
continue;
}
if (wsane.iErrorCode[FD_READ_BIT] != NO_ERROR)
return ErrorHook(wsane.iErrorCode[FD_READ_BIT]);
}
xferSize = recv(sock, responseBuf->data, (int)responseBuf->size(),
MSG_PARTIAL);
if (xferSize == SOCKET_ERROR)
{
err = WSAGetLastError();
if (err == WSAEWOULDBLOCK)
{
needRecvWait = true;
continue;
}
// Workaround for crap routers that slam connection shut at EOF
if (err == WSAECONNRESET && response.length() > 0)
return NO_ERROR;
return ErrorHook(WSAGetLastError());
}
if (xferSize <= 0)
break;
response.append(responseBuf->data, 0, (std::string::size_type)xferSize);
}
return ErrorHook(err);
}
是的,我的意思是一個RST包。我通過發佈異步接收ASAP來減輕它的負擔,所以我現在通常會得到這些數據,但是您最終確信(windows)TCP堆棧在收到RST後會放棄緩衝數據。 – doug65536 2012-05-01 06:19:45
@ doug65536這是一個很好奇的方式。聲明總是正確的。 – EJP 2012-05-01 09:56:57
是的,我猜我希望你錯了:) – doug65536 2013-01-24 23:49:07