深入理解Reactor模型的原理与应用

1、什么是Reactor模型

        Reactor意思是“反应堆”,是一种事件驱动机制。

        和普通函数调用的不同之处在于:应用程序不是主动的调用某个 API 完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到 Reactor 上,如果相应的时间发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。

        对于刚开始接触这个机制,个人感觉翻译成“感应器”可能会更好理解一点,因为注册在Reactor上的函数就像感应器一样,只要有事件到达,就会触发它开始工作。

        Reactor 模式是编写高性能网络服务器的必备技术之一。


2、Reactor模型的优点

  • 响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;
  • 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
  • 可扩展性强,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;
  • 可复用性高,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;
           Reactor 模型开发效率上比起直接使用 IO 复用要高,它通常是单线程的,设计目标是希望单线程使用一颗 CPU 的全部资源。
            优点即每个事件处理中很多时候可以不考虑共享资源的互斥访问。可是缺点也是明显的,现在的硬件发展,已经不再遵循摩尔定律,CPU 的频率受制于材料的限制不再有大的提升,而改为是从核数的增加上提升能力,当程序需要使用多核资源时,Reactor 模型就会悲剧 , 为什么呢?
            如果程序业务很简单,例如只是简单的访问一些提供了并发访问的服务,就可以直接开启多个反应堆,每个反应堆对应一颗 CPU 核心,这些反应堆上跑的请求互不相关,这是完全可以利用多核的。例如 Nginx 这样的 http 静态服务器。

3、通过对网络编程(epoll)代码的优化,深入理解Reactor模型

1、epoll的普通版本,根据fd类型(listen_fd和client_fd)分为两大类处理。

        如果是listen_fd,调用accept处理连接请求;

        如果是client_fd,调用recv或者send处理数据。

         代码实现:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>#include <errno.h>int main(int argc, char* argv[])
{if (argc < 2)return -1;int port = atoi(argv[1]);   //字符串转换为整型int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0)return -1;struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));   //新申请的空间一定要置零addr.sin_family = AF_INET;addr.sin_port = htons(port);    //转换成网络字节序addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)return -2;if (listen(sockfd, 5) < 0)return -3;//epollint epfd = epoll_create(1); //创建epoll,相当于红黑树的根节点struct epoll_event ev, events[1024] = {0};  //events相当于就绪队列,一次性可以处理的集合ev.events = EPOLLIN;ev.data.fd = sockfd;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);    //将ev节点加入到epoll,此处的sockfd参数随便添加没有意义,需要操作系统索引和它有对应的句柄while (1){int nready = epoll_wait(epfd, events, 1024, -1);    //第四个参数-1表示一直等待,有事件才返回if (nready < 1) //没有事件触发,nready代表触发事件的个数break;int i = 0;for (i = 0; i < nready; i++)    //epoll_wait带出的就绪fd包括两大类:1、处理连接的listen_fd,2、处理数据的send和recv{if (events[i].data.fd == sockfd) //如果是listenfd,就将它加入到epoll{struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_len = sizeof(client_addr);int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);if (client_fd <= 0)continue;char str[INET_ADDRSTRLEN] = {0};printf("recv from IP = %s ,at Port= %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));ev.events = EPOLLIN | EPOLLET;  //epoll默认是LT模式ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);}else    //fd进行读写操作{//对fd的读写操作没有分开int client_fd = events[i].data.fd;char buf[1024] = {0};int ret = recv(client_fd, buf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//}else{//}printf("ret < 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev);}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev); //close关闭连接后要将它既是从epoll中删除}else{printf("Recv: %s, %d Bytes\n", buf, ret);}//区分fd的读写操作,即recv和sendif (events[i].events & EPOLLIN){int client_fd = events[i].data.fd;char buf[1024] = {0};int ret = recv(client_fd, buf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//...}else{//...}printf("ret < 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev);}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev); //close关闭连接后要将它既是从epoll中删除}else{printf("Recv: %s, %d Bytes\n", buf, ret);}}if (events[i].events & EPOLLOUT)    //为什么需要判断EPOLLOUT,而不是直接else?因为一个fd有可能同时存在可读和可写事件的{int client_fd = events[i].data.fd;char buf[1024] = {0};send(client_fd, buf, sizeof(buf), 0);}}}}return 0;
}

 

2、epoll的优化版本,根据事件类型(读和写)分为两大类处理。

         代码实现:

        for (i = 0; i < nready; i++)    //epoll_wait带出的就绪fd包括两大类:1、处理连接的listen_fd,2、处理数据的send和recv{//区分fd的读写操作if (events[i].events & EPOLLIN){if (events[i].data.fd == sockfd) //如果是listenfd,就将它加入到epoll{struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_len = sizeof(client_addr);int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);if (client_fd <= 0)continue;char str[INET_ADDRSTRLEN] = {0};printf("recv from IP = %s ,at Port= %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));ev.events = EPOLLIN | EPOLLET;  //epoll默认是LT模式ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);}else {int client_fd = events[i].data.fd;char buf[1024] = {0};int ret = recv(client_fd, buf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//...}else{//...}printf("ret < 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev);}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", client_fd);close(client_fd);ev.events = EPOLLIN;ev.data.fd = client_fd;epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &ev); //close关闭连接后要将它既是从epoll中删除}else{printf("Recv: %s, %d Bytes\n", buf, ret);}}}//为什么需要判断EPOLLOUT,而不是直接else?因为一个fd有可能同时存在可读和可写事件的if (events[i].events & EPOLLOUT)    {int client_fd = events[i].data.fd;char buf[1024] = {0};send(client_fd, buf, sizeof(buf), 0);}}

 

3、epoll的Reactor模式, epoll由以前的对网络io(fd)进行管理,转变成对events事件进行管理。

         代码实现:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>#include <errno.h>//每个fd所对应的信息
struct sockitem
{int sockfd;int (*callback)(int fd, int events, void*arg);char sendbuf[1024];char recvbuf[1024];
};//每个epoll所对应的信息
struct epollitem
{int epfd;struct epoll_event events[1024];    //events相当于就绪队列,一次性可以处理的集合
};struct epollitem *eventloop = NULL;int recv_cb(int fd, int events, void*arg);
int send_cb(int fd, int events, void*arg);int accept_cb(int fd, int events, void*arg)
{printf("---accept_cb(int fd, int events, void*arg)---\n");struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));socklen_t client_len = sizeof(client_addr);int client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd <= 0)return -1;char str[INET_ADDRSTRLEN] = {0};printf("recv from IP = %s ,at Port= %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)), ntohs(client_addr.sin_port));struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;  //epoll默认是LT模式struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));si->sockfd = client_fd;si->callback = recv_cb;ev.data.ptr = si;epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, client_fd, &ev);return client_fd;
}int recv_cb(int fd, int events, void*arg)
{printf("---recv_cb(int fd, int events, void*arg)---\n");struct epoll_event ev;struct sockitem *sit = (struct sockitem*)arg;int ret = recv(fd, sit->recvbuf, 1024, 0);if (ret < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){//...}else{//...}printf("ret < 0,断开连接:%d\n", fd);ev.events = EPOLLIN;epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);    //close关闭连接后要将它既是从epoll中删除close(fd);free(sit);  //连接关闭后释放内存}else if (ret == 0)  //接收到了客户端发来的断开连接请求FIN后,没有及时调用close函数,进入了CLOSE _WAIT状态{printf("ret = 0,断开连接:%d\n", fd);ev.events = EPOLLIN;epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev); close(fd);free(sit);}else{printf("Recv from recvbuf:  %s, %d Bytes\n", sit->recvbuf, ret);ev.events = EPOLLIN | EPOLLOUT;  //sit->sockfd = fd;sit->callback = send_cb;ev.data.ptr = sit;epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);}return ret;
}int send_cb(int fd, int events, void*arg)
{struct epoll_event ev;struct sockitem *sit = (struct sockitem*)arg;strncpy(sit->sendbuf, sit->recvbuf, sizeof(sit->recvbuf) + 1);send(fd, sit->sendbuf, sizeof(sit->recvbuf) + 1, 0);ev.events = EPOLLIN | EPOLLET;  //sit->sockfd = fd;sit->callback = recv_cb;ev.data.ptr = sit;epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);return fd;
}int main(int argc, char* argv[])
{if (argc < 2)return -1;int port = atoi(argv[1]);   //字符串转换为整型int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0)return -1;struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));   //新申请的空间一定要置零addr.sin_family = AF_INET;addr.sin_port = htons(port);    //转换成网络字节序addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)return -2;if (listen(sockfd, 5) < 0)return -3;//epolleventloop = (struct epollitem *)malloc(sizeof(struct epollitem));eventloop->epfd = epoll_create(1); //创建epoll,相当于红黑树的根节点struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));si->sockfd = sockfd;si->callback = accept_cb;ev.data.ptr = si;   //将fd和对应的回调函数绑定一起带进epollepoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);    //将ev节点加入到epoll,此处的sockfd参数随便添加没有意义,需要操作系统索引和它有对应的句柄while (1){int nready = epoll_wait(eventloop->epfd, eventloop->events, 1024, -1);    //第四个参数-1表示一直等待,有事件才返回if (nready < 1) //没有事件触发,nready代表触发事件的个数break;int i = 0;for (i = 0; i < nready; i++){//区分fd的读写操作if (eventloop->events[i].events & EPOLLIN){struct sockitem *sit = (struct sockitem*)eventloop->events[i].data.ptr;sit->callback(sit->sockfd, eventloop->events[i].events, sit);    //不用区分listen_fd和recv_fd,相应的fd都会调用他们所对应的callback}//为什么需要判断EPOLLOUT,而不是直接else?因为一个fd有可能同时存在可读和可写事件的if (eventloop->events[i].events & EPOLLOUT)    {struct sockitem *sit = (struct sockitem*)eventloop->events[i].data.ptr;sit->callback(sit->sockfd, eventloop->events[i].events, sit);}}}return 0;
}

4、Reactor模型的应用 

        1、单线程模式的Reactor,参考libevent、redis;

        2、多线程模式的Reactor,参考memcached;

        3、多进程模式的Reactor,参考nginx。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

继域名解析后------------我的网页进不去了!!!(修复方法)

昨天域名解析之后&#xff0c;今天来公司上班发现公司网页进不去了&#xff0c;好在现在是平台升级维护期间&#xff0c;没什么业务。统计过后&#xff0c;发现有一半的公司员工登录不进去。这一半能登陆上的同事所使用的网络是500M电信宽带&#xff0c;而我们这边登录不上的都…

html打开d盘文件,为什么ie浏览器打不开c/d盘的html文件,直接跳出保存

满意答案 pvgpl 2014.02.05 采纳率&#xff1a;42% 等级&#xff1a;12 已帮助&#xff1a;12615人 清理一下磁盘碎片和系统垃圾。 不行就---------------- 请不要盗用我的答案!! 一号方案(新X) 注意(原创): 1.安全模式下&#xff0c;效果更好! 2. 以下所要使用的软件&#…

手把手教会你|Sockets多用户-服务器数据库编程

网络编程经常涉及数据库访问,电子商务更离不开数据库。例如用户请求股票报价、产品价格查询、网上交易等请求,服务器则需要连接对应的数据库,发送查询指令,得到数据库记录,经过处理后,发送给提出这个请求的用户。 在实际应用中,数据库经常由专门管理数据库的服务器运行。…

股票交易规则

交易时间 股票交易时要遵循 价格优先 时间优先&#xff0c;但如果同价又同时报价&#xff0c;那大单优先 具体交易时间规定&#xff1a; 每周一至周五&#xff0c;每天上午9:30至11:30&#xff0c;下午13:00至15:00&#xff0c;法定假期除外。 A股 上海交易所 集合竞价&#…

股票交易接口有没有提供试用的?

我们做量化交易&#xff0c;是需要使用工具的&#xff0c;因为量化交易交易频率很高&#xff0c;如果人工操作经历有限&#xff0c;一般是无法执行到位的&#xff0c;不过股票交易接口一般都需要收费&#xff0c;而且还不便宜&#xff0c;如果可以试用的话&#xff0c;那一定是…

股票自动委托下单html,股票怎么设置自动挂单?股票挂单的方式

股票怎么设置自动挂单?接下来小编为大家介绍。 股票挂单指在股票交易时把所要买进或卖出的股票的名称、数量、价格填写后提交给交易系统等待成交的过程。 传统网上交易下单&#xff0c;需要①从行情软件切换到委托软件&#xff0c;②输入需要买卖股票的代码&#xff0c;③输入…

基于SpringBoot实现MySQL与Redis的数据一致性

问题场景 在并发场景下&#xff0c;MySQL和Redis之间的数据不一致性可能成为一个突出问题。这种不一致性可能由网络延迟、并发写入冲突以及异常情况处理等因素引起&#xff0c;导致MySQL和Redis中的数据在某些时间点不同步或出现不一致的情况。数据一致性问题的级别可以分为三…

list(介绍与实现)

目录 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modififiers 1.2.6 list的迭代器失效 2. list的模拟实现 2.1 模拟实现list 2.2 list的反向迭代器 1.…

代码随想录第32天|122.买卖股票的最佳时机 II,55. 跳跃游戏 ,45. 跳跃游戏 II

122.买卖股票的最佳时机 II 122. 买卖股票的最佳时机 II 思路比较简单 class Solution {public int maxProfit(int[] prices) {int res0,sum0;for(int i0;i<prices.length-1;i){if(prices[i1]-prices[i]>0){sumprices[i1]-prices[i];}ressum>res?sum:res;}return …

笔记本重装win7旗舰版原版操作系统

正常开机的电脑&#xff1a;直接重装&#xff08;最简单&#xff0c;最快&#xff09;、Ghost重装、U盘重装、光盘重装、硬盘安装 不能正常开机的电脑&#xff1a;U盘重装、光盘重装、硬盘安装 注意&#xff1a; 1.windows7原版系统是不带任何驱动程序的&#xff0c;请事先准…

w ndows7旗舰版怎么重装系统,windows7旗舰版iso怎么安装

现在安装系统要求操作简单、方便。硬盘安装方式就是最简单、最方便的系统安装方法。保证电脑能进入系统的前提下&#xff0c;本地硬盘安装windows7旗舰版iso系统&#xff0c;能够让你快速体验新的windows7旗舰版iso系统。接下来详细讲解下安装windows7旗舰版iso系统的操作过程&…

w ndows7旗舰版网卡驱动,Ghost windows7 64位系统旗舰版网卡驱动工具推荐下载

雨林木风Ghost windows7 64位系统旗舰版网卡驱动工具可以使系统能够快速的链接网络&#xff0c;提供丰富的驱动文件&#xff0c;雨林木风Ghost windows7 64位系统旗舰版网卡驱动工具是很多的用户遇到问题都可以解决的一种工具&#xff0c;所以今天我们就来看看吧。 深度技术Gho…

windows7旗舰版安装过程

一&#xff0c;首先我提供一个windows7旗舰版<带激活工具>的下载地址: 7600_x86_zh-cn_Ultimate_DVD.iso 4 s5 T: O7 L. P* g如果连接错误就复制下面地址到迅雷thunder://QUFodHRwOi8vZG93bjIuZ2hvc3QyLmNuLzc2MDBfeDg2X3poLWNuX1VsdGltYXRlX0RWRC5pc29aWg泗水,泗水论坛,…

windows7系统之家旗舰版下载

肯定有很多朋友都想要了解windows7系统之家这款系统吧?毕竟这款系统可是非常值得一试的,如果大家想要使用这款系统的话,大家就需要事先了解它了,所以下面就给大家带来windows7系统之家下载吧。 ●系统之家ghost win7 x64 旗舰版v1610作品简述: 《系统之家ghost win7 x64 旗…

服务器 字体文件太大,网页的字体文件过大

今天写好了一个网页&#xff0c;开心的放到了云服务器&#xff0c;查看效果&#xff0c;结果发现忘记把字体文件拷过去。但是当字体文件拷过去后&#xff0c;再次访问网页时&#xff0c;网页加载变得十分缓慢&#xff0c;几分钟后才会将字体加载完成。 当时那个郁闷啊&#xff…

高等数学上册 第十章 重积分 第十一章 曲线积分与曲面积分 知识点总结

重积分 二重积分计算法&#xff1a; 直角坐标下&#xff1a;化为二次积分 { 如果图形是 X Y 型&#xff0c;则都可以&#xff0c;但要考虑哪个计算不定积分方便 如果图形既不是 X 也不是 Y 型&#xff0c;则要拆分 极坐标下&#xff1a; ∬ f ( x , y ) d x d y ∬ f ( ρ cos…

DevOps系列文章 之 Python基础

Python 基础篇&#xff08;一&#xff09; print( )函数、input( )函数 用 Python 来打印 Hello, world 你会惊奇地发现Python是如此的简洁与优雅。 # 打印Hello, world print(Hello, world) Hello, world# input()函数&#xff1a;接受一个标准输入数据&#xff0c;返回为s…

九龙证券|主力资金 矿业龙头尾盘净买超亿元

家用电器职业取得主力大手笔抢筹。 今天主力资金净流出260.84亿元&#xff0c;其间创业板净流出93.77亿元&#xff0c;沪深300成份股净流出40.64亿元。 申万一级职业中&#xff0c;今天有25个职业上涨&#xff0c;家用电器涨幅居首&#xff0c;达3.31%&#xff1b;其后煤炭、建…

九龙证券|又3个涨停,退市风险急升!

*ST新海退市危险急剧上升&#xff01; 到4月14日&#xff0c;*ST新海收盘价接连14个买卖日低于1元/股。按照退市新规&#xff0c;若*ST新海在接下来6个买卖日收盘价继续低于1元/股&#xff0c;将触及买卖类强制退市景象而终止上市&#xff0c;公司股票将不进入退市整理期。 面…

股票查询小程序_以龙虎榜数据为例

功能需求 1.程序启动后&#xff0c;给用户提供查询接口&#xff0c;允许用户重复查股票行情信息(用到循环&#xff09; 2.允许用户通过模糊查询股票名&#xff0c;比如输入“生物”&#xff0c;就把所有股票名称中包含“生物”的所有股票的信息打印出来 3.允许按收盘价,涨跌…