2010-01-02 150 views
22

有時boost :: asio似乎在我希望之前斷開連接,即在服務器正確處理斷開連接之前。我不知道這是如何實現的,因爲客戶端似乎認爲它完全發送了消息,但是當服務器發出錯誤時它甚至沒有讀取消息頭......在測試期間,這種情況只發生在5次,服務器收到客戶端關閉消息,並乾淨地斷開客戶端。boost :: asio乾淨地斷開連接

錯誤:

客戶端斷開 「一個現有的連接被強行關閉遠程主機」:

void disconnect() 
{ 
    boost::system::error_code error; 
    //just creates a simple buffer with a shutdown header 
    boost::uint8_t *packet = createPacket(PC_SHUTDOWN,0); 
    //sends it 
    if(!sendBlocking(socket,packet,&error)) 
    { 
     //didnt get here in my tests, so its not that the write failed... 
     logWrite(LOG_ERROR,"server", 
      std::string("Error sending shutdown message.\n") 
      + boost::system::system_error(error).what()); 
    } 

    //actaully disconnect 
    socket.close(); 
    ioService.stop(); 
} 
bool sendBlocking(boost::asio::ip::tcp::socket &socket, 
    boost::uint8_t *data, boost::system::error_code* error) 
{ 
    //get the length section from the message 
    boost::uint16_t len = *(boost::uint16_t*)(data - 3); 
    //send it 
    asio::write(socket, asio::buffer(data-3,len+3), 
     asio::transfer_all(), *error); 
    deletePacket(data); 
    return !(*error); 
} 

服務器:

void Client::clientShutdown() 
{ 
    //not getting here in problem cases 
    disconnect(); 
} 
void Client::packetHandler(boost::uint8_t type, boost::uint8_t *data, 
    boost::uint16_t len, const boost::system::error_code& error) 
{ 
    if(error) 
    { 
     //error handled here 
     delete[] data; 
     std::stringstream ss; 
     ss << "Error recieving packet.\n"; 
     ss << logInfo() << "\n"; 
     ss << "Error: " << boost::system::system_error(error).what(); 
     logWrite(LOG_ERROR,"Client",ss.str()); 

     disconnect(); 
    } 
    else 
    { 
     //call handlers based on type, most will then call startRead when 
     //done to get the next packet. Note however, that clientShutdown 
     //does not 
     ... 
    } 
} 



void startRead(boost::asio::ip::tcp::socket &socket, PacketHandler handler) 
{ 
    boost::uint8_t *header = new boost::uint8_t[3]; 
    boost::asio::async_read(socket,boost::asio::buffer(header,3), 
     boost::bind(&handleReadHeader,&socket,handler,header, 
     boost::asio::placeholders::bytes_transferred,boost::asio::placeholders::error)); 
} 
void handleReadHeader(boost::asio::ip::tcp::socket *socket, PacketHandler handler, 
    boost::uint8_t *header, size_t len, const boost::system::error_code& error) 
{ 
    if(error) 
    { 
     //error "thrown" here, len always = 0 in problem cases... 
     delete[] header; 
     handler(0,0,0,error); 
    } 
    else 
    { 
     assert(len == 3); 
     boost::uint16_t payLoadLen = *((boost::uint16_t*)(header + 0)); 
     boost::uint8_t type  = *((boost::uint8_t*) (header + 2)); 
     delete[] header; 
     boost::uint8_t *payLoad = new boost::uint8_t[payLoadLen]; 

     boost::asio::async_read(*socket,boost::asio::buffer(payLoad,payLoadLen), 
      boost::bind(&handleReadBody,socket,handler, 
      type,payLoad,payLoadLen, 
      boost::asio::placeholders::bytes_transferred,boost::asio::placeholders::error)); 
    } 
} 
void handleReadBody(ip::tcp::socket *socket, PacketHandler handler, 
    boost::uint8_t type, boost::uint8_t *payLoad, boost::uint16_t len, 
    size_t readLen, const boost::system::error_code& error) 
{ 
    if(error) 
    { 
     delete[] payLoad; 
     handler(0,0,0,error); 
    } 
    else 
    { 
     assert(len == readLen); 
     handler(type,payLoad,len,error); 
     //delete[] payLoad; 
    } 
} 
+0

你有沒有找到答案? – GrahamS

回答

21

我想你應該在致電socket.close()之前打電話給socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec)

boost::asio documentation for basic_stream_socket::close狀態:

For portable behaviour with respect to graceful closure of a connected socket, call shutdown() before closing the socket.

這應確保插座上的任何掛起操作是否正確取消任何緩衝區來調用socket.close之前刷新。

+0

我和Fire Lancer的問題完全一樣,這對我來說已經解決了,謝謝。這應該可以被接受的答案。 – Silverlan

5

也許這是發生了什麼事:

  • 客戶端發送斷開包
  • 客戶端關閉套接字
  • 服務器讀取處理程序被調用,但由於套接字已關閉,因此存在與關閉包關聯的錯誤。

我看到你的閱讀處理程序,如果有錯誤,你從不檢查你的關閉數據包是否在那裏。也許是。 基本上我說的是,也許你的客戶端有時能夠在服務器有機會分別處理它們之前發送關閉和關閉包。

+0

「//錯誤」在這裏拋出,len總是在問題情況下爲0 ......所以它總是讀取頭的0個字節,即它沒有讀取任何數據包......並且堅持一個睡眠(500 )或客戶端上的某些東西並不是一個好的解決方案,因爲在較慢的網絡上這樣做可能並不總是足夠的,並且是明顯的延遲。 –

+0

也許等待來自服務器的OK包,或讓服務器斷開連接? – Macke

+0

但是,如果客戶端然後等待服務器發送斷開連接消息(作爲對客戶端斷開連接消息的響應),那麼我只會結束服務器在客戶端斷開連接的相反問題.... –

3

使用async_write()並將write.close()放入寫入處理程序中。這將確保數據包由boost asio處理,並且在處理過程中不會被忽略(因爲close()調用)。

2

我有一個非常類似的問題。我相信這與Windows回收連接有關。以下是否熟悉?

  • 您在啓動程序時立即得到此錯誤,但在連接建立後不會立即生效?
  • 如果您在重新啓動應用程序之前等待超過4分鐘,則錯誤不會發生?

tcp規範指定,默認情況下,當tcp連接關閉時,它應該等待4分鐘才能進行最終確認。您可以使用netstat以FIN_WAIT狀態查看這些連接。 Windows操作系統檢測您何時嘗試連接到完全相同的系統,並採取這些部分關閉的連接並對其進行回收。您第二次調用該程序會獲得第一次運行留下的「關閉」連接。它得到下一個確認,然後真正關閉。

9

我試圖與這兩個close()方法和shutdown()方法

socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec) 

關機方法是最好的兩個來做到這一點。然而,我發現使用ASIO套接字的析構函數是乾淨利落的方法,因爲ASIO會爲您完成所有這些工作。所以你的目標是讓套接字掉出範圍。現在,您可以使用shared_ptr輕鬆完成此操作,並將shared_ptr重置爲全新套接字或null。這將調用ASIO套接字的析構函數,並且生活很好。

+0

使用'shared_ptr'完成這項工作非常好! – nabroyan

+0

這是誤導。析構函數只能通過socket.close(ec)進行操作,請參閱https://stackoverflow.com/a/39823756/1889040。所以你必須手動調用'socket.shutdown'來正確關閉。我可以確認沒有'socket.shutdown'緩衝區(在關閉調用之前)可能不會被刷新。 – scinart