在實際產品運行中,對連接管理有了更新的認識,這里分享一下。
shared_ptr管理連接對象的生命周期
shared_ptr的引用計數器決定了連接對象的生命周期。這里我說的連接對象就是在我的前文:http://blog.csdn.net/csfreebird/article/details/8522620
中的Client對象:
- #include "core/connection.h"
- #include <vector>
- using namespace std;
- class Client: public Connection<Client> {
- public:
- Client(io_service& s);
銷毀連接對象的條件
那么什么情況下shared_ptr的引用技術會變成0呢?必須滿足下列所有條件:
1. 如果你不再發起任何異步讀/寫操作
因為每一次異步讀/寫操作都會將Client對象自己的this指針包裝成shared_ptr,通過bind交給boost asio框架,此時框架將持有該shared_ptr,直到讀/寫完成,回調我們自己的函數后才會將引用計數器減1。
2. 如果沒有任何其他對象或者容器持有這個shared_ptr。
實際通信程序為了實現服務器事件通知,我會將所有的Client對象的shared_ptr保存在一個容器中,比如map。然后定期的檢查這些Client有沒有在數據庫中有事件要發布,如果有,則調用Client的方法發送數據。也會定期檢查Client對象代表的連接上的心跳消息,如果心跳超時,則將share_ptr從map中移除掉,並停止任何讀/寫的異步操作(也就是上上面第一個條件)
只有在滿足了這兩個條件的情況下,shared_ptr會自動銷毀Client對象,Client對象內部的成員變量socket也會被銷毀。
一般情況下不需要手動關閉socket
所以,我之前用下面的函數關閉socket一般情況下是不需要的
- void CloseSocket() {
- try {
- socket.shutdown(tcp::socket::shutdown_both);
- socket.close();
- } catch (std::exception& e) {
- BOOSTER_INFO("Connection") << "thread id: " << this_thread::get_id() << e.what() << endl;
- }
- }
多線程環境下關閉連接導致crash
如果要使用CloseSocket,多線程環境下必須小心。比如我碰到過這樣的情況:一個線程檢查到了超時,然后調用Client::CloseSocket方法,同時另一個線程正在該Client上發起了異步的讀/寫操作,結果進程崩潰了。boost asio不允許這樣做。
參考:http://web.archiveorange.com/archive/v/Q0J4VefPMc2v8QYvcVr3
異步讀/寫阻塞導致連接對象無法銷毀
如果就堅持不用CloseSocket行么,我碰到另一種情況,async_write/async_read會阻塞,結果shared_ptr的引用計數不會為0,所以Client對象無法被銷毀。那么怎么安全的調用CloseSocket的呢?用strand,下面是代碼:
- void Client::ToClose() {
- strand_.post(bind(&Client::DoClose, shared_from_this()));
- }
- void Client::DoClose() {
- CloseSocket();
- }
其他線程就只需要調用ToClose函數即可。
由於strand_.post的橋梁作用,CloseSocket會在io_service運行的線程池中被保護。就不會出現和async_read/async_write同時被執行的情況。
在實際編程中我們可能由於很多原因要關閉連接,比如前面說的心跳超時,也有收到了不正確的數據,或者在asio傳遞出來的網絡錯誤,又或者是收到了關閉進程的信號。
無論何種原因,都可以采用上面的方式進行關閉連接。所以原則只有一個,記住shared_ptr的生命周期即可。
優雅的關閉進程
如何在這種情況下優雅的退出進程是個問題,否則退出時程序會crash,留下core文件。這種情況是:
1. 線程池中運行這io_service對象,進行異步的讀/寫操作
2. 一個線程定期檢查數據庫中的事件消息,並發送給所有遠程終端,同時檢查每個連接的心跳超時時間。
3. 一個全局的map對象保存了所有Client的shared_ptr。
在以后的文章中會探索。