【C++boost::asio网络编程】有关异步Server样例以及伪闭包延长连接生命周期方法的笔记

异步Server

  • 客户端源码
  • Session类
    • start函数
    • handle_read
    • handle_write
  • Server类
    • 构造函数
    • start_accept
    • handle_accept
  • 可能会造成的隐患
  • 利用伪闭包延长连接的生命周期

客户端源码

#include <iostream>
#include <boost/asio.hpp>
#include <string>
int main()
{try{boost::asio::io_context ioc;std::string ip = "127.0.0.1";unsigned short port = 8888;boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port);boost::asio::ip::tcp::socket sock(ioc, ep.protocol());boost::system::error_code ec = boost::asio::error::host_not_found;sock.connect(ep,ec);if (ec.value() != 0){std::cout << ec.value() << ":" << ec.message() << std::endl;return ec.value();}const int MAX_SIZE = 1024;char request[MAX_SIZE];std::cout << "Enter Message:";std::cin.getline(request, MAX_SIZE);size_t request_length = strlen(request);boost::asio::write(sock, boost::asio::buffer(request, request_length));//收到服务端的消息char response[MAX_SIZE];memset(response, '\0', MAX_SIZE);sock.read_some(boost::asio::buffer(response, MAX_SIZE));std::cout << "server echo# " << response << std::endl;}catch (std::exception& e){std::cout << e.what() << std::endl;}return 0;
}

Session类

  Session类的作用是服务端为某个已连接的客户端处理消息收发的会话类(在这里先不考虑粘包问题,并采用和客户端进行应答的方式发送和接收一个固定长度的数据)

class Session
{
public:Session(boost::asio::io_context& ioc):_socket(ioc){}boost::asio::ip::tcp::socket& Socket(){return _socket;}void Start();
private:void handle_read(const boost::system::error_code& ec, size_t bytes_transferred);void handle_write(const boost::system::error_code& ec);boost::asio::ip::tcp::socket _socket;const static int max_length = 1024;char _data[max_length];
};

  其中,成员变量_data就是用来存储数据的,而_socket则是用来服务当前客户端的,handle_readhandle_write分别是异步写和异步读的回调函数

start函数

void Session::Start()
{memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2));
}

  在Start函数中,先将_data中的数据清空防止数据污染。然后采用异步读取方式来准备接收客户端发送过来的数据。

handle_read

void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred)
{if (ec.value() == 0){//将读到的数据全部发回去std::cout << "server has received:" << _data << std::endl;boost::asio::async_write(_socket,boost::asio::buffer(_data, bytes_transferred),std::bind(&Session::handle_write, this, std::placeholders::_1));}else{std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;delete this;}
}

  当一次异步读事件结束之后,会调用回调函数handle_read。在这个回调函数中,会先将收到的数据打印出来,然后进行异步写操作。

handle_write

void Session::handle_write(const boost::system::error_code& ec)
{if (ec.value() == 0){memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2));}else{std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;delete this;}
}

  当一次写操作结束之后,又会调用handle_write,此时服务端又开始在准备接收客户端的发送过来的消息

Server类

  Server类为服务器接收连接的管理类

class Server
{
public:Server(boost::asio::io_context& ioc, short port);
private:void start_accept();void handle_accept(Session* new_session,const boost::system::error_code& ec);boost::asio::io_context& _ioc;boost::asio::ip::tcp::acceptor _acceptor;
};

构造函数

Server::Server(boost::asio::io_context& ioc, short port):_ioc(ioc),_acceptor(ioc,boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(),port))
{start_accept();
}

  在构造函数中,先创建一个acceptor对象并绑定ip和端口,然后调用start_accept函数

start_accept

void Server::start_accept()
{Session* session = new Session(_ioc);_acceptor.listen();_acceptor.async_accept(session->Socket(), std::bind(&Server::handle_accept, this, session, std::placeholders::_1));
}

  在start_accept函数中,先创建一个会话类,然后_acceptor对象去监听是否有客户端连接

_acceptor.async_accept(session->Socket(), std::bind(&Server::handle_accept, this, session, std::placeholders::_1));

  以上这段代码就是_acceptor在等待客户端的连接,如果有客户端来连接的话,就用先前创建的会话类对象来管理这个连接,并且调用handle_accept

handle_accept

void Server::handle_accept(Session* new_session, const boost::system::error_code& ec)
{if (ec.value() == 0){new_session->Start();}else{delete new_session;}start_accept();
}

  如果发现没有什么问题的话,就启用这个会话类让它去接收客户端发来的消息并将消息回传回去

可能会造成的隐患

  在实际的应用中,很少会出现异步读和异步写事件只挂起一个的现象,通常情况下,它们是相互独立的,即可能服务端正在接收客户端发来的数据同时,它也正在向客户端发送数据。这样的话,代码就要修改成这样

void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred)
{if (ec.value() == 0){//将读到的数据全部发回去memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2));std::cout << "server has received:" << _data << std::endl;boost::asio::async_write(_socket,boost::asio::buffer(_data, bytes_transferred),std::bind(&Session::handle_write, this, std::placeholders::_1));}else{std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;delete this;}
}

  即在读回调事件处理完之后,又同时挂起一个读事件和一个写事件(暂不考虑数据冲突的问题)。但是如果在服务端正要写数据的时候,客户端关闭连接,就会同时触发读回调和写回调,

在这里插入图片描述

利用伪闭包延长连接的生命周期

在这里采用了智能指针的方式来构造一个伪闭包用来延长session对象的生命周期

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> new_session,const boost::system::error_code& ec);boost::asio::io_context& _ioc;boost::asio::ip::tcp::acceptor _acceptor;std::map<std::string, std::shared_ptr<Session>> _sessions;
};
void Server::ClearSession(std::string uuid)
{_sessions.erase(uuid);
}

  在Server类中添加了一个map结构用来管理每个SessionClearSession函数用来将sessionmap中清除出去

class Server;
class Session:public std::enable_shared_from_this<Session>
{
public:Session(boost::asio::io_context& ioc,Server* server):_socket(ioc),_server(server){boost::uuids::uuid a_uuid = boost::uuids::random_generator()();_uuid = boost::uuids::to_string(a_uuid);}boost::asio::ip::tcp::socket& Socket(){return _socket;}const std::string& uuid();void Start();
private:void handle_read(const boost::system::error_code& ec, size_t bytes_transferred,std::shared_ptr<Session> self_shared);void handle_write(const boost::system::error_code& ec, std::shared_ptr<Session> self_shared);boost::asio::ip::tcp::socket _socket;const static int max_length = 1024;char _data[max_length];Server* _server;std::string _uuid;
};

  在Session类中,首先在构造函数中构造一个uuid用来为每个会话类对象创建一个“key值”以方便Server对每个连接进行管理

void Session::Start()
{memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2,shared_from_this()));
}

  在Start函数中,由于handle_read函数和handle_write函数添加了指向自身的智能指针,所以在bind操作的最后一个参数采用了shared_from_this的写法用来确保每个智能指针共享的是同一份引用计数

void Session::handle_read(const boost::system::error_code& ec, size_t bytes_transferred, std::shared_ptr<Session> self_shared)
{if (ec.value() == 0){//memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2,self_shared));//将读到的数据全部发回去std::cout << "server has received:" << _data << std::endl;boost::asio::async_write(_socket,boost::asio::buffer(_data, bytes_transferred),std::bind(&Session::handle_write, this, std::placeholders::_1,self_shared));}else{std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;//delete this;_server->ClearSession(_uuid);}
}void Session::handle_write(const boost::system::error_code& ec, std::shared_ptr<Session> self_shared)
{if (ec.value() == 0){memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, std::placeholders::_1, std::placeholders::_2,self_shared));}else{std::cout << "error code:" << ec.value() << " error message:" << ec.message() << std::endl;//delete this;_server->ClearSession(_uuid);}
}

  在handle_readhandle_write函数中,都取消了之前直接delete的做法而是采用将当前对象从Servermap中清除出去,如果map中没有找到当前对象就当作无事发生。这样写的好处是最大程度上保证了session对象的生命周期和当前所在函数的生命周期至少是一致的,不会发生当前正在使用一个对象,但是这个对象已经在其他函数内部被释放了造成异常的现象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/61781.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实时数据开发 | Flink的数据分区策略--物理分区操作

物理分区操作 物理分区(physica1partitioning)操作的作用是根据指定的分区策略将数据重新分限到不同节点的 Task 实例上执行。当使用DataSteam提供的 API对数据处理过程中&#xff0c;赖于算子本身对数据的分区控制&#xff0c;如果用户希望自己控制数据分区&#xff0c;例如当…

力扣hot100道【贪心算法后续解题方法心得】(三)

力扣hot100道【贪心算法后续解题方法心得】 十四、贪心算法关键解题思路1、买卖股票的最佳时机2、跳跃游戏3、跳跃游戏 | |4、划分字母区间 十五、动态规划什么是动态规划&#xff1f;关键解题思路和步骤1、打家劫舍2、01背包问题3、完全平方式4、零钱兑换5、单词拆分6、最长递…

【linux】(23)对象存储服务-MinIo

MinIO 是一个高性能的对象存储服务&#xff0c;兼容 Amazon S3 API。 Docker安装MinIo 前提条件 确保您的系统已经安装了 Docker。如果还没有安装 Docker&#xff0c;可以参考 Docker 官方文档进行安装。 1. 拉取 MinIO Docker 镜像 首先&#xff0c;从 Docker Hub 拉取 Mi…

MySQL有哪些日志?

MySQL主要有三种日志&#xff1a;undo log、redo log、binlog。前两种是InnoDB特有的&#xff0c;binlog是MySQL的Server层中的。 Buffer Pool buffer pool是MySQL的缓冲池&#xff0c;里面存储了数据页、索引页、undo页等&#xff08;与数据库不一致的即为脏页&#xff09;。…

机器学习周志华学习笔记-第13章<半监督学习>

机器学习周志华学习笔记-第13章&#xff1c;半监督学习&#xff1e; 卷王&#xff0c;请看目录 13半监督学习13.1 生成式方法13.2 半监督SVM13.3 基于分歧的方法13.4 半监督聚类 13半监督学习 前面我们一直围绕的都是监督学习与无监督学习&#xff0c;监督学习指的是训练样本包…

ThingsBoard集成外部工单系统方案

在 ThingsBoard 的仪表盘中集成和使用 Jitbit Helpdesk&#xff0c;需要结合 Jitbit 提供的 REST API 和 ThingsBoard 的自定义 小部件&#xff08;Widgets&#xff09; 功能。以下是详细的实现步骤&#xff1a; 1. 确定集成场景 在仪表盘中集成 Jitbit Helpdesk 的常见需求包…

SpringCloud框架学习(第六部分:Sentinel实现熔断与限流)

目录 十四、SpringCloud Alibaba Sentinel实现熔断与限流 1.简介 2.作用 3.下载安装 4.微服务 8401 整合 Sentinel 入门案例 5.流控规则 &#xff08;1&#xff09;基本介绍 &#xff08;2&#xff09;流控模式 Ⅰ. 直接 Ⅱ. 关联 Ⅲ. 链路 &#xff08;3&#xff0…

【Java基础面试题009】Java的I/O流是什么?

相关知识补充&#xff1a;黑马-字符集、IO流&#xff08;一&#xff09;.pdf Autism_Btkrsr/Blog_md_to_pdf - 码云 - 开源中国 (gitee.com) 黑马-IO流&#xff08;二&#xff09;.pdf Autism_Btkrsr/Blog_md_to_pdf - 码云 - 开源中国 (gitee.com) 回答重点 Java的I/O&…

第六届国际科技创新学术交流会暨管理科学信息化与经济创新发展(MSIEID 2024)

重要信息 大会官网&#xff1a;msieid2024.iaecst.org &#xff08;点击了解大会&#xff0c;参会等内容&#xff09; 大会时间&#xff1a;2024年12月6-8日 大会地点&#xff1a;中国-广州 大会简介 随着全球化和信息化的不断深入&#xff0c;管理科学、信息化和经济发展…

【计算机视觉算法与应用】模板匹配、图像配准

目录 1. 基于灰度值的模板匹配 2. 基于相关性的模板匹配 3. 基于形状的模板匹配 4. 基于组件的模板识别 5. 基于形变的模板匹配 6. 基于描述符的模板匹配 7. 基于点的模板匹配 性能比较 模板匹配的算法实现需要结合具体需求和应用场景来选择方法。以下是基于 OpenCV 的…

【25春招前端八股文】——JS数据类型检测方式

检测数据类型 # typeof 总结&#xff1a;数组、对象、null都会被判断为object&#xff0c;其他判断都正确的类型。 可以检测基本数据类型null会检测为Object&#xff0c;因为null也是一个空的引用对象复杂数据类型只能检测function和Object 情况说明&#xff1a; 数组&#x…

python学opencv|读取视频(一)灰度视频制作和保存

【1】引言 上一次课学习了用opencv读取图像&#xff0c;掌握了三个函数&#xff1a;cv.imread()、cv.imshow()、cv.imwrite() 相关链接如下&#xff1a; python学opencv|读取图像-CSDN博客 这次课我们继续&#xff0c;来学习用opencv读取视频。 【2】学习资源 首先是官网…

题外话 (火影密令)

哥们&#xff01; 玩火影不&#xff01; 村里人全部评论&#xff01; 不评论的忍战李全保底&#xff01; 哥们&#xff01; 密令领了不&#xff01; “1219村里人集合”领了吗&#xff01; 100金币&#xff01; 哥们&#xff01; 我粉丝没人能上影&#xff01; 老舅说的…

Go学习笔记之数据类型转换

Go数据类型转换 整型与浮点型转换 package mainimport ("fmt""strconv" )func main() {// 类型转换建议是从低位的类型转换到高位的类型,比如从int转换到float64,从float32转换到float64d : 10f : 3.14fmt.Println(float64(d) f)}其他类转换成字符串 //…

001-SpringBoot整合日志

SpringBoot整合日志 一、引入依赖二、配置 application.yml三、配置文件 logback.xml四、配置文件 WebConfigurerAdapter五、配置常量文件六、配置拦截器七、效果展示一、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId&…

Rust 图形界面开发——使用 GTK 创建跨平台 GUI

第五章 图形界面开发 第一节 使用 GTK 创建跨平台 GUI GTK&#xff08;GIMP Toolkit&#xff09;是一个流行的开源跨平台图形用户界面库&#xff0c;适用于创建桌面应用程序。结合 Rust 的 gtk-rs 库&#xff0c;开发者能够高效地构建现代化 GUI 应用。本节将详细探讨 GTK 的…

Linux的磁盘/文件管理,以centos7为例

Linux的磁盘/文件管理,以centos7为例 物理磁盘情况我的磁盘简述一下含义 使用新硬盘流程简述硬盘分区格式化(文件系统(本地文件系统))创建挂载点;挂载磁盘挂载解释 LVM(Logical Volume Management)硬盘到文件系统的层次结构LVM优势:重要概念实践操作 附录(命令) 物理磁盘情况 …

Conda-Pack打包:高效管理Python环境

在Python开发中&#xff0c;环境管理是一个不可忽视的重要环节。Conda是一个流行的包管理器和环境管理器&#xff0c;它允许用户创建隔离的环境&#xff0c;以避免不同项目之间的依赖冲突。Conda-pack是一个工具&#xff0c;可以帮助我们将一个conda环境打包成一个可移植文件&a…

人形机器人训练、机器臂远程操控、VR游戏交互、影视动画制作,一副手套全部解决!

广州虚拟动力基于自研技术推出了多节点mHand Pro动捕数据手套&#xff0c;其最大的特点就是功能集成与高精度捕捉&#xff0c;可以用于人形机器人训练、机器臂远程操控、VR游戏交互、影视动画制作等多种场景。 一、人形机器人训练 mHand Pro动捕数据手套双手共装配16个9轴惯性…

vue3+view-ui-plus+vite+less 实现自定义iview样式

首先是结论&#xff1a; "less": "^2.7.3", "less-loader": "^4.1.0", vite.config.js resolve: {alias: {// 设置路径~: path.resolve(__dirname, ./),// 设置别名: path.resolve(__dirname, ./src)},extensions: [.mjs, .js, .ts…