C++ 网络编程学习二

C++ 网络编程学习二

    • asio异步写操作
    • asio异步读操作
    • asio 异步echo服务端
      • asio异步服务器中存在的隐患


asio异步写操作

  • async_write_some是异步写的函数:传入buffer和回调函数以及参数以后,发送后会调用回调函数。
void Session::WriteToSocketErr(const std::string& buf) {// make_shared 延长_send_node 的生命周期。_send_node = make_shared<MsgNode>(buf.c_str(), buf.length());//异步发送数据,因为异步所以不会一下发送完//async_write_some的回调函数要求是两个参数的:发送多少,以及可能返回的错误码,都作为参数传递给回调函数。/*但是自己定义的函数参数为3个,通过bind将三个参数转换为两个参数的普通函数。*/this->_socket->async_write_some(asio::buffer(_send_node->_msg, _send_node->_total_len),std::bind(&Session::WriteCallBackErr,this, std::placeholders::_1, std::placeholders::_2, _send_node));
}
  • 回调函数需要判断是否所有数据都发送完成了,如果没有,继续调用回调函数进行发送。
void Session::WriteCallBackErr(const boost::system::error_code& ec, std::size_t bytes_transferred,std::shared_ptr<MsgNode> msg_node)
{if (bytes_transferred + msg_node->_cur_len < msg_node->_total_len) {_send_node->_cur_len += bytes_transferred;this->_socket->async_write_some(asio::buffer(_send_node->_msg+_send_node->_cur_len,_send_node->_total_len-_send_node->_cur_len),std::bind(&Session::WriteCallBackErr,this, std::placeholders::_1, std::placeholders::_2, _send_node));}
}
  • 上面的代码中存在的问题:异步发送的过程是无序的,用户想发送数据的时候就调用WriteToSocketErr,或者循环调用WriteToSocketErr,很可能在一次没发送完数据还未调用回调函数时再次调用WriteToSocketErr。那么有可能出现发送数据的顺序和想要的顺序不同。
  • 为了确保发送数据的顺序正确性,在应用层使用一个队列,保证发送顺序。并用一个标志位用来判断当前是否还有数据正在发送过程中。
class Session{
public:// 函数参数和上面的参数并无差别void WriteCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);void WriteToSocket(const std::string &buf);
private:std::queue<std::shared_ptr<MsgNode>> _send_queue; // 发送数据顺序队列std::shared_ptr<asio::ip::tcp::socket> _socket;bool _send_pending;// 判断当前是否有数据正在发送,为true表示一个节点还未发送完。
};
  • 发送函数:发送的时候先把数据插到队列中,回调后,将正在发送标志位置为true。
void Session::WriteToSocket(const std::string& buf) {// 将要发送的数据插入队列_send_queue.emplace(new MsgNode(buf.c_str(), buf.length()));if (_send_pending) { //还有数据在发送的话,就先returnreturn;}//异步发送数据,因为异步所以不会一下发送完this->_socket->async_write_some(asio::buffer(buf), std::bind(&Session::WriteCallBack,this, std::placeholders::_1, std::placeholders::_2));_send_pending = true; // 调用一次async_write_some,肯定会触发WriteCallBack,这个时候将标志位置为true
}
  • 回调函数:回调函数在执行时,会首先判断队首元素是否全部发送出去了,如果没有就继续发送队首元素。队首元素发送完毕后,继续取出队列中的元素。
void Session::WriteCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred, std::shared_ptr<MsgNode>) {if (ec.value() != 0) {//出错了std::cout << "Error , code is " << ec.value() << " . Message is " << ec.message();return;}// 取出队首元素auto& send_data = _send_queue.front();send_data->_cur_len += bytes_transferred;//数据未发送完, 则继续发送if (send_data->_cur_len < send_data->_total_len) {this->_socket->async_write_some(asio::buffer(send_data->_msg + send_data->_cur_len, send_data->_total_len - send_data->_cur_len),std::bind(&Session::WriteCallBack,this, std::placeholders::_1, std::placeholders::_2));return;}_send_queue.pop();if (_send_queue.empty()) {_send_pending = false;}else {// 队列不为空,继续发送。auto& send_data = _send_queue.front();this->_socket->async_write_some(asio::buffer(send_data->_msg + send_data->_cur_len, send_data->_total_len - send_data->_cur_len),std::bind(&Session::WriteCallBack,this, std::placeholders::_1, std::placeholders::_2));}}
  • async_write_some函数不能保证每次回调函数触发时发送的长度为要总长度,每次都要在回调函数判断发送数据是否完成,asio提供了一个更简单的发送函数async_send,这个函数在发送的长度未达到我们要求的长度时就不会触发回调,所以触发回调函数时要么时发送出错了要么是发送完成了,其内部的实现原理就是帮我们不断的调用async_write_some直到完成发送,所以async_send不能和async_write_some混合使用。
void Session::WriteAllToSocket(const std::string& buf) {// 将要发送的数据插入队列_send_queue.emplace(new MsgNode(buf.c_str(), buf.length()));if (_send_pending) return;// 异步发送数据,数据不会一次发送完,但是只会触发一次回调this->_socket->async_send(asio::buffer(buf), std::bind(&Session::WriteAllCallBack, this, std::placeholders::_1, std::placeholders::_2));_send_pending = true;
}
void Session::WriteAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred) {if (ec.value() != 0) {std::cout << "Error occured! Error code = "<< ec.value()<< ". Message: " << ec.message();return;}//如果发送完,则pop出队首元素_send_queue.pop();//如果队列为空,则说明所有数据都发送完,将pending设置为falseif (_send_queue.empty()) {_send_pending = false;}//如果队列不是空,则继续将队首元素发送if (!_send_queue.empty()) {auto& send_data = _send_queue.front();this->_socket->async_send(asio::buffer(send_data->_msg + send_data->_cur_len, send_data->_total_len - send_data->_cur_len),std::bind(&Session::WriteAllCallBack,this, std::placeholders::_1, std::placeholders::_2));}
}

asio异步读操作

  • 异步读操作和异步的写操作类似同样有async_read_someasync_receive函数,前者触发的回调函数获取的读数据的长度可能会小于要求读取的总长度,后者触发的回调函数读取的数据长度等于读取的总长度。
  • 同样的async_read_someasync_receive不能混用,因为async_receive的底层就是循环调用async_read_some。
void Session::ReadFromSocket() {if (_recv_pending) return;_recv_node = std::make_shared<MsgNode>(RECVSIZE);// 一块一块接收_socket->async_read_some(asio::buffer(_recv_node->_msg, _recv_node->_total_len), std::bind(&Session::ReadCallBack, this,std::placeholders::_1, std::placeholders::_2));_recv_pending = true;
}
void Session::ReadCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred) {_recv_node->_cur_len += bytes_transferred;// 如果没有接受完,继续接收if (_recv_node->_cur_len < _recv_node->_total_len) {_socket->async_read_some(asio::buffer(_recv_node->_msg + _recv_node->_cur_len,_recv_node->_total_len - _recv_node->_cur_len), std::bind(&Session::ReadCallBack, this,std::placeholders::_1, std::placeholders::_2));return;}_recv_pending = false;//指针置空_recv_node = nullptr;
}
  • async_receive:接收完数据后才会调用一次回调。
void Session::ReadAllFromSocket() {if (_recv_pending) {return;}_recv_node = std::make_shared<MsgNode>(RECVSIZE);_socket->async_receive(asio::buffer(_recv_node->_msg, _recv_node->_total_len), std::bind(&Session::ReadAllCallBack, this,std::placeholders::_1, std::placeholders::_2));_recv_pending = true;
}
void Session::ReadAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred) {if (ec.value() != 0) {std::cout << "Error occured! Error code = "<< ec.value()<< ". Message: " << ec.message();return;}_recv_node->_cur_len += bytes_transferred;//将数据投递到队列里交给逻辑线程处理,此处略去//如果读完了则将标记置为false_recv_pending = false;//指针置空_recv_node = nullptr;
}

asio 异步echo服务端

#pragma once
#include<memory>
#include<boost/asio.hpp>
#include<iostream>
#include<queue>
using namespace std;
using namespace boost;
using boost::asio::ip::tcp;// Session类主要是处理客户端消息收发的会话类,简单起见,不考虑粘包问题,也不考虑支持手动调用发送的接口,只以应答的方式发送和接收固定长度(1024字节长度)的数据。
class Session {
public:// 上下文初始化Session,socket绑定上下文Session(boost::asio::io_context& ioc) :_socket(ioc) {}tcp::socket& Socket() {return _socket;}void Start();// 在start中监听客户端的读写
private:// handle_read和handle_write分别为服务器的读回调函数和写回调函数。// 当服务器读数据时,会调用handle_read,在handle_read过程中,要把数据回传给客户端,要写时,会调用handle_write。void handle_read(const boost::system::error_code& error, size_t bytes_transfered);void handle_write(const boost::system::error_code& error);tcp::socket _socket; //_socket为单独处理客户端读写的socket。enum { max_length = 1024 };char _data[max_length]; //_data用来接收客户端传递的数据};//最大报文接收大小
const int RECVSIZE = 1024;// Server类是为服务器接收连接的管理类。
class Server {
public:Server(boost::asio::io_context& ioc, short port);
private:void start_accept();//启动连接描述符,初始化一个acceptor,将要接收连接的acceptor绑定到服务上// 内部就是将accpeptor对应的socket描述符绑定到epoll或iocp模型上,实现事件驱动。void handle_accept(Session* new_session, const boost::system::error_code& error); // 有连接过来的时候,触发回调函数,回调session的数据。boost::asio::io_context& _ioc;// 上下文,不允许被复制被构造。tcp::acceptor _acceptor;
};
void Session::Start() {memset(_data, 0, max_length);// async_read_some 在boost asio底层用的是epoll,把_socket的读事件添加到epoll表里。// 当_socket有读事件就绪的时候,就会触发handle_read,读:对端发送数据,_socket底层的读tcp缓冲区由空变成有数据。_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::handle_read, this, placeholders::_1, placeholders::_2));}// 读的回调函数:客户端发送数据过来,就会调用这个函数
void Session::handle_read(const boost::system::error_code& error, size_t bytes_transfered) {if (!error) {cout << "server receive data is " << _data << endl; // 收到数据// 将收到的数据发送回客户端,就行了。boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transfered),std::bind(&Session::handle_write, this, placeholders::_1)); // 当发送完成后触发handle_write回调函数。}else {cout << "read error" << endl;delete this;}
}// 写回调函数,写回调之后,就要开始读了。
// 触发写回调,是因为tcp有空闲空间,能够把用户态的数据拷贝到tcp缓冲区。
void Session::handle_write(const boost::system::error_code& error) {if (!error) {memset(_data, 0, max_length);// 继续读去吧。_socket.async_read_some(boost::asio::buffer(_data, max_length), std::bind(&Session::handle_read,this, placeholders::_1, placeholders::_2));}else {cout << "write error"<<error.value() << endl;delete this;}
}// 构造函数:_ioc是引用变量,要用初始化列表的方式进行赋值,_acceptor专门捕获连接
Server::Server(boost::asio::io_context& ioc, short port):_ioc(ioc),_acceptor(ioc,tcp::endpoint(tcp::v4(),port)) {cout << "Server start success, on port: " << port << endl;start_accept();
}void Server::start_accept() {Session* new_session = new Session(_ioc); // 定义新的session// 新的连接到来以后,调用handle_accept// placeholders::_1 占位符,是asio api要求的错误码。// asio就会通过placeholders::_1 这个占位符,把错误码发送给handle_accept函数。_acceptor.async_accept(new_session->Socket(),std::bind(&Server::handle_accept, this, new_session, placeholders::_1));
}void Server::handle_accept(Session* new_session, const boost::system::error_code& error) {if (!error) {new_session->Start();}else {delete new_session;}start_accept();//再次调用start_accept(),继续接收,不能接收完新的连接之后,就什么都不干了。
}

客户端不用写成异步的,因为客户端并不是以并发为主。

asio异步服务器中存在的隐患

  • 服务器即将发送数据前(调用async_write前),此刻客户端中断
  • 服务器此时调用async_write会触发发送回调函数,判断ec为非0进而执行delete this逻辑回收session
  • 但要注意的是客户端关闭后,在tcp层面会触发读就绪事件,服务器会触发读事件回调函数。在读事件回调函数中判断错误码ec为非0,进而再次执行delete操作,从而造成二次析构,这是极度危险的。

参考列表
https://www.bilibili.com/video/BV15P411S7fp/?p=7&spm_id_from=pageDriver

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

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

相关文章

什么是小红书品牌笔记,如何做好达人投放?

认认真真撰写了小红书品牌笔记&#xff0c;从各个方面确保了笔记的优质程度&#xff0c;却没能产生爆文。这很可能是笔记与用户之间的中间环节出了问题&#xff0c;也就是达人投放有问题。今天我们就为大家带来什么是小红书品牌笔记&#xff0c;如何做好达人投放&#xff1f; 一…

【c语言】if 选择语句

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&…

rk3568 I2C

rk3568 I2C I2C(Inter-Integrated Circuit)总线是一种串行通信协议,由Philips(现在的NXP)公司于1980年代初开发。它最初被设计用于连接不同的集成电路和集成模块,以降低系统成本和复杂度。随着时间的推移,I2C总线已经成为连接各种设备的主要标准之一。I2C总线最初只支持…

java设计模式的总结

作用 设计模式是一套被反复使用、经过验证、解决特定问题的最佳实践。它们提供了一种通用的语言&#xff0c;让软件开发者能够更轻松地理解和设计复杂的系统。设计模式有助于提升代码的可读性、可维护性、可扩展性和灵活性。 总结 现知设计模式总共可以分为三种目的型设计模…

SQL--字符串反转函数 reverse() 简单明了

字符串反转函数&#xff1a;reverse 语法: reverse(string A) 返回值: string 说明&#xff1a;返回字符串A的反转结果 举例&#xff1a; hive> select reverse(abcedfg); gfdecba

Navicat的使用

1. Navicat介绍 Navicat: 是一款流行的数据库管理和开发工具, 它支持多种数据库系统, 如: MySQL, MariaDB, MongoDB, SQL Server, Oracle, PostgreSQL 等. Navicat通过提供直观的图形用户界面(GUI), 使用户能够更轻松地执行各种数据库操作, 而无需记住复杂的SQL命令.以下是Nav…

PyQt5图片浏览器

PyQt5图片浏览器 实现方式功能实现具体代码 界面实现pillow源码修改ImageQt错误主页面布局 项目开源地址 分享一个图片浏览器 实现方式 qt本身有一个QGraphicsView类用来当做视图框架。 具体参考&#xff1a;如何在pyqt中使用 QGraphicsView 实现图片查看器 不过大佬给的例子…

聚集高速托盘类四向穿梭车ASRV|一车跑全仓可获得10000个货位的HEGERLS智能搬运机器人

随着国内外制造业加速转型升级&#xff0c;越来越多的企业需要进行物流智能化升级&#xff0c;但是往往受到仓库面积、高度、形状等现实条件的限制&#xff0c;以及市场不确定性因素的影响。因此&#xff0c;相对于投资传统的自动化立体库&#xff0c;企业更倾向于选择智能化、…

【深度学习数学工具】K-FAC:Kronecker-factored Approximate Curvature

Kronecker-factored Approximate Curvature (K-FAC) 是一种优化深度神经网络的先进方法&#xff0c;特别适用于大规模网络。K-FAC通过近似神经网络的Hessian矩阵的结构&#xff0c;以更有效率和准确性的方式更新网络权重。这种方法基于一个关键的观察&#xff1a;深度网络的Hes…

如何在 HTML 中嵌套、引入其他的 HTML?

在日常开发中&#xff0c;书写的 HTML 页面往往不是独立或互不通用&#xff0c;通常是有些头部、尾部或者其他部分是公用的&#xff0c;但是 HTML 有不同 JSP 页面可以使用类似 include 这样的动作标记&#xff0c;所以接下来介绍二种 HTML 页面引入其他 HTML 页面的方法。 1.…

机器学习-01-课程目标与职位分析

总结 本系列是机器学习课程的第01篇&#xff0c;主要介绍本门课程的课程目标与职位分析 教材 数据挖掘与机器学习 课程教学方法 布鲁姆教学法 认知领域&#xff08;cognitive domain&#xff09; 1.知道&#xff08;知识&#xff09;&#xff08;knowledge&#xff09; 是指…

kylin v10 升级 openssl、openssh

升级 openssl 一、查看当前安装的版本 # openssl version OpenSSL 1.0.2g 1 Mar 2016注意&#xff1a;不要卸载旧版本&#xff0c;会出依赖方面的问题&#xff01; 二、下载 wget https://www.openssl.org/source/openssl-1.1.1g.tar.gz三、编译 tar zxvf openssl-1.1.1g…

细嗦MySQL三大日志

文章目录 三大日志&#xff1a;binlog&#xff08;归档日志&#xff09;、redo log&#xff08;重做日志&#xff09;、undo log&#xff08;回滚日志&#xff09;redo log刷盘机制日志文件组 binlog记录格式写入机制 两阶段提交undo log提供回滚操作提供MVCC&#xff08;多版本…

CSS常见的选择器介绍

CSS&#xff08;层叠样式表&#xff09;选择器是一种模式&#xff0c;用于选择要应用样式的HTML元素。以下是一些常见的CSS选择器类型和实际应用样例供参考&#xff1a; 1、元素选择器&#xff1a; 直接通过HTML元素名称选择元素。例如&#xff0c;p选择所有<p>元素。 …

MySQL基本知识

目录 一&#xff0c;MySQL的元数据库 1.1.什么是元数据库 1.2.有哪些元数据库 1.3.切换数据库 二&#xff0c;账户管理 2.1.设置权限 2.2.授权用户 2.3.查看权限 2.4.撤销权限 三&#xff0c;MySQL引擎 3.1什么是数据库引擎 3.2.查看数据引擎 3.3.MyISAM引擎 3.4…

科技云报道:黑马Groq单挑英伟达,AI芯片要变天?

科技云报道原创。 近一周来&#xff0c;大模型领域重磅产品接连推出&#xff1a;OpenAI发布“文字生视频”大模型Sora&#xff1b;Meta发布视频预测大模型 V-JEPA&#xff1b;谷歌发布大模型 Gemini 1.5 Pro&#xff0c;更毫无预兆地发布了开源模型Gemma… 难怪网友们感叹&am…

【Excel PDF 系列】POI + iText 库实现 Excel 转换 PDF

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言转换前后效果引入 pom 配置代码实现 前言 最近遇到生成 Excel 并转 pdf 的需求&#xff0c;磕磕碰碰总…

stm32——hal库学习笔记(DMA实验)

一、DMA介绍&#xff08;了解&#xff09; 二、DMA结构框图介绍&#xff08;熟悉&#xff09; 三、DMA相关寄存器介绍&#xff08;熟悉&#xff09; 四、DMA相关HAL库驱动介绍&#xff08;掌握&#xff09; 五、DMA配置步骤&#xff08;掌握&#xff09; 六、编程实战&#xff…

Anaconda和TensorFlow环境搭建!!

Anaconda下载 进入官网下载 https://www.anaconda.com/download 也可以通过清华的映像站下载&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 我这里下载的是3.4.20版本。下载好就可以安装默认安装就行。 打开Anaconda Prompt修改成国内镜像 conda c…

大概了解一下G1收集器

在上一篇文章中&#xff08;链接&#xff1a;大概了解一下CMS收集器&#xff09;我们提到&#xff0c;CMS是一种主要针对旧生代对象进行回收的收集器。与CMS不同&#xff0c;G1号称“全功能的垃圾收集器”&#xff0c;对初生代内存和旧生代内存均进行管理。鉴于此&#xff0c;这…