提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔
文章目錄
- 前言
- 一、智能指針管理Session
- 二、用智能指針來實現Server的函數
- 1.start_accept()
- 1.引用計數注意點
- 2.std::bind 與異步回調函數的執行順序分析
- 2.handle_accept
- 1.異步調用中函數執行順序與智能指針插入順序的問題
- 3.ClearSession
- 三、Session的uuid
- 1.Boost UUID(唯一標識符)簡介與使用
- 四、Start
- shared_from_this() 使用詳解
- 一、shared_from_this() 是什么?
- 二、為什么需要它?
- 三、怎么使用?
- 四、使用注意事項
- 五、使用場景:Boost.Asio 的 Session 生命周期
- 總結
- 五、讀和寫的回調
- 總結
前言
提示:這里可以添加本文要記錄的大概內容:
之前的異步服務器為echo模式,但其存在安全隱患,就是在極端情況下客戶端關閉導致觸發寫和讀回調函數,二者都進入錯誤處理邏輯,進而造成二次析構的問題。
下面我們介紹通過C11智能指針構造成一個偽閉包的狀態延長session的生命周期
提示:以下是本篇文章正文內容,下面案例可供參考
一、智能指針管理Session
我們可以通過智能指針的方式管理Session類,將acceptor接收的鏈接保存在Session類型的智能指針里。由于智能指針會在引用計數為0時自動析構,所以為了防止其被自動回收,也方便Server管理Session
我們在Server類中添加成員變量,該變量為一個map類型,key為Session的uid,value為該Session的智能指針。
一個智能指針管理一個session
uuid是用來標識每一個session的,方便后期查找和刪除這個會話
🧠 再通俗點說:
想象你是開聊天室服務器的老板,來了很多用戶(Session),你得:
給每個用戶一個“編號”或“身份證號” → 這就是 UUID。
把用戶都存在一個表里(map)。
要踢人(斷開連接)的時候,只要知道身份證號就能精準找出來刪掉
class Server
{
public:Server(boost::asio::io_context& ioc, short port);void ClearSession(std::string uuid);
private:void start_accept();//監聽連接void handle_accept(std::shared_ptr<Session> , const boost::system::error_code& ec);//當有連接的時候觸發這個回調函數boost::asio::io_context& _ioc;//因為上下文不允許被復制 所以用引用tcp::acceptor _acceptor;std::map<std::string, std::shared_ptr<Session>>_sessions;
};
當后面在回調函數的時候,出現錯誤我們就將這個session從map中移除
此時管理這個session的智能指針的引用計數就會-1
不過此時不一定引用計數就會為0,說不定其他地方比如回調函數中還持有這個智能指針,因為我們在進行回調函數bind綁定的時候,通過值來傳遞這個智能指針,這個智能指針的引用計數也會+1
二、用智能指針來實現Server的函數
Server::Server(boost::asio::io_context& ioc, short port) :_ioc(ioc), _acceptor(ioc, tcp::endpoint(tcp::v4(), port))
{start_accept();
}void Server::start_accept()
{shared_ptr<Session>new_session=make_shared<Session>(_ioc,this);_acceptor.async_accept(new_session->Socket(),std::bind(&Server::handle_accept,this,new_session,placeholders::_1));}void Server::handle_accept(shared_ptr<Session> new_session,
const boost::system::error_code& ec)
{if (!ec)//成功{new_session->Start();_sessions.insert(make_pair(new_session->GetUuid(), new_session));}else{//delete new_session;}start_accept();//再次準備
}void Server::ClearSession(std::string uuid)
{_sessions.erase(uuid);
}
1.start_accept()
1.引用計數注意點
我們在**start_accept()**中不再用new來創建一個session,而是用一個智能指針來管理
此時重點! 我們創建了一個智能指針,此時的引用計數為1
然后我們bind中也按值傳遞了這個指針,此時引用計數+1
然后 start_accept() 結束 引用計數-1
然后進入 handle_accept()
2.std::bind 與異步回調函數的執行順序分析
? 問題描述:
在 C++ 中,當我們使用 std::bind 將某個函數(如 B)綁定為另一個函數(如 A)的一部分時,程序的實際執行流程是怎樣的?
具體來說:
如果 A 函數中使用 std::bind 綁定了 B 函數作為回調,執行流程到底是:
- 進入 A → 進入 B → 退出 B → 退出 A
- 進入 A → 退出 A → 等待某個事件 → 再執行 B → 退出 B
🧠 梳理核心概念:
這取決于 你是否立即調用了綁定后的函數對象,以及是否在 異步環境中使用它
🔍 兩種情況詳細分析:
🚀 情況 1:同步調用(立即執行綁定函數)
void A() {auto boundB = std::bind(B, 123);boundB(); // 立即執行
}執行流程:
進入 A↓
進入 B(因為 boundB() 被立即調用)↓
退出 B↓
退出 A
🔄 情況 2:異步綁定(比如注冊回調)
void A() {socket.async_read_some(buffer, std::bind(B, placeholders::_1, placeholders::_2));
}
這里:
● std::bind(B, …) 是將 B 打包成一個回調;
● async_read_some 是注冊事件,不會立刻執行;
● 所以 A 很快返回,B 要等事件觸發才執行
進入 A
↓
注冊回調(并不會執行 B)
↓
退出 A
↓
[某個時間點事件觸發]
↓
進入 B
↓
退出 B
所以這里的 handle_read 是回調函數,它會在數據到達時異步調用,而不是在start_accept()t 中立即調用
2.handle_accept
進入 handle_accept() 如果成功就進入這個session的讀寫通信就行,然后將這個session插入map中
1.異步調用中函數執行順序與智能指針插入順序的問題
?問題描述
在 Server::handle_accept 函數中:
if (!ec) {new_session->Start();_sessions.insert(make_pair(new_session->GetUuid(), new_session));
}
Start() 函數內部調用了 async_read_some,并使用 shared_from_this() 將當前 session 指針綁定到回調函數中。
問題在于:
● new_session->Start() 里面啟動了異步通信(如 async_read_some),
● _sessions.insert(…) 是在 Start() 完成后執行的。
那么問題是:
是否必須等 Start() 里面的異步通信完成并回調走完,insert() 才會執行?
還是說,只要 Start() 同步執行完,insert() 就會馬上執行?
?答案總結
insert(…) 會在 Start() 函數同步執行完畢后立即執行,不會等待異步讀寫操作或其回調完成。
原因:
● async_read_some(…) 本質上只是在內部注冊了一個回調函數,不阻塞當前線程,不執行回調。
● 一旦當前的 Start() 函數體執行完,就會馬上回到 handle_accept 繼續執行 insert(…)。
● 而回調(handle_read)只有等有客戶端數據傳來,觸發了 IO 事件,才會異步被執行
3.ClearSession
StartAccept函數中雖然new_session是一個局部變量,但是我們通過bind操作,將new_session作為數值傳遞給bind函數,而bind函數返回的函數對象內部引用了該new_session所以引用計數加1,這樣保證了new_session不會被釋放。
在HandleAccept函數里調用session的start函數監聽對端收發數據,并將session放入map中,保證session不被自動釋放。
此外,需要封裝一個釋放函數,將session從map中移除,當其引用計數為0則自動釋放
void Server::ClearSession(std::string uuid)
{_sessions.erase(uuid);
}
三、Session的uuid
1.Boost UUID(唯一標識符)簡介與使用
💡 什么是 UUID?
UUID(Universally Unique Identifier)是一個標準格式的 128 位數字,它的目標是在分布式系統中生成唯一 ID,可以避免因為數據庫自增或時間戳導致的沖突
格式示例:
550e8400-e29b-41d4-a716-446655440000
UUID 通常用于:
● 唯一標識某個會話(Session)
● 唯一標識某個用戶、資源
● 防止 ID 沖突
● 分布式系統或網絡系統中的唯一對象標識
🧰 Boost UUID 的使用
Boost 提供了 boost::uuids 命名空間中的 UUID 功能。使用非常簡單,以下是步驟和示例代碼:
🔌 引入頭文件
#include <boost/uuid/uuid.hpp> // uuid 類型
#include <boost/uuid/uuid_generators.hpp> // 生成器
#include <boost/uuid/uuid_io.hpp> // 支持 uuid 轉字符串
🛠? 生成一個隨機 UUID
boost::uuids::random_generator generator;//一個函數對象
boost::uuids::uuid id = generator();
std::string uuid_str = boost::uuids::to_string(id);std::cout << "UUID: " << uuid_str << std::endl;
? random_generator() 使用系統隨機數生成 UUID,符合 UUID v4(隨機生成版本)
? 常見用法:類中作為 Session 唯一標識符
class Session {
public:Session() {boost::uuids::uuid uid = boost::uuids::random_generator()();_uuid = boost::uuids::to_string(uid);}std::string GetUuid() const { return _uuid; }private:std::string _uuid;
};
?? 注意事項
● uuid 類型本身不是字符串,它是一個 16 字節數組。需要用 boost::uuids::to_string() 轉成字符串。
● 生成器對象可以復用,但你也可以每次臨時構造
📝 小結
● Boost.UUID 是生成唯一 ID 的利器,適合在網絡通信、分布式系統、資源標識中使用。
● 在你當前寫的 Session 類中用它生成 UUID,再放入 map<string, shared_ptr> 管理,非常合適。
● 常用接口是 random_generator() + to_string()
四、Start
我們進入Start函數中,我們用準備bind讀函數
void Session::Start()
{memset(_data, 0, sizeof(_data));_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, placeholders::_1, placeholders::_2,shared_from_this()));}
我們知道我們要傳入智能指針來管理
這里我們用shared_from_this()
因為這是這個Session本身的智能指針,就是在前面創造出來的,這里相當于自己的副本,這樣會使得引用計數同步,就不會導致前面的已經析構了,但這里還在
shared_from_this() 使用詳解
一、shared_from_this() 是什么?
shared_from_this() 是 std::enable_shared_from_this 提供的成員函數,用于在類的成員函數中獲取當前對象的 shared_ptr 智能指針副本。
它的作用是:
? 在類對象已經被 shared_ptr 管理時,從內部安全地獲取它自己的智能指針副本
二、為什么需要它?
如果你在類成員函數中通過 this 傳遞當前對象的裸指針給外部(如異步回調),可能在異步調用發生前對象就已被銷毀,導致程序崩潰(懸空指針)
boost::asio::async_read(_socket, buffer,std::bind(&Session::handle_read, this, ...)); // ?? 非安全
使用 shared_from_this() 可以延長對象生命周期,保證在異步回調中對象仍然有效
三、怎么使用?
1.繼承 std::enable_shared_from_this
class Session : public std::enable_shared_from_this<Session>
{...
};
2.在類成員函數中使用 shared_from_this()
void Session::Start() {auto self = shared_from_this();boost::asio::async_read(_socket, buffer,std::bind(&Session::handle_read, self, ...)); // ? 綁定智能指針,防止提前析構
}
四、使用注意事項
五、使用場景:Boost.Asio 的 Session 生命周期
當前 Boost.Asio 的服務端代碼中,Session 表示一個客戶端連接,異步讀取時這樣使用:
_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, ..., shared_from_this()));
這里的 shared_from_this() 是為了把當前對象打包成 shared_ptr,傳遞給回調,確保異步過程中不會被提前銷毀
總結
● shared_from_this() 適合需要延長對象生命周期的場景,尤其是異步/回調中。
● 使用前務必確保對象由 shared_ptr 創建。
● 適用于資源敏感類、異步類、會話類等典型需求
五、讀和寫的回調
在讀和寫的回調中我們分別傳入管理這個session的智能指針就行,然后如果出錯,就從map中移除就行,后續bind結束后就會逐漸的使得引用計數-1 當為0的時候就自動析了
//讀的回調函數
void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred, shared_ptr<Session>_self_shared)
{if (ec)//0正確 1錯誤{cout << "read error" << endl;//delete this;_server->ClearSession(_uuid);}else{cout << "server receivr data is "<<_data << endl;//將收到的數據發送回去boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transferred),std::bind(&Session::handle_write,this, placeholders::_1, placeholders::_2, _self_shared));}
}//寫的回調函數
void Session::handle_write(const boost::system::error_code& ec, size_t
bytes_transferred,shared_ptr<Session>_self_shared)
{ if (ec)//0正確 1錯誤{cout << "write error" << endl;_server->ClearSession(_uuid);}else{//發完了 就清除掉原先的memset(_data, 0, sizeof(_data));//繼續讀_socket.async_read_some(boost::asio::buffer(_data, bytes_transferred),std::bind(&Session::handle_read, this, placeholders::_1, placeholders::_2, _self_shared));}
}
總結
我們通過C11的bind和智能指針實現了類似于go,js等語言的閉包功能,保證在回調函數觸發之前Session都是存活的