C++ 网络编程学习五

C++网络编程学习五

    • 网络结构的更新
    • 单例模式
      • 懒汉单例模式
      • 饿汉单例模式
      • 懒汉式指针
      • 智能指针设计单例类
    • 服务器优雅退出
    • asio的多线程模型IOService
    • asio多线程IOThreadPool
      • epoll 和 iocp的一些知识点

网络结构的更新

  1. asio网络层,会使用io_context进行数据封装,底层的话,在linux中就是epoll模型,在windows就是iocp模型。
    在这里插入图片描述
  2. 当服务器的接受数据较多时,又要处理接收到的信息的逻辑处理,逻辑处理一般会放到一个逻辑处理队列中进行处理。因为有时候逻辑比较复杂。
  3. 通过一个队列,单独线程从队列中取出逻辑函数。从而实现网络线程和逻辑线程分开,由一个队列进行连接。极大地提升网络线程的收发能力,并且可以用多线程的方式管理网络层。
  4. asio的多线程模式:
    • 启动n个线程,每个线程都有一个iocontext,每个线程负责一部分的socket。
    • 一个ioconext由多个线程共享。也可以一定程度上减轻readhandler的负担。
  5. 逻辑处理一般都是单线程的,因为大量的用户同时处理一个逻辑过程的时候,频繁地加锁取消锁,还不如就单线程的来做。

完善消息结构:
消息 = 消息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);
}

单例模式

懒汉单例模式

  1. 通过静态成员变量实现单例。存在隐患,对于多线程方式生成的实例可能是多个。
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

  1. 第一个是启动多个线程,每个线程管理一个iocontext。第二种是只启动一个iocontext,被多个线程共享。
    在这里插入图片描述

  2. 启动线程的个数,不要超过核数。

  3. 每个线程独立调用io_context,一个socket会被注册在同一个io_context里,它的回调函数也会被单独的一个线程回调:

    • 那么对于同一个socket,他的回调函数每次触发都是在同一个线程里,就不会有线程安全问题,网络io层面上的并发是线程安全的。
    • 如果两个socket对应的上层逻辑处理,如果有交互或者访问共享区,会存在线程安全问题。可以通过加锁或者逻辑队列的方式解决安全问题。
  4. 多线程的优势:提升了并发能力, 单线程仅有一个io_context服务用来监听读写事件,就绪后回调函数在一个线程里串行调用。如果一个回调函数的调用时间较长肯定会影响后续的函数调用。

  5. 通过逻辑队列的方式将网络线程和逻辑线程解耦合,不会出现前一个调用时间影响下一个回调触发的问题。

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

  1. 一个IOServicePool开启n个线程和n个iocontext,每个线程内独立运行iocontext, 各个iocontext监听各自绑定的socket是否就绪,如果就绪就在各自线程里触发回调函数。
  2. IOThreadPool:初始化一个iocontext用来监听服务器的读写事件,包括新连接到来的监听也用这个iocontext。只是我们让iocontext.run在多个线程中调用,这样回调函数就会被不同的线程触发,从这个角度看回调函数被并发调用了。
  3. 线程池统一管理一个io_context,每个线程调用一个io_context,会话session都注册到一个,哪个线程调用了io_context.run,哪个线程去就绪队列取出回调函数。
  4. 回调函数对同一个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

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

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

相关文章

1.下载安装ESP32开发环境ESP-IDE

ESP32简介 ESP32介绍 说到ESP32&#xff0c;首先ESP32不是一个芯片&#xff0c;ESP32是一个系列芯片&#xff0c; 是乐鑫自主研发的一系列芯片微控制器。它主要的功能就是支持WiFi和蓝牙&#xff0c; ESP32指的是ESP32裸芯片。但是&#xff0c;“ESP32”一词通常指ESP32系列芯…

Unity之PUN实现多人联机射击游戏的优化

目录 &#x1f3ae;一、 跳跃&#xff0c;加速跑 &#x1f3ae;二、玩家自定义输入昵称 &#x1f345;2.1 给昵称赋值 &#x1f345;2.2 实现 &#x1f3ae;三、玩家昵称同步到房间列表 &#x1f345;3.1 获取全部玩家 &#x1f345;3.2 自定义Player中的字段 &#…

圈内大佬呕心之作,一年后斩获腾讯T3,这份Java学习笔记有多厉害

说这句话的人其实有一些误解&#xff0c;误解就在于&#xff0c;安逸的生活并不等于不需要奋斗&#xff0c;这要看你的家底。 某聪如果说要选择安逸的生活&#xff0c;他可以很安逸&#xff0c;因为他有了安逸的资本&#xff0c;而大部分的你&#xff0c;并没有这个资本&#…

SQL中的distinct的使用方法

1. distinct含义与使用方法 distinct用来查询不重复记录的条数,即用distinct来返回不重复字段的条数&#xff08;count(distinct id)&#xff09;,其原因是distinct只能返回他的目标字段&#xff0c;而无法返回其他字段。 注意事项 distinct 【查询字段】&#xff0c;必须放…

压缩json字符串

GZIPOutputStream 需要关闭&#xff0c;而 ByteArrayOutputStream 不需要关闭。具体原因如下&#xff1a; GZIPOutputStream&#xff1a;GZIPOutputStream是一种过滤流&#xff0c;它提供了将数据压缩为GZIP格式的功能。当使用此类的实例写入数据时&#xff0c;它会对数据进行压…

阿里云数据湖存储加速套件JindoData

计算存储分离已经成为云计算的一种发展趋势。在计算存储分离之前&#xff0c;普遍采用的是传统的计算存储相互融合的架构&#xff0c;但是这种架构存在一定的问题&#xff0c;比如在集群扩容的时候会面临计算能力和存储能力相互不匹配的问题。用户在某些情况下只需要扩容计算能…

[MYSQL数据库]- 索引

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、认识索…

力扣--课程表--bfs+dfs

整体思路&#xff1a; 这是一道拓扑序列的题目&#xff0c;我们将边的方向定义成从先修课指向后修课的方向&#xff0c;借一下官方的题解图片&#xff0c;我们需要判断的是形成的这个图结构是否存在环&#xff0c;如果存在环&#xff0c;那么代表不能完成所有课程的学习。 bfs思…

强推游戏爱好者!雾锁王国联机服务器部署教程

继《幻兽帕鲁》游戏爆火之后&#xff0c;与它同类型的《雾锁王国》也是强力刷屏&#xff0c;不分伯仲&#xff0c;在 Steam 上的评分一直稳定在“特别好评”&#xff0c;让小伙伴们很“上头”。就在两者游戏玩家反响爆火的同时&#xff0c;官方服务器人数爆满&#xff0c;卡顿频…

JavaScript进阶2之执行上下文、作用域链、函数创建、函数激活、checkScope的执行过程、闭包、this

JavaScript进阶 执行上下文执行上下文中的属性变量对象全局上下文的变量对象函数上下文执行过程进入执行上下文代码执行思考题 作用域链函数创建函数激活checkScope的执行过程总结 闭包分析闭包 this 执行上下文 执行上下文中的属性 每一个执行上下文都有三个核心属性 变量对…

【趣味项目】一键生成LICENSE

【趣味项目】一键生成LICENSE 项目地址&#xff1a;GitHub(最新版本) | GitCode(旧版本) 项目介绍 一款用于自动生成开源项目协议的工具&#xff0c;可以通过 npm 进行安装后在命令行使用&#xff0c;非常方便 使用方式 npm install xxhls/get-license -gget-license --l…

python的函数与类的定义

目录 1.函数 1.函数的定义 2.输入参数与输出参数的类型 3.输入和输出多个参数 1.普通参数 2.含有任意数量的参数 3.关键字参数 4.普通参数与多个参数的结合 2.类 1.类的定义 2.类的实例化 3.继承 1.函数 1.函数的定义 def 函数名(输入参数): 文档字符串 函数体 …

第七届国际通信与网络工程会议(ICCNE 2024)即将召开!

2024年第七届国际通信与网络工程会议&#xff08;ICCNE 2024&#xff09;将于7月26-28日在越南岘港召开。本次会议由维新大学主办&#xff0c;岘港大学、胡志明市科技大学协办。ICCNE 2024旨在为来自行业和学术界的研究人员、从业者和专业人士提供论坛&#xff0c;分享其最新研…

基于boost库的搜索引擎项目

文章目录 一、项目背景二、什么样的搜索引擎三、搜索引擎的宏观图原理四、Parse模块4.1下载boost库源代码4.2提取boost库中以.html为结尾的文件4.2.1 boost库的简单使用 4.3数据清洗(去标签化)4.3.1数据清洗的具体实现 4.4将清洗后的数据写入到raw.txt文件中 五、正排索引 vs 倒…

C++面试问题收集

0 持续更新中 目录 0 持续更新中 1 C语言相关 1.1 malloc/free和new/delete区别 1.2 内存泄漏 1.3 堆区和栈区的区别 1.4 宏定义和const的区别 1.5 多态 1.6 类中的静态成员变量 2 操作系统相关 2.1 进程和&#xff08;用户&#xff09;线程的区别 2.2 系统调用 2.3…

学习SSM的记录(七)-- SSM整合

目录 SSM整合理解 什么是SSM整合 SSM整合核心问题 1.SSM整合需要几个ioc容器 2.每一个ioc容器对应哪些类型组件 ​编辑 3.ioc容器之间关系和调用方向 4.具体多少配置类以及对应容器关系 5.ioc初始化方式和配置位置 ​编辑 SSM整合配置实战 1.数据库准备 2.创建项目…

十分钟教你有效运用百度检索式

十分钟教你有效运用百度检索式 ​ 百度作为国内最大的搜索引擎网站&#xff0c;虽然很多人都喷它的搜索页面广告太多&#xff0c;但是对于不会科学上网的同学来说&#xff0c;似乎除了继续用百度以外也没有更好的选择。 ​ 其实搜索引擎一般都会内涵一些高级的搜索技巧&#…

中间件漏洞(redis)

目录 1.Redis服务器被挖矿案例 2.redis常见用途 3.redis环境配置 4.redis的持久化机制 5.redis动态修改配置 6.webshell提权案例 7.定时任务bash反弹连接提权案例 8.SSH Key提权案例 9.redis安全加固分析 1.Redis服务器被挖矿案例 我没有体验过&#xff0c;那就看看别…

【Redis】Redis常用命令一

1.keys&#xff1a;返回所有满足条件的key&#xff0c;比如&#xff1a; KEYS pattern时间复杂度&#xff1a;O(N)&#xff0c;返回值&#xff1a;匹配pattern的所有key。 • h?llo 匹配 hello , hallo 和 hxllo • h*llo 匹配 hllo 和 heeeello • h[ae]llo 匹配 hello 和 …

【CSP试题回顾】201709-1-打酱油

CSP-201709-1-打酱油 解题代码 #include<iostream> using namespace std;int n, num;int main() { cin >> n;if (n / 50 ! 0){num 7 * (n / 50);n % 50;}if (n / 30 ! 0){num 4 * (n / 30);n % 30;}if (n / 10 ! 0) num (n / 10);cout << num;return…