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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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洗礼下正迈向智能型发展道路。而零售行业面…