C++ 网络编程学习五
C++网络编程学习五
- 网络结构的更新
- 单例模式
- 懒汉单例模式
- 饿汉单例模式
- 懒汉式指针
- 智能指针设计单例类
- 服务器优雅退出
- asio的多线程模型IOService
- asio多线程IOThreadPool
- epoll 和 iocp的一些知识点
网络结构的更新
- asio网络层,会使用io_context进行数据封装,底层的话,
在linux中就是epoll模型,在windows就是iocp模型。
- 当服务器的接受数据较多时,又要处理接收到的信息的逻辑处理,逻辑处理一般会放到一个
逻辑处理队列中进行处理。
因为有时候逻辑比较复杂。 - 通过一个队列,单独线程从队列中取出逻辑函数。从而实现网络线程和逻辑线程分开,由一个队列进行连接。极大地提升网络线程的收发能力,并且可以用多线程的方式管理网络层。
- asio的多线程模式:
- 启动n个线程,每个线程都有一个iocontext,每个线程负责一部分的socket。
- 一个ioconext由多个线程共享。也可以一定程度上减轻readhandler的负担。
- 逻辑处理一般都是单线程的,因为大量的用户同时处理一个逻辑过程的时候,频繁地加锁取消锁,还不如就单线程的来做。
完善消息结构:
消息 = 消息id + 消息长度 + 消息内容。 前两部分统一封装到消息头里,tlv格式。消息id占2个字节,消息长度占2个字节,消息头共4个字节。
更新消息节点:将收取消息的类和发送消息的类,继承自消息基类。
//HEAD_TOTAL_LEN = 4 包含id 和 消息长度。
SendNode::SendNode(const char* msg, short max_len, short msg_id):MsgNode(max_len + HEAD_TOTAL_LEN)
, _msg_id(msg_id){//先发送id, 本机字节序转为网络字节序short msg_id_host = boost::asio::detail::socket_ops::host_to_network_short(msg_id);memcpy(_data, &msg_id_host, HEAD_ID_LEN); // 变量按照字节流的方式,写到_data的前两个字节。//消息长度和消息体本身 转为网络字节序short max_len_host = boost::asio::detail::socket_ops::host_to_network_short(max_len);// 消息体长度信息的拷贝。memcpy(_data + HEAD_ID_LEN, &max_len_host, HEAD_DATA_LEN);// 消息体本身的拷贝。memcpy(_data + HEAD_ID_LEN + HEAD_DATA_LEN, msg, max_len);
}
单例模式
懒汉单例模式
- 通过静态成员变量实现单例。
存在隐患,对于多线程方式生成的实例可能是多个。
class Single2 {
private:Single2() {}Single2(const Single2&) = delete;Single2& operator=(const Single2&) = delete;
public:// 静态局部变量实现单例static Single2& GetInst() {static Single2 single; // 生命周期和进程一样,函数的局部静态变量生命周期随着进程结束而结束。return single;}
};
饿汉单例模式
class Single2Hungry
{
private:Single2Hungry(){}Single2Hungry(const Single2Hungry &) = delete;Single2Hungry &operator=(const Single2Hungry &) = delete;
public:static Single2Hungry *GetInst(){if (single == nullptr){single = new Single2Hungry();}return single;}
private:static Single2Hungry *single;
};
// 初始化
Single2Hungry *Single2Hungry::single = Single2Hungry::GetInst();
饿汉式是在程序启动时就进行单例的初始化,这种方式也可以通过懒汉式调用,无论饿汉式还是懒汉式都存在一个问题,就是什么时候释放内存?多线程情况下,释放内存就很难了,还有二次释放内存的风险。
懒汉式指针
单例模式的单例由指针存在,创建单例的时候,用加锁的方式进行判断。防止在加锁的过程中,出现单例类被创建的情况。
class SinglePointer
{
private:SinglePointer(){}SinglePointer(const SinglePointer &) = delete;SinglePointer &operator=(const SinglePointer &) = delete;
public:static SinglePointer *GetInst(){if (single != nullptr){return single;}s_mutex.lock();if (single != nullptr){s_mutex.unlock();return single;}single = new SinglePointer();s_mutex.unlock();return single;}
private:static SinglePointer *single;static mutex s_mutex;
};
智能指针设计单例类
class SingleAuto
{
private:SingleAuto(){}SingleAuto(const SingleAuto&) = delete;SingleAuto& operator=(const SingleAuto&) = delete;
public:~SingleAuto(){cout << "single auto delete success " << endl;}static std::shared_ptr<SingleAuto> GetInst(){if (single != nullptr){return single;}s_mutex.lock();if (single != nullptr){s_mutex.unlock();return single;}single = std::shared_ptr<SingleAuto>(new SingleAuto);s_mutex.unlock();return single;}
private:static std::shared_ptr<SingleAuto> single;static mutex s_mutex;
};
服务器优雅退出
- 服务器退出之前,要把服务器逻辑队列中的服务执行完成。
- asio提供的信号建立的方式,
利用signal_set 定义了一系列信号合集,并且绑定了一个匿名函数,匿名函数捕获了io_context的引用
,并且函数中设置了停止操作,也就是说当捕获到SIGINT,SIGTERM等信号时,会调用io_context.stop
。
int main()
{try {boost::asio::io_context io_context;// 绑定信号 想捕获的信号,都加进去就行了 捕获io_context的收到的信号boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);// 异步等待方式,等待停止信号// 绑定了一个匿名函数,匿名函数捕获了io_context的引用,并且函数中设置了停止操作,也就是说当捕获到SIGINT,SIGTERM等信号时,会调用io_context.stopsignals.async_wait([&io_context](auto, auto) {io_context.stop();});CServer s(io_context, 10086);io_context.run();}catch (std::exception& e) {std::cerr << "Exception: " << e.what() << endl;}
}
asio的多线程模型IOService
-
第一个是启动多个线程,每个线程管理一个iocontext。第二种是只启动一个iocontext,被多个线程共享。
-
启动线程的个数,不要超过核数。
-
每个线程独立调用io_context,一个socket会被注册在同一个io_context里
,它的回调函数也会被单独的一个线程回调:- 那么对于同一个socket,他的回调函数每次触发都是在同一个线程里,
就不会有线程安全问题,网络io层面上的并发是线程安全的。
- 如果两个socket对应的上层逻辑处理,如果有交互或者访问共享区,会存在线程安全问题。可以通过
加锁
或者逻辑队列
的方式解决安全问题。
- 那么对于同一个socket,他的回调函数每次触发都是在同一个线程里,
-
多线程的优势:提升了并发能力, 单线程仅有一个io_context服务用来监听读写事件,就绪后回调函数在一个线程里串行调用。如果一个回调函数的调用时间较长肯定会影响后续的函数调用。
-
通过逻辑队列的方式将网络线程和逻辑线程解耦合,不会出现前一个调用时间影响下一个回调触发的问题。
class AsioIOServicePool :public Singleton<AsioIOServicePool>
{friend Singleton<AsioIOServicePool>; //声明友元,用这个类访问构造函数
public:using IOService = boost::asio::io_context; // 定义别名// 通常会将一些异步操作提交给io_context进行处理,然后该操作会被异步执行,而不会立即返回结果。// 如果没有其他任务需要执行,那么io_context就会停止工作,导致所有正在进行的异步操作都被取消。// 这时,我们需要使用boost::asio::io_context::work对象来防止io_context停止工作。using Work = boost::asio::io_context::work; // work,防止io_context在没有被注册事件的时候退出。using WorkPtr = std::unique_ptr<Work>; //work不被改变。~AsioIOServicePool();AsioIOServicePool(const AsioIOServicePool&) = delete;//不加引用的话,会造成一个递归构造的危险,不允许拷贝构造AsioIOServicePool& operator=(const AsioIOServicePool&) = delete;// 使用 round-robin 的方式返回一个 io_serviceboost::asio::io_context& GetIOService();// 轮询的方式void Stop();// 停止ioservice
private:// 不让外界直接调用AsioIOServicePool(std::size_t size = std::thread::hardware_concurrency()); //根据CPU核数去创建线程数量std::vector<IOService> _ioServices; //IOService的vector变量,用来存储初始化的多个IOService。std::vector<WorkPtr> _works;std::vector<std::thread> _threads;std::size_t _nextIOService;//下标
};// 构造函数,初始化size个ioservice,size个_works, _ioServices绑定到works
// _ioServices放到不同的线程中。
AsioIOServicePool::AsioIOServicePool(std::size_t size) :_ioServices(size),
_works(size), _nextIOService(0) {for (std::size_t i = 0; i < size; ++i) {_works[i] = std::unique_ptr<Work>(new Work(_ioServices[i]));}//遍历多个ioservice,创建多个线程,每个线程内部启动ioservicefor (std::size_t i = 0; i < _ioServices.size(); ++i) {_threads.emplace_back([this, i]() {_ioServices[i].run();// 如果不绑定work,run()就会直接返回了});}
}AsioIOServicePool::~AsioIOServicePool() {std::cout << "destruct" << std::endl;
}// 轮询的方式,每次取出来的ioservice都不同。
boost::asio::io_context& AsioIOServicePool::GetIOService() {auto& service = _ioServices[_nextIOService++]; //取出serviceif (_nextIOService == _ioServices.size()) {_nextIOService = 0;}return service;
}void AsioIOServicePool::Stop() {for (auto& work : _works) {work.reset();// 想要Service停掉,要先work.reset();// work.reset()是让unique指针置空并释放,那么work的析构函数就会被调用,work被析构,其管理的io_service在没有事件监听时就会被释放。}for (auto& t : _threads) {t.join();}
}
asio多线程IOThreadPool
一个IOServicePool开启n个线程和n个iocontext,每个线程内独立运行iocontext, 各个iocontext监听各自绑定的socket是否就绪,如果就绪就在各自线程里触发回调函数。
- IOThreadPool:
初始化一个iocontext用来监听服务器的读写事件,包括新连接到来的监听也用这个iocontext。只是我们让iocontext.run在多个线程中调用
,这样回调函数就会被不同的线程触发,从这个角度看回调函数被并发调用了。 - 线程池统一管理一个io_context,每个线程调用一个io_context,会话session都注册到一个,哪个线程调用了io_context.run,哪个线程去就绪队列取出回调函数。
- 回调函数对同一个session来说就是不安全的。
class AsioThreadPool :public Singleton<AsioThreadPool>
{
public:friend class Singleton<AsioThreadPool>;~AsioThreadPool() {}AsioThreadPool& operator=(const AsioThreadPool&) = delete;AsioThreadPool(const AsioThreadPool&) = delete;boost::asio::io_context& GetIOService();void Stop();
private:AsioThreadPool(int threadNum = std::thread::hardware_concurrency());boost::asio::io_context _service;std::unique_ptr<boost::asio::io_context::work> _work; //没有客人来,我也不会让饭店关门std::vector<std::thread> _threads;
};// 初始化列表进行初始化
AsioThreadPool::AsioThreadPool(int threadNum) :_work(new boost::asio::io_context::work(_service)) {for (int i = 0; i < threadNum; ++i) {_threads.emplace_back([this]() {_service.run();});}
}
线程池里每个线程都会运行_service.run函数
,这就是多线程调用一个io_context的逻辑。- 因为回调函数是在不同的线程里调用的,所以会存在不同的线程调用同一个socket的回调函数的情况。
_service.run 内部在Linux环境下调用的是epoll_wait返回所有就绪的描述符列表
,在windows上会循环调用GetQueuedCompletionStatus函数返回就绪的描述符
,二者原理类似,进而通过描述符找到对应的注册的回调函数,然后调用回调函数。
epoll 和 iocp的一些知识点
IOCP的使用主要分为以下几步:
1 创建完成端口(iocp)对象。
2 创建一个或多个工作线程,在完成端口上执行并处理投递到完成端口上的I/O请求。
3 Socket关联iocp对象,在Socket上投递网络事件。
4 工作线程调用GetQueuedCompletionStatus函数获取完成通知封包,取得事件信息并进行处理。epoll_wait的工作方式:
1 调用epoll_creat在内核中创建一张epoll表。
2 开辟一片包含n个epoll_event大小的连续空间。
3 将要监听的socket注册到epoll表里。
4 调用epoll_wait,传入之前我们开辟的连续空间,epoll_wait返回就绪的epoll_event列表,
epoll会将就绪的socket信息写入我们之前开辟的连续空间。
- 使用这种方式,有可能会存在隐患,不同的线程有可能处理同一块Read回调处理函数,存在网络上的并行。
改进方法:再添加一个strand管理的队列,asio的strand是一个安全队列,里面进行独立的单线程访问。
- 回调处理放在_strand中进行执行。
void CSession::Start(){::memset(_data, 0, MAX_LENGTH);_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH),boost::asio::bind_executor(_strand, std::bind(&CSession::HandleRead, this,std::placeholders::_1, std::placeholders::_2, SharedSelf())));
}
- IOThreadPool相比于IOServicePool,速度慢一些。
参考列表
https://www.bilibili.com/video/BV1FV4y1U7oo/
https://llfc.club/category?catid=225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2Qld2hoFIu8ycYBJXQdxwyWEBfy
相关文章:

C++ 网络编程学习五
C网络编程学习五 网络结构的更新单例模式懒汉单例模式饿汉单例模式懒汉式指针智能指针设计单例类 服务器优雅退出asio的多线程模型IOServiceasio多线程IOThreadPoolepoll 和 iocp的一些知识点 网络结构的更新 asio网络层,会使用io_context进行数据封装,…...

案例分析篇05:数据库设计相关28个考点(9~16)(2024年软考高级系统架构设计师冲刺知识点总结系列文章)
专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…...

pip 和conda 更换镜像源介绍
1、前言 很多深度学习的项目免不了安装库文件、配置环境等等,如果利用官方提供的连接,网速很慢,而且很容易download掉。 所以配置好了虚拟环境,将pip换源属实重要 常见的国内镜像源有清华、中科大、阿里等等... 这里建议用中科…...

Git概述及安装步骤
一、Git简介 Git是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型的各种项目。Git 易于学习,占地面积小,性能极快。它具有廉价的本地库,方便的暂存区域和多个工作流分支等特性。其性能优于Subversion、CV…...

北京保险服务中心携手镜舟科技,助推新能源车险市场规范化
2022 年,一辆新能源汽车在泥泞的小路上不慎拖底,动力电池底壳受损,电池电量低。车主向保险公司报案,希望能够得到赔偿。然而,在定损过程中,保司发现这辆车的电池故障并非由拖底事件引起,而是由于…...

给女朋友的浪漫微信消息推送超详细版
1. 下载代码 地址: 链接: https://pan.baidu.com/s/1lESgRoWn8bXyE0jsSVCHqQ?pwdimr6 提取码: imr6 根据 resources/db 下sql文件创建表 修改yml文件中数据库连接 2. 进入微信测试平台 地址:微信公众平台 扫码登录获取测试号信息 修改代…...
Android开发 Activity启动模式、ViewModel与LiveData,及Kotlin Coroutines
目录 Activity启动模式 onNewIntent解释 Activity启动模式的考虑时机 Service启动模式 ContentProvider的作用 Broadcast的注册方式 AsyncTask的作用 ViewModel LiveData Kotlin Coroutines 结合使用 Activity启动模式 Android中Activity的启动模式有四种࿰…...
MQL语言实现抽象工厂模式
文章目录 一、定义抽象产品接口二、定义抽象工厂接口三、定义具体产品四、定义具体工厂五、定义工厂客户端六、客户端调用工厂客户端七、抽象工厂模式的结构 一、定义抽象产品接口 //------------------------------------------------------------------ //| participants …...

UE4开个头-简易小汽车
跟着谌嘉诚学的小Demo,记录一下 主要涉及到小白人上下车和镜头切换操作 1、动态演示效果 2、静态展示图片 3、蓝图-上下车...
Java基础入门day04
day04 包 包可以用来区分相同的类名 将相同的类放在不同包下,可以进行存储 一个目录下没有办法存在两个同名的文件 包最终在文件系统中与文件目录结构是一一对应的 在不同包下可以存放相同类名的文件 包后期还可以实现项目中模块的精确划分,controller,…...

中值定理j
f ( n ) ( ξ ) 0 f^{(n)}(\xi)0 f(n)(ξ)0...

第2篇【Docker项目实战】使用Docker部署Raneto知识库平台(转载)
【Docker项目实战】使用Docker部署Raneto知识库平台 一、Raneto介绍 1.1 Raneto简介 Raneto是一个免费、开放、简单的 Markdown 支持的 Node.js 知识库。 1.2 知识库介绍 知识库 知识库是指存储和组织知识的系统或库,它包括了各种类型的信息和知识,如…...
【Javascript】 Promise 对象(二)
【Javascript】 Promise 对象(一)-CSDN博客 Promise.all() Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 const p Promise.all([p1, p2, p3]);上面代码中,Promise.all()方法接受一个数组作为参数&…...

细说C++反向迭代器:原理与用法
文章目录 一、引言二、反向迭代器的原理与实现细节三、模拟实现C反向迭代器反向迭代器模板类的设计反向迭代器的使用示例与测试 一、引言 迭代器与反向迭代器的概念引入 迭代器(Iterator)是C标准模板库(STL)中的一个核心概念&am…...

SpringBoot(依赖管理和自动配置)
文章目录 1.基本介绍1.springboot是什么?2.快速入门1.需求分析2.环境配置1.确认开发环境2.创建一个maven项目3.依赖配置 pom.xml4.文件目录5.MainApp.java (启动类,常规配置)6.HelloController.java (测试Controller&a…...

cad怎么转换成黑白的pdf图纸?分享3个常用的软件!
在工程设计、建筑、机械制造等领域,CAD图纸的应用非常广泛。然而,有时出于某些需要,我们可能需要将CAD图纸转换为黑白的PDF格式。那么,如何实现这一转换呢?本文将为您详细介绍几种常用的转换软件及其操作步骤。 迅捷CA…...

maven本地仓库依赖上传到远程仓库
本地仓库上传到远程仓库 批量上传: 批量本地仓库依赖(jar包)上传脚本: #!/bin/bash # copy and run this script to the root of the repository directory containing files # this script attempts to exclude uploading itse…...

ISIS多区域实验简述
为支持大型路由网络,IS-IS在路由域内采用两级分层结构。 IS-IS网络中三种级别的路由设备:将Level-1路由设备部署在区域内,Level-2路由设备部署在区域间,Level-1-2路由设备部署在Level-1和Level-2路由设备的中间。 实验拓扑图&…...

go语言基础笔记
1.基本类型 1.1. 基本类型 bool int: int8, int16, int32(rune), int64 uint: uint8(byte), uint16, uint32, uint64 float32, float64 string 复数:complex64, complex128 复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部…...

kettle 9.4和Pentoho 9.4下载及安装方法简介
kettle 9.4和Pentoho 9.4下载及安装方法简介 下载地址: https://sourceforge.net/projects/pentaho/files/ 下载步骤: #------------- 一、点击选项卡:summary/ 二、点击第一行链接 https://www.hitachivantara.com/en-us/products/pentaho…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...