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_some
和async_receive
函数,前者触发的回调函数获取的读数据的长度可能会小于要求读取的总长度,后者触发的回调函数读取的数据长度等于读取的总长度。 - 同样的
async_read_some
和async_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
相关文章:
C++ 网络编程学习二
C 网络编程学习二 asio异步写操作asio异步读操作asio 异步echo服务端asio异步服务器中存在的隐患 asio异步写操作 async_write_some是异步写的函数:传入buffer和回调函数以及参数以后,发送后会调用回调函数。 void Session::WriteToSocketErr(const st…...

SpringMVC 学习(四)之获取请求参数
目录 1 通过 HttpServletRequest 获取请求参数 2 通过控制器方法的形参获取请求参数 3 通过 POJO 获取请求参数(重点) 1 通过 HttpServletRequest 获取请求参数 public String handler1(HttpServletRequest request) <form action"${pageCont…...

多模态表征—CLIP及中文版Chinese-CLIP:理论讲解、代码微调与论文阅读
我之前一直在使用CLIP/Chinese-CLIP,但并未进行过系统的疏导。这次正好可以详细解释一下。相比于CLIP模型,Chinese-CLIP更适合我们的应用和微调,因为原始的CLIP模型只支持英文,对于我们的中文应用来说不够友好。Chinese-CLIP很好地…...
Git本地分支关联远程分支
Git本地分支关联远程分支 本地分支相关操作 查看本地分支 git branch新建本地分支 git branch name切换本地分支 git checkout name新建本地分支并切换到该分支 git checkout -b name #或 git branch name删除本地分支 git branch -d name git branch -D name #强制删除远程分…...
[FT]chatglm2微调
1.准备工作 显卡一张:A卡,H卡都可以,微调需要一张,大概显存得30~40G吧环境安装: 尽量在虚拟环境安装:参见,https://blog.csdn.net/u010212101/article/details/103351853环境安装参见ÿ…...

AI赋能Oracle DBA:以自然语言与Oracle数据库互动
DBA AI助手:以自然语言与Oracle数据库互动 0. 引言1. AI赋能Oracle DBA的优势2. AI如何与Oracle数据库交互3. 自然语言查询的一些示例4. 未来展望 0. 引言 传统的Oracle数据库管理 (DBA) 依赖于人工操作,包括编写复杂的SQL语句、分析性能指标和解决各种…...

Django学习记录04——靓号管理整合
1.靓号表 1.1 表结构 1.2 靓号表的构造 class PrettyNum(models.Model): 靓号表 mobile models.CharField(verbose_name"手机号", max_length11)# default 默认值# null true,blank true 允许为空price models.IntegerField(verbose_name"价…...

AD9226 65M采样 模数转换
目录 AD9220_ReadTEST AD9220_ReadModule AD9226_TEST_tb 自己再写个 260M的时钟,四分频来提供65M的时钟。 用 vivado 写的 AD9226_ReadTEST module AD9226_ReadTEST( input clk, input rstn,output clk_driver, //模块时钟管脚 input [12:0]IO_data, //模块数…...
远程控制桌面,让电脑办公更简单
随着科技的不断发展,远程办公已经成为了越来得越多企业和个人的选择。远程控制电脑办公,仅需1款软件即可轻松get! 1.绿虫电脑管理软件 是一款功能强大的办公电脑管理软件,仅需安装在被控端电脑,主控端通过网页登录后…...

猫头虎分享已解决Bug || 网络连接问题:NetworkError: Failed to fetch
博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …...

Layer1 明星项目 Partisia Blockchain 何以打造互操作、可创新的数字经济网络
我们的目标是创建一个以用户为中心的全新数字经济网络:在去信任化和公平透明的环境下,所有的隐私数据都能够得到天然保障,企业、用户等各角色的协作与共享将会更顺利地进行。 —— Partisia Blockchain 团队 作为一个以 Web3 安全为技术方向的…...
用CSS制作弧形卡片的三种创意方法!
在平时开发中,有时候会碰到下面这种“弧形”样式,主要分为“内凹”和“外凸”两种类型,如下 该如何实现呢?或者想一下,有哪些 CSS 属性和“弧形”有关?下面介绍 3 种方式,一起看看吧 一、borde…...
守护健康之光 —— 小脑萎缩患者的生活指南
生活中,我们或许会遇到一些特殊的挑战,而面对这些挑战时,了解和掌握正确的应对策略至关重要。今天,我们要聊一聊一个较为少见却不容忽视的话题——小脑萎缩。这不仅是患者的战役,也是家人和社会共同的关怀课题。下面&a…...
CSS选择器:让样式精确命中目标
CSS选择器:让样式精确命中目标 在网页开发中,CSS选择器是一种强大的工具,它可以帮助我们精确地定位HTML元素,以便为它们应用样式。在这篇博客中,我们将探讨一些常见的CSS选择器,了解它们的功能和使用方法。…...
前端不传被删记录的id怎么删除记录,或子表如何删除记录
1.删除主表相关子表所有记录 2.再保存一次前端传来的记录 3.如果子表是通过先生成空记录,再put修改模式,可以在执行1和2两步后再拿模板集合和当前现有子表集合套两个for循环对比判断,count记录模板记录和子表记录每次循环重合次数ÿ…...
axios的基本特性用法
1. axios的基本特性 axios 是一个基于Promise用于浏览器和node.js的HTTP客户端。 它具有以下特征: 支持浏览器和node.js支持promiseAPI自动转换JSON数据能拦截请求和响应请求转换请求数据和响应数据(请求是可以加密,在返回时也可进行解密&…...

打印水仙花数---c语言刷题
欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,欢迎指出~ 题述 求出0~100000之间的所有“水仙花数”并输出。 “水仙花数”是指一个n位数,其各位数字的n次方之和确好等于该数本身,如:153&#…...

springboot基础(82):分布式定时任务解决方案shedlock
文章目录 前言简介shedlock dbSchedulerLock注解说明 shedlock redis遇到的问题1.配置shedlock不生效2.报错net/javacrumbs/shedlock/core/LockProvider shedlock升级高版本同名定时任务 前言 多节点或者多服务器拥有相同的定时任务,这种情况下,不同节…...
【Golang】Gorm乐观锁optimisticlock的使用
在数据库操作中,为了保证数据的一致性和完整性,常常需要采取一些措施来防止并发操作导致的数据冲突。悲观锁和乐观锁是两种常见的并发控制机制。 悲观锁(Pessimistic Lock) 悲观锁的基本假设是,数据在并发访问时很可能…...

Apache Doris 发展历程、技术特性及云原生时代的未来规划
本文节选自《基础软件之路:企业级实践及开源之路》一书,该书集结了中国几乎所有主流基础软件企业的实践案例,由 28 位知名专家共同编写,系统剖析了基础软件发展趋势、四大基础软件(数据库、操作系统、编程语言与中间件…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...