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

embedded/2024/12/4 15:46:03/

异步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.ppmy.cn/embedded/142935.html

相关文章

7、硬盘品牌分类介绍:西数 - 计算机硬件品牌系列文章

西数硬盘是由美国西部数据公司生产的硬盘&#xff0c;‌是全球知名的硬盘厂商之一。‌公司始创于1970年&#xff0c;‌并于1988年开始设计和生产硬盘&#xff0c;‌总部设在美国加州Lake Forest。‌西部数据公司拥有全球员工大约两万三千人&#xff0c;‌其生产机构设在马来西亚…

VR眼镜可视化编程:开启医疗信息系统新纪元

一、引言 随着科技的飞速发展&#xff0c;VR 可视化编程在医疗信息系统中的应用正逐渐成为医疗领域的新趋势。它不仅为医疗教育、手术培训、疼痛管理等方面带来了新的机遇&#xff0c;还在提升患者体验、推动医疗信息系统智能化等方面发挥着重要作用。 在当今医疗领域&#xf…

SQL面试题——抖音SQL面试题 每分钟最大在线人数

SQL面试题——抖音SQL面试题 每分钟最大在线人数 前面我们计算平台的最大在线人数,可以参考我们之前的文章,而且我们当时提出了两种思路,当然有一种是有问题的 SQL面试题——抖音SQL面试题 最大在线用户数 最终的核心思想是理解为一个水池子,有流进来的水的同时有流出去…

【jvm】C1编译器

目录 1. 说明2. 作用3. 特点4. 编译流程5. C1编译器与分层编译6. C1编译器的相关参数 1. 说明 1.JVM&#xff08;Java Virtual Machine&#xff09;C1编译器是Java虚拟机中的一个即时编译器&#xff08;Just-In-Time Compiler&#xff0c;JIT&#xff09;&#xff0c;也称为Cl…

【方案三】JAVA中使用ocr(Umi-OCR)

目录 前言&#xff1a; 需求&#xff1a; 代码&#xff1a; 难点&#xff1a; 参考文档&#xff1a; 前言&#xff1a; 前两个方案都是自己做着玩儿的&#xff0c;实际运用到上线项目是要收费的&#xff0c;该方案使用的是免费开源的工具&#xff0c;就算运用到商业项目也…

服务发布策略:包括蓝绿部署、A/B测试以及金丝雀发布

原文地址:https://help.aliyun.com/zh/mse/use-cases/service-release-strategies?spma2c4g.11186623.help-menu-123350.d_3_2_3.460a525dLQbFlG&scm20140722.H_375329._.OR_help-T_cn#DAS#zh-V_1 蓝绿部署 蓝绿部署需要对服务的新版本进行冗余部署&#xff0c;一般新版…

Github提交Pull Request教程 Git基础扫盲(零基础易懂)

1 PR是什么&#xff1f; PR&#xff0c;全称Pull Request&#xff08;拉取请求&#xff09;&#xff0c;是一种非常重要的协作机制&#xff0c;它是 Git 和 GitHub 等代码托管平台中常见的功能&#xff0c;被广泛用于参与社区贡献&#xff0c;从而促进项目的发展。 PR的整个过…

滑动窗口最大值

滑动窗口最大值 1、题目描述2、解答思路 1、题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。 2、解答思路 本题需要…