2017-08-15 144 views
4

從網絡中斷中恢復我正在編寫一個接受來自設備的數據並對其進行處理的服務器。一切工作正常,除非網絡中斷(即,如果我拔掉以太網電纜,然後重新連接它)。我正在使用read_until(),因爲設備使用的協議會使用特定的字節序列來終止數據包。當數據流中斷時,read_until()按預期阻塞。但是,當流再次啓動時,它仍然被阻塞。如果我用Wireshark查看數據流,設備將繼續傳輸,並且每個數據包都由網絡堆棧確認。但是,如果我查看bytes_readable,它始終爲0.如何檢測中斷以及如何重新建立與數據流的連接?以下是代碼片段,並提前感謝您提供的任何幫助。 [進入容易對我來說,這是我的第一個堆棧溢出問題....是的,我曾嘗試尋找答案。]如何使用boost :: asio從網絡中斷中恢復如何使用boost :: asio

using boost::asio::ip::tcp; 

boost::asio::io_service IOservice; 
tcp::acceptor acceptor(IOservice, tcp::endpoint(tcp::v4(), listenPort)); 
tcp::socket socket(IOservice); 
acceptor.accept(socket); 

for (;;) 
{ 
    len = boost::asio::read_until(socket, sbuf, end); 
    // Process sbuf 
    // etc. 
} 

回答

2

記住,客戶端發起連接,所以你唯一需要的要實現的是重新創建套接字並重新開始接受。我將保持你的代碼片段的格式,但我希望你的真實代碼能夠被正確地封裝。

using SocketType = boost::asio::ip::tcp::socket; 

std::unique_ptr<SocketType> CreateSocketAndAccept(
    boost::asio::io_service& io_service, 
    boost::asio::ip::tcp::acceptor& acceptor) { 
    auto socket = std::make_unique<boost::asio::ip::tcp::socket>(io_service); 
    boost::system::error_code ec; 
    acceptor.accept(*socket.get(), ec); 
    if (ec) { 
    //TODO: Add handler. 
    } 
    return socket; 
} 

... 
auto socket = CreateSocketAndAccept(IOservice, acceptor); 
for (;;) { 
    boost::system::error_code ec; 
    auto len = boost::asio::read_until(*socket.get(), sbuf, end, ec); 
    if (ec) // you could be more picky here of course, 
      // e.g. check against connection_reset, connection_aborted 
    socket = CreateSocketAndAccept(IOservice, acceptor); 
    ... 
} 

腳註:不用說,socket需要留在範圍內。

編輯:基於以下意見。

監聽套接字本身並不知道客戶端是否保持沉默或是否被切斷。所有的操作,特別是同步操作,應該在完成時施加時間限制。考慮設置SO_RCVTIMEOSO_KEEPALIVE(每個插槽或系統寬度,更多信息請參閱How to use SO_KEEPALIVE option properly to detect that the client at the other end is down?)。

另一種選擇是異步並實現一個完整的「共享」套接字服務器(BOOST示例頁面是一個很好的開始)。

無論哪種方式,您都可能遇到數據一致性問題並被迫處理它,例如,當客戶端檢測到連接中斷時,它會重新發送數據。 (或者使用更高級協議的更復雜的東西)

+0

謝謝你的湯姆。但問題是,當我拔出插頭時,read_until()不會返回錯誤代碼。它只是阻止並永遠不會返回。 – Schnizz

+1

現在想想,我想說這是有道理的。服務器(接收套接字)無法區分線路上的不活動狀態和非正常關閉連接。我會考慮在套接字上設置'SO_RCVTIMEO'或者在最後期限計時器周圍實現一個異步封裝來停止運行異步讀取的io_service。無論哪種方式,我會說你需要一個關於超時的外部信息。也就是說,我建議使用完全異步,爲傳入的,可接受的TCP連接創建多個「共享」套接字。 –

+0

是的,我想知道是否完全異步可以解決問題。我會放棄這一點。感謝幫助。 – Schnizz

1

如果你想保持同步,我看到處理事情的方式是在檢測到中斷時銷燬套接字。阻塞調用應該拋出一個你可以捕獲的異常,然後再次開始接受連接。

for (;;) 
{ 
    try { 
     len = boost::asio::read_until(socket, sbuf, end); 
     // Process sbuf 
     // etc. 
    } 
    catch (const boost::system::system_error& e) { 
    // clean up. Start accepting new connections. 
    } 
} 

當湯姆在他的回答中提到,有活動和非正常斷開之間沒有區別,所以你需要一個外部機制來檢測這一點。

如果您希望持續進行數據傳輸,可能每個服務器端的連接超時就足夠了。一個簡單的ping也可以工作。接受連接後,每隔X秒ping你的客戶端,並且如果他沒有回答就宣佈連接失效。

+0

值得一提的是,異常等同於使用'boost :: system :: error_code'。實際上,前者是在普通系統調用錯誤的基礎上實現(拋出)的。 –

+0

我按照Tom的建議實現了一個超時,它在Windows中工作,但不在Linux中。不過從長遠來看,異步可能是更優雅的選擇。所以在某些時候我會重構它來使用異步方法。感謝你們兩位的幫助。 – Schnizz

相關問題