2010-06-17 123 views
17

我的客戶端應用程序使用boost::asio::ip::tcp::socket連接到遠程服務器。 如果應用程序失去與此服務器的連接(例如,由於服務器崩潰或正在關閉),我希望它會定期嘗試重新連接,直到成功爲止。如何在斷開連接後乾淨地重新連接boost :: socket?

我需要在客戶端乾淨地處理斷開連接,整理並重復嘗試重新連接?

目前我的代碼中有趣的部分看起來像這樣。

connect這樣的:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    socket.connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     mydisconnect(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 

runIOServer()方法就是:

void MyClient::runIOService(boost::asio::io_service& io_service) 
{ 
    size_t executedCount = io_service.run(); 
    cout << "io_service: " << executedCount << " handlers executed." << endl; 
    io_service.reset(); 
} 

若有異步讀取處理程序返回一個錯誤,那麼他們只需調用這個disconnect方法:

void MyClient::mydisconnect(void) 
{ 
    boost::system::error_code errorcode; 

    if (socket.is_open()) 
    { 
     // Boost documentation recommends calling shutdown first 
     // for "graceful" closing of socket. 
     socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.shutdown error: " << errorcode.message() << endl; 
     } 

     socket.close(errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.close error: " << errorcode.message() << endl; 
     }  

     // Notify the observer we have disconnected 
     myObserver->disconnected();    
    } 

..試圖恩典完全斷開,然後通知觀察員,該觀察員將以5秒的間隔開始呼叫connect(),直到重新連接。

還有什麼我需要做的嗎?

目前確實似乎工作。如果我終止了連接的服務器,則在讀取處理程序時出現預期的"End of file"錯誤,並且mydisconnect()被調用時沒有任何問題。

但是,當它然後嘗試重新連接,並失敗,我看到它報告"socket.shutdown error: Invalid argument"。這是否僅僅是因爲我試圖關閉一個沒有讀/寫掛起的套接字?還是更多?

+0

如果您已經檢測到連接的另一端被關閉,那麼調用關機的理由是什麼? – 2010-06-17 19:26:32

+0

@samm:這不推薦?我認爲可能會有套接字上的掛起操作需要使用shutdown()來取消。我主要只是爲了簡單起見:如果我想正常斷開連接,或者任何異步操作返回錯誤,則會調用相同的'mydisconnect()'方法。 – GrahamS 2010-06-18 08:09:04

+0

我不確定它是否被推薦。待處理的數據或操作將在哪裏進行?連接的另一端不在那裏。 – 2010-06-18 11:52:34

回答

22

您每次重新連接時都需要創建一個新的boost::asio::ip::tcp::socket。最簡單的方法是使用boost::shared_ptr來分配堆上的套接字(如果你的套接字完全被封裝在類中,你也可以使用scoped_ptr)。例如:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    // socket is of type boost::shared_ptr<boost::asio::ip::tcp::socket> 
    socket.reset(new boost::asio::ip::tcp::socket(...)); 
    socket->connect(server_endpoint, errorcode); 
    // ... 
} 

然後,當mydisconnect被調用時,你可以解除分配插座:

void MyClient::mydisconnect(void) 
{ 
    // ... 
    // deallocate socket. will close any open descriptors 
    socket.reset(); 
} 

你看到的錯誤可能是操作系統後您清理文件描述符的結果」我叫close。當您撥打close,然後在同一個套接字上嘗試使用connect時,可能是嘗試連接無效的文件描述符。此時,根據您的邏輯,您應該看到一條以「連接失敗:...」開頭的錯誤消息,但您隨後調用mydisconnect,該錯誤消息可能試圖在無效文件描述符上調用shutdown。惡性循環!

+0

試圖阻止可移植性從來就不是一個好主意,尤其是當你甚至不知道目標操作系統或用於開發的操作系統時,這一點! – Geoff 2010-06-18 22:20:48

+0

@Geoff:這是一個公平的觀點......我編輯了我的回覆。 – bjlaub 2010-06-18 23:06:14

+0

感謝這種方法對我很好。我修改了connect,總是創建一個新的套接字,然後像你所描述的那樣在'shared_ptr'上調用'.reset'。如果連接嘗試失敗,我只需調用'socket-> close()'並將它留在那裏。我離開了'disconnect'方法,就像使用*禮貌*'關機'調用一樣。 – GrahamS 2010-06-25 11:37:25

1

我在過去使用Boost.Asio做過類似的事情。我使用異步方法,因此重新連接通常會讓現有的ip :: tcp :: socket對象超出範圍,然後爲調用async_connect創建一個新對象。如果async_connect失敗,我使用一個定時器休眠一下然後重試。

+0

謝謝,所以當你檢測到一個錯誤時,你只需調用'socket.close()',然後創建一個新的? – GrahamS 2010-06-18 08:15:41

+0

當ip :: tcp :: socket對象超出作用域時,描述符被關閉。 – 2010-06-18 11:53:01

4

爲了清楚這裏的目的是我用最後的辦法(但這是基於bjlaub的回答,所以請給任何upvotes他):

我宣佈socket成員作爲scoped_ptr

boost::scoped_ptr<boost::asio::ip::tcp::socket> socket; 

然後我修改了connect方法是:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Create new socket (old one is destroyed automatically) 
    socket.reset(new boost::asio::ip::tcp::socket(io_service)); 

    // Attempt connection 
    socket->connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     socket->close(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 
0

我已經試用過close()方法和關機方法而他們只是爲了讓我感到棘手。 Close()可以拋出一個錯誤,你需要捕捉並且是做你想做的事情的粗魯方法:)和shutdown()似乎是最好的,但是在多線程軟件上,我發現它可能很繁瑣。所以最好的辦法就像Sam說的那樣讓它超出範圍。如果套接字是類的成員,則可以1)重新設計,以便類使用「連接」對象來封裝套接字並讓它超出範圍或2)將其包裝在智能指針中並重置智能指針。如果你使用boost,包括shared_ptr便宜並且像魅力一樣工作。從來沒有一個套接字清理問題,使用shared_ptr。只是我的經驗。

+0

你*必須*調用'close(),''tricky'或不。否則,你有資源泄漏。對此沒有任何「粗魯」。 'shutdown()'不是'close()'的另一種選擇。 – EJP 2017-03-29 04:08:15

相關問題