C++ asio網絡編程(6)利用C11模擬偽閉包實現連接的安全回收

提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔

文章目錄

  • 前言
  • 一、智能指針管理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 函數作為回調,執行流程到底是:

  1. 進入 A → 進入 B → 退出 B → 退出 A
  2. 進入 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都是存活的

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/906295.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/906295.shtml
英文地址,請注明出處:http://en.pswp.cn/news/906295.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

AI與產品架構設計(2):Agent系統的應用架構與落地實

什么是AI Agent&#xff1f;其在架構中的獨特定位 AI Agent&#xff08;人工智能代理&#xff09;是一種模擬人類智能行為的自主系統&#xff0c;通常以大型語言模型&#xff08;LLM&#xff09;作為核心引擎。簡單來說&#xff0c;Agent能夠像人一樣感知環境信息、規劃行動方…

Rust 數據結構:String

Rust 數據結構&#xff1a;String Rust 數據結構&#xff1a;String什么是字符串&#xff1f;創建新字符串更新字符串將 push_str 和 push 附加到 String 對象后使用 運算符和 format! 宏 索引到字符串字符串在內存中的表示字節、標量值和字形簇 分割字符串遍歷字符串的方法 R…

Java卡與SSE技術融合實現企業級安全實時通訊

簡介 在數字化轉型浪潮中,安全與實時數據傳輸已成為金融、物聯網等高安全性領域的核心需求。本文將深入剖析東信和平的Java卡權限分級控制技術與浪潮云基于SSE的大模型數據推送技術,探索如何將這兩項創新技術進行融合,構建企業級安全實時通訊系統。通過從零到一的開發步驟,…

繼MCP、A2A之上的“AG-UI”協議橫空出世,人機交互邁入新紀元

第一章&#xff1a;AI交互的進化與挑戰 1.1 從命令行到智能交互 人工智能的發展歷程中&#xff0c;人機交互的方式經歷了多次變革。早期的AI系統依賴命令行輸入&#xff0c;用戶需通過特定指令與機器溝通。隨著自然語言處理技術的進步&#xff0c;語音助手和聊天機器人逐漸普…

MySQL刷題相關簡單語法集合

去重 distinct 關鍵字 eg. &#xff1a;select distinct university from user_profile 返回行數限制&#xff1a; limit關鍵字 eg. &#xff1a;select device_id from user_profile limit 2 返回列重命名&#xff1a;as 關鍵字 eg.&#xff1a;select device_id as user_in…

Kubernetes MCP服務器(K8s MCP):如何使用?

#作者&#xff1a;曹付江 文章目錄 1、什么是 Kubernetes MCP 服務器&#xff1f;1.1、K8s MCP 服務器 2、開始前的準備工作2.1. Kubernetes集群2.2. 安裝并運行 kubectl2.3. Node.js 和 Bun2.4. &#xff08;可選&#xff09;Helm v3 3、如何設置 K8s MCP 服務器3.1. 克隆存儲…

計算機網絡-HTTP與HTTPS

文章目錄 計算機網絡網絡模型網絡OSITCP/IP 應用層常用協議HTTP報文HTTP狀態碼HTTP請求類型HTTP握手過程HTTP連接HTTP斷點續傳HTTPSHTTPS握手過程 計算機網絡 網絡模型 為了解決多種設備能夠通過網絡相互通信&#xff0c;解決網絡互聯兼容性問題。 網絡模型是計算機網絡中用于…

Springboot 跨域攔截器配置說明

錯誤代碼 跨域設置 Configuration public class WebConfig implements WebMvcConfigurer {/*** cors 跨域配置*/Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedMethods("GET", "HEAD", &qu…

受不了github的網絡限制了,我開源了一個圖床工具 gitee-spring-boot-starter

嗨嗨嗨~ 我老馬又又來了&#xff01;&#xff01;&#xff01;上次寫了一篇我開源了一款阿里云OSS的spring-boot-starter&#xff0c;然后買的資源包到期了&#xff0c;后面又想白&#xff08;開&#xff09;嫖&#xff08;源&#xff09;的路子&#xff0c;首先想到了使用gith…

基于labview的聲音采集、存儲、處理

程序1&#xff1a;基于聲卡的數據采集 程序2&#xff1a;基于聲卡的雙聲道模擬輸出 程序3&#xff1a;聲音信號的采集與存儲 程序4&#xff1a;聲音信號的功率譜分析 程序5&#xff1a;基于labview的DTMF

第一次經歷項目上線

這幾天沒寫csdn&#xff0c;因為忙著項目上線的問題&#xff0c;我這階段改了非常多的前端bug哈哈哈哈&#xff0c;說幾個比較好的bug思想&#xff01; 這個頁面算是我遇到的比較大的bug&#xff0c;因為我一開始的邏輯都寫好了&#xff0c;詢價就是在點擊快遞公司彈出彈框的時…

基于EFISH-SCB-RK3576/SAIL-RK3576的消防機器人控制器技術方案?

&#xff08;國產化替代J1900的應急救援智能化解決方案&#xff09; 一、硬件架構設計? ?極端環境防護系統? ?防爆耐高溫設計?&#xff1a; 采用陶瓷纖維復合裝甲&#xff08;耐溫1200℃持續1小時&#xff09;&#xff0c;通過GB 26784-2023消防設備防爆認證IP68防護等級…

企業開發工具git的使用:從入門到高效團隊協作

前言&#xff1a;本文介紹了Git的安裝、本地倉庫的創建與配置&#xff0c;以及工作區、暫存區和版本庫的區分。詳細講解了版本回退、撤銷修改等操作&#xff0c;并深入探討了分支管理&#xff0c;包括分支的創建、切換、合并、刪除及沖突解決。此外&#xff0c;還介紹了遠程操作…

Java反射機制詳解:原理、應用與實戰

一、反射機制概述 Java反射(Reflection)是Java語言的一個強大特性&#xff0c;它允許程序在運行時(Runtime)獲取類的信息并操作類或對象的屬性、方法等。反射機制打破了Java的封裝性&#xff0c;但也提供了極大的靈活性。 反射的核心思想&#xff1a;在運行時而非編譯時動態獲…

成功案例丨從草圖到鞍座:用先進的發泡成型仿真技術變革鞍座制造

案例簡介 在鞍座制造中&#xff0c;聚氨酯泡沫成型工藝是關鍵環節&#xff0c;傳統依賴實驗測試的方法耗時且成本高昂。為解決這一問題&#xff0c;意大利自行車鞍座制造商 Selle Royal與Altair合作&#xff0c;采用Altair Inspire PolyFoam軟件進行發泡成型仿真。 該工具幫助團…

隧道結構安全在線監測系統解決方案

一、方案背景 隧道是地下隱蔽工程&#xff0c;會受到潛在、無法預知的地質因素影響。隨著我國公路交通建設的發展&#xff0c;隧道占新建公路里程的比例越來越大。隧道屬于線狀工程&#xff0c;有的規模較大&#xff0c;可長達幾公里或數十公里&#xff0c;往往穿越許多不同環境…

選錯方向太致命,華為HCIE數通和云計算到底怎么選?

現在搞HCIE的兄弟越來越多了&#xff0c;但“數通和云計算&#xff0c;到底考哪個&#xff1f;”這問題&#xff0c;依舊讓不少人頭疼。 一個是華為認證的老牌王牌專業——HCIE數通&#xff0c;穩、系統、崗位多&#xff1b; 一個是新趨勢方向&#xff0c;貼合云原生、數字化…

相機基礎常識

相機基礎常識 相機中顏色濾鏡的作用&#x1f3a8; 1. **捕捉彩色圖像**? 最常見的顏色濾鏡陣列是 **拜耳濾鏡&#xff08;Bayer Filter&#xff09;**&#xff1a; &#x1f50d; 2. **實現特定的圖像效果或分析功能**? 常見的濾鏡類型包括&#xff1a; &#x1f6e0;? 3. *…

paddle ocr本地化部署進行文字識別

一、Paddle 簡介 1. 基本概念 Paddle&#xff08;全稱 PaddlePaddle&#xff0c;飛槳&#xff09;是百度開發的 開源深度學習平臺&#xff0c;也是中國首個自主研發、功能豐富、技術領先的工業級深度學習平臺。它覆蓋了深度學習從數據準備、模型訓練、模型部署到預測的全流程…

開源AI大模型等“神秘組合”,如何顛覆零售業數字化轉型?

基于開源AI大模型、AI智能名片與S2B2C商城小程序源碼的零售行業數字化轉型新路徑研究 摘要&#xff1a;在業界將企業數字化轉型劃分為管理數字化、工業數字化和營銷數字化三大部分的背景下&#xff0c;國內大型制造企業在ERP與工業4.0洗禮下正邁向智能型發展道路。而零售行業面…