【项目】Reactor模式的服务器

目录

Reactor完整代码连接

前置知识:

 1.普通的epoll读写有什么问题?

2.Connection内的回调函数是什么

3.服务器的初始化(Connection只是使用的一个结构体)

4.等待就绪事件:有事件就绪,对使用Connection的不同对象(封装fd,回调方法)调用对应的回调方法;

5._listenSock读取 :Accepter函数获取新链接怎么处理的

6.普通套接字读取:Recver

7.对写事件的关心是按需关系的:

8.执行效果:

9.Reactor的优势:


Reactor完整代码连接

前置知识:

Reactor叫做反应堆模式;反应:对已经就绪的事件(读、写、异常)进行处理;

我们使用epoll来实现,select、poll、epoll是多路转接的发展史,epoll完善了select、poll的缺点;

  • 需要程序员维护数组                                      select/poll都有这个缺点    
  • 有大量的遍历                                                 select/poll都有这个缺点 
  • 大量参数为输入输出型参数,需要重新设置  select有
  • 管理的fd有上限                                              select有

 1.普通的epoll读写有什么问题?

  • 使用的是一个静态数组读取;报文长,读到就是不完整报文,报文短,一次读取可能有多个报文,最后一个报文可能不是完整的;
  • 这样的错误报文没法分析和处理,就不能构造响应报文;

综上所述:问题为没法保证读取到的是完整报文,导致没法分析和处理、构建响应报文;

    void Read(int fd){char buff[1024];ssize_t s = read(fd, buff, 1023);if(s > 0){buff[s] = 0;LOG2(INFO, buff, fd);}

解决办法:将文件描述符封装,并且有接受发送缓冲区,使用string就可以;

  1. 使用静态数组读取,然后添加到接受缓冲区保存;
  2. 读取完毕,对接受缓冲区分析是否有完整报文;
  3. 处理完整请求报文,构建响应报文添加到发送缓冲区;
using func_t = std::function<void(Connection*)>;
using cals_t = std::function<void(std::string &, Connection*)>;class Connection{
public:Connection(int fd = -1 ):_fd(fd), _ts(nullptr){}~Connection(){if(_fd >= 0)close(_fd);}
public:int _fd;//读写异常回调方法func_t _recver;func_t _sender;func_t _exception;//接受缓冲区std::string _outbuff;//发送缓冲区std::string _inbuff;//TcpServer的回指指针,对写事件的关心是按需打开TcpServer *_ts;//连接最近活跃活动的时间time_t _times;
};

2.Connection内的回调函数是什么

一个包装器;返回值为void,参数为Connection*,的函数指针、仿函数、lamada表达,它都可以接受;

using func_t = std::function<void(Connection*)>;

优势:

  • _listenSock是读方法是接受新连接,普通是读取请求报文
  • 初始化时设置读写异常回调方法(回调:使用函数指针执行的函数),不需要判断是_listenSock还是普通套接字,统一使用con->_recver;

3.服务器的初始化(Connection只是使用的一个结构体)

  1. 套接字创建,bind,监听;
  2. 构建epoll模型,epoll函数也封装了,_epfd封装在Epoll类内;
  3. 初始化_listenSock    Connection结构体;读取回调方法Accept是类函数,多一个this指针,需要使用std::bind来改变参数个数,进行传递给包装器
  4. epoll_wait的事件就绪队列初始化;
class TcpServer{const static int gport = 8080;const static int gnum = 128;TcpServer(int port = gport, int num = gnum):_port(gport), _evts_num(num){//套接字,创建bind监听_listenSock = Sock::Socket();Sock::Bind(_listenSock,_port);Sock::Listen(_listenSock);//构建epoll模型_epoll.CreateEpoll();//listensock添加epoll模型和_connections管理起来AddConnection(_listenSock, std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);//epoll_wait就绪队列,获取已就绪的事件_evts = new epoll_event[_evts_num];}~TcpServer(){if(_listenSock >= 0)close(_listenSock);if(_evts != nullptr)delete[] _evts;for(auto &pr : _connections){_connections.erase(pr.first);delete pr.second;}}
private:int _listenSock;//epollEpoll _epoll;//就绪队列epoll_event* _evts;int _evts_num;//管理connection对象std::unordered_map<int, Connection*> _connections;int _port;//业务处理的回调指针cals_t _cb;
};

4.等待就绪事件:有事件就绪,对使用Connection的不同对象(封装fd,回调方法)调用对应的回调方法;

    void LoopOnce(){int n = _epoll.WaitEpoll(_evts, _evts_num);//有事件就绪if(n > 0){//LOG2(INFO, "epoll wait success",fd);for(int i=0; i<n; i++){int fd = _evts[i].data.fd;int events = _evts->events;//连接关闭或者错误,改为读写统一处理,读写失败调异常处理;if( events & EPOLLHUP){LOG2(INFO,"连接关闭",fd);events |= (EPOLLIN | EPOLLOUT);}if( events & EPOLLERR){LOG2(INFO,"错误",fd);events |= (EPOLLIN | EPOLLOUT);} if(_connections.count(fd) && events & EPOLLIN){if(IsConnectionExits(fd) && _connections[fd]->_recver != nullptr){_connections[fd]->_recver(_connections[fd]);}}if(_connections.count(fd) && events & EPOLLOUT){if(IsConnectionExits(fd) && _connections[fd]->_sender != nullptr)_connections[fd]->_sender(_connections[fd]);}}}else if(n == 0){LOG(INFO, "timeout");}else{LOG(FATAL,"epoll wait fail");exit(4);}}void Dispatcher(cals_t cb){_cb = cb;while(true){//去除不活跃的连接DeleteInactivity();LoopOnce();}}

5._listenSock读取 :Accepter函数获取新链接怎么处理的

  • 得到新连接,如果新的fd是合法的,设置对应的读写异常回调方法,读:读取请求报文,写:发送响应报文,异常:出现错误进行处理;
  • 所有的套接字都是使用ET模式(通知效率高,只支持非阻塞读写),EPOLLET因该被设置;
void Accepter(Connection * con){while(true){con->_times = time(nullptr);struct sockaddr_in tmp;socklen_t tlen = sizeof(tmp);int new_sock = accept(con->_fd, (struct sockaddr *)&tmp, &tlen);if(new_sock < 0){//所以事件处理完毕if(errno == EAGAIN || errno == EWOULDBLOCK)break;else if(errno == EINTR)//可能被信号中断,概率极小continue;else{std::cout << "accept fail , errno :" << errno << strerror(errno) << std::endl;break;}} else//添加到epoll模型和_connections中管理;{if(AddConnection(new_sock, std::bind(&TcpServer::Recver, this, std::placeholders::_1), std::bind(&TcpServer::Sender, this, std::placeholders::_1),std::bind(&TcpServer::Exception, this, std::placeholders::_1)))LOG2(INFO, "add connection success",new_sock);elseLOG2(RISK, "add connection fail", new_sock);}}}

6.普通套接字读取:Recver

  1. 一直读取,直到错误或者读取完毕;每次读取的结果都放到接受缓冲区;
  2. 读取完毕,对接受缓冲区处理,拿出一个个完整的报文,对请求报文进行业务处理;
    void Recver(Connection *con){con->_times = time(nullptr);const int buff_size = 1024;char buff[buff_size];while(true){ssize_t s = recv(con->_fd, buff, buff_size - 1, 0);if(s > 0){buff[s] = 0;con->_outbuff += buff;}else if(s == 0){LOG2(INFO, "写端关闭", con->_fd);con->_exception(con);return;}else{//读取完毕if(errno == EAGAIN || errno == EWOULDBLOCK ){LOG2(INFO, "处理完毕", con->_fd);break;}else if(errno == EINTR)continue;else{LOG2(ERROR, "recv fail ,fd: ", con->_fd);con->_exception(con);return;}}}std::cout << "fd: " << con->_fd << "outbuff: " << con->_outbuff <<std::endl;//对outbuff内的完整报文,进行处理std::vector<std::string> out;//分隔报文,函数在protocol.hppSplitMessage(out, con->_outbuff);for(auto &s : out)_cb(s, con);//业务逻辑回调指针,在主函数}

7.对写事件的关心是按需关系的:

  • 如果开启关心,还没有数据发送,写事件会一直就绪;所以按需关心;
  • 请求报文业务处理完毕,构建好响应报文,一定有响应,打开对写事件的关心;
  • 也是Connection为什么封装一个Tcperver指针的原因,这里开启写事件的关心;
//业务处理
void CalArguments(std::string &str, Connection *con)
{//请求报文反序列化Request req;//std::cout<<str <<std::endl;if(!req.Deserialize(str)){LOG2(ERROR, "deseroalize fail" ,con->_fd);return;}//对数据处理Response res;calculator(req, res);//响应报文序列化std::string s = res.Serialize();//添加到inbuffcon->_inbuff += s;//一定有响应报文,打开写事件的关系con->_ts->EnableReadWrite(con->_fd, true, true);
}

8.执行效果:

  • 我写的协议是对任意两个数加减乘除;
  • 每个请求或者响应都是用 x  做为分割符的;

9.Reactor的优势:

和进程/线程做比较:

  • 它是一个单进程的就可以并发处理请求的服务器,比进程/线程减少了创建、销毁、调度的时间;
  • 它的等待一批fd,减少了单位等待时间;一个线程等待对应一个fd;
  • 有很高的复用性,替换业务逻辑就行了;

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

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

相关文章

配置keil生成asm汇编文件

简介&#xff1a;ASM是汇编语言源程序的扩展名&#xff1b;程序在编译的过程中&#xff0c;会将源代码编译会汇编代码&#xff0c;一步步生成可执行文件&#xff1b; 1&#xff1a;keil中options的配置 这个语法应该是根据工程工程哪里的配置名称来的&#xff0c;也可以使用固…

伦敦银线性回归分析

在金融市场中&#xff0c;商品的价格一段时间内总是会围绕着一条线性回归趋势线&#xff0c;在两侧波动并沿着这条趋势线方向发展。当价格在波动过程中偏离趋势线距离太大了&#xff0c;就会再次向趋势线靠拢。 波浪理论认为商品的价格走势都是波浪式发展的&#xff0c;无论处于…

vue中实现签名画板

特意封装成了一个组件&#xff0c;签名之后会生成一张图片 signBoard.vue <template><el-drawer title"签名" :visible.sync"isShowBoard" append-to-body :show-close"false" :before-close"closeBoard" size"50%&quo…

【Unity每日一记】WheelColider组件汽车游戏的关键

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

.netcore对传输类设置区分大小

.Net Core中内置了对Json的转化与解析 可将PropertyNameCaseInsensitive false 设置为区分大小写。

vue-别名路径联想提示的配置

在根路径下&#xff0c;新建 jsconfig.json 文件&#xff0c;即可 在输入 自动联想到src目录。 代码如下&#xff1a; // 别名路径联想提示&#xff1a;输入自动联想 {"compilerOptions":{"baseUrl":"./","paths": {"/*":[…

Vue2项目练手——通用后台管理项目第二节

Vue2项目练手——通用后台管理项目 路由限制重复跳转CommonAside.vue 顶部header组件搭建与样式修改右边用户菜单栏使用的组件图片CommonHeader.vue Vuex实现左侧折叠文件目录store/index.jsstore/tab.jsmain.jsCommonHeader.vueCommonAside.vueMain.vue 路由限制重复跳转 路由…

如何提高抖音直播间的人气(从15个方面为你解答)

抖音直播是一项非常受欢迎的内容创作方式&#xff0c;但是在直播过程中若是没有足够的人气&#xff0c;会让主播感到非常沮丧。如何才能提高抖音直播间的人气呢&#xff1f;本文将从15个方面为你一一解答。 一、打造独特个性的直播形象 在抖音直播中&#xff0c;每一个主播都有…

vue-elementPlus自动按需导入和主题定制

elementPlus自动按需导入 装包 -> 配置 1. 装包&#xff08;主包和两个插件包&#xff09; $ npm install element-plus --save npm install -D unplugin-vue-components unplugin-auto-import 2. 配置 在vite.config.js文件中配置&#xff0c;配置完重启&#xff08;n…

Vue3+Element Plus实现el-table跨行显示(非脚手架)

Vue3Element Plus实现el-table跨行显示 app组件内容使用:span-method"objectSpanMethod"自定义方法实现跨行显示查询方法初始化挂载新建一个html即可进行测试&#xff0c;完整代码如下效果图 app组件内容 <div id"app"><!-- 远程搜索 --><e…

stable diffusion实践操作-复制-清空-保存提示词

系列文章目录 stable diffusion实践操作 stable diffusion实践操作-webUI教程 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、右上生成图标附近按钮介绍1. 箭头介绍&#xff08;复现别人的…

这可能是最全面的Python入门手册了!

无论是学习任何一门语言&#xff0c;基础知识一定要扎实&#xff0c;基础功非常的重要&#xff0c;找到一个合适的学习方法和资料会让你少走很多弯路&#xff0c; 你的进步速度也会快很多&#xff0c;无论我们学习的目的是什么&#xff0c;不得不说Python真的是一门值得付出时间…

vue使用百度地图实现地点查询

效果 代码 首先在index.html中引入script&#xff1a; <head><meta charset"utf-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width,initial-scal…

java八股文面试[数据库]——主键的类型自增还是UUID

auto_increment的优点&#xff1a; 字段长度较uuid小很多&#xff0c;可以是bigint甚至是int类型&#xff0c;这对检索的性能会有所影响。 在写的方面&#xff0c;因为是自增的&#xff0c;所以主键是趋势自增的&#xff0c;也就是说新增的数据永远在后面&#xff0c;这点对于…

vue3组件通信学习笔记

1、Prop 父组件 <template><div class"parent"><h1>我是父元素</h1><Child :msg"msg"></Child></div> </template><script setup> import Child from ./Child.vue let msg ref(我是父组件的数据…

探讨前后端分离开发的优势、实践以及如何实现更好的用户体验?

随着互联网技术的迅猛发展&#xff0c;前后端分离开发已经成为现代软件开发的一种重要趋势。这种开发模式将前端和后端的开发工作分开&#xff0c;通过清晰的接口协议进行通信&#xff0c;旨在优化开发流程、提升团队协作效率&#xff0c;并最终改善用户体验。本文将深入探讨前…

申威芯片UOS中opencv DNN推理

Cmake&#xff0c;opencv&#xff0c;opencv-contribute安装 #apt可能需要更新apt update apt install -y wget unzip apt-get install build-essential libgtk2.0-dev libgtk-3-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev#安装cmake apt i…

ES6的面向对象编程以及ES6中的类和对象

一、面向对象 1、面向对象 &#xff08;1&#xff09;是一种开发思想&#xff0c;并不是具体的一种技术 &#xff08;2&#xff09;一切事物均为对象&#xff0c;在项目中主要是对象的分工协作 2、对象的特征 &#xff08;1&#xff09;对象是属性和行为的结合体 &#x…

设计模式行为型-模板模式

文章目录 一&#xff1a;模板方法设计模式概述1.1 简介1.2 定义和目的1.3 关键特点1.4 适用场景 二&#xff1a;模板方法设计模式基本原理2.1 抽象类2.1.1 定义和作用2.1.2 模板方法2.1.3 具体方法 2.2 具体类2.2.1 定义和作用2.2.2 实现抽象类中的抽象方法2.2.3 覆盖钩子方法 …

27.CSS粒子特效

效果 源码 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Vanilla JS Particles</title><link rel="stylesheet" href="style.css"> </head> <body>…