在winsock 2中,当对等方重置TCP连接时,如何避免数据丢失?

在winsock 2中,当对等方重置TCP连接时,如何避免数据丢失?,tcp,router,upnp,winsock2,Tcp,Router,Upnp,Winsock2,我正在编写一个UPnP客户机,我的一个测试路由器总是“猛击”关闭连接,而不是在发送响应后进行优雅的关机发送。这导致我的recv呼叫无法获取数据 我知道数据在那里,因为我可以在数据包嗅探器中看到它 如果我的代码以足够快的速度在连接重置之前接收数据,那么我将获得数据。在许多情况下,对等方在我可以接收连接之前重置连接,导致没有数据复制到我的接收缓冲区,并且接收到WSAECONNRESET错误 有没有办法修复我的终端,以容忍netgear路由器中写得不好的UPnP实现 我尝试使用WSAEventSele

我正在编写一个UPnP客户机,我的一个测试路由器总是“猛击”关闭连接,而不是在发送响应后进行优雅的关机发送。这导致我的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);
}
//可靠地管理发送和接收的对象,即使
//peer会做一些愚蠢的事情,比如在EOF关闭连接
类收发机
{
插座;
AutoWSACloseEvent-syncEvent;
//缓冲池
模板
结构缓冲区
:公共滑动条目的输入
,公共自动工具
{
字符数据[bufferSize];
大小\u t大小()常量
{
返回缓冲区大小;
}
};
公众:
收发器():插座(无效的_插座)
{
}
int Init(插座插座);
int SendAll(const std::string和data);
int ReceiveAll(标准::字符串和数据);
};
int UpnpNat::收发器::初始化(套接字套接字)
{
INTERR;
这个->袜子=袜子;
syncEvent=WSACreateEvent();
如果(!syncEvent)
返回ErrorHook(WSAGetLastError());
err=WSAEventSelect(sock、syncEvent、FD|u READ | FD|u CLOSE);
if(err==SOCKET\u错误)
返回ErrorHook(WSAGetLastError());
返回无错误;
}
int UpnpNat::收发器::SendAll(常量std::字符串和请求)
{
for(intofs=0;ofs<(int)request.length();)
{
auto xferSize=send(sock,&request[ofs],(int)request.length()-ofs,0);
if(xferSize==SOCKET\u错误)
返回ErrorHook(WSAGetLastError());
ofs+=xferSize;
}
返回无错误;
}
int UpnpNat::收发器::ReceiveAll(std::字符串和响应)
{
int err=无错误;
int-xversize;
auto responseBuf=MakeAutoDelete(new Buffer());
if(!responseBuf)
返回ErrorHook(ERROR\u OUTOFMEMORY);
bool needRecvWait=false;
对于(;;)
{
if(needRecvWait)
{
needRecvWait=false;
如果(WSAWaitForMultipleEvents)(1,syncEvent.Storage(),
FALSE,30000,FALSE)!=等待对象(0)
{
err=WSAETIMEDOUT;
返回ErrorHook(err);
}
wsanetorkevents wsane;
ZeroInit(&wsane);
err=WSAEnumNetworkEvents(sock、syncEvent和wsane);
if(err==SOCKET\u错误)
返回ErrorHook(WSAGetLastError());
if(wsane.l网络通风口和FD_关闭)
{
err=wsane.iErrorCode[FD_CLOSE_BIT];
打破
}
如果((wsane.lNetworkEvents&FD_READ)==0)
{
继续;
}
if(wsane.iErrorCode[FD\u读取位]!=无错误)
返回ErrorHook(wsane.iErrorCode[FD_READ_BIT]);
}
xferSize=recv(sock,responseBuf->data,(int)responseBuf->size(),
味精(部分);
if(xferSize==SOCKET\u错误)
{
err=WSAGetLastError();
if(err==WSAEWOULDBLOCK)
{
needRecvWait=true;
继续;
}
//解决在EOF关闭连接的垃圾路由器的方法
if(err==WSAECONNRESET&&response.length()>0)
返回无错误;
返回ErrorHook(WSAGetLastError());
}
if(xferSize数据,0,(std::string::size\u type)xferSize);
}
返回ErrorHook(err);
}

如果你所说的“slams”是指“发送RST”,那你就无能为力了。如果收到RST,TCP堆栈必须中止连接并放弃所有挂起的数据。

是的,我指的是RST数据包。我通过尽快发布异步接收缓解了这种情况,所以我通常现在就获取数据,但您最终发现,(windows)TCP堆栈在收到RST时确实会丢弃缓冲数据。@doug65536这是一种奇怪的说法。这句话总是正确的。