2012-03-18 89 views
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); 
} 

回答

2

如果用'砰'的意思是'發送RST',那麼沒有什麼可以做的。如果收到RST,TCP堆棧必須中止連接並丟棄所有未決數據。

+0

是的,我的意思是一個RST包。我通過發佈異步接收ASAP來減輕它的負擔,所以我現在通常會得到這些數據,但是您最終確信(windows)TCP堆棧在收到RST後會放棄緩衝數據。 – doug65536 2012-05-01 06:19:45

+0

@ doug65536這是一個很好奇的方式。聲明總是正確的。 – EJP 2012-05-01 09:56:57

+0

是的,我猜我希望你錯了:) – doug65536 2013-01-24 23:49:07