前言


这篇文章主要介绍整个框架用到的最核的一个设计模式:反应器模式。这个设计模式可以在《面向对象的软件架构》中详细了解,没有这本书的小伙伴不要急,我通过咱们的SimpleRpc来告诉大家这个设计模式是如何运用的。之所以它叫反应器模式,是因为它是处理事件的一种比较优美的框架。如何优美,我们慢慢道来。

如何设计一个高吞吐量的web服务?


web服务会面对大量的网络请求,服务要对这些请求进行处理,如何设计我们的web服务呢?有如下几种模型供参考。

  1. 单线程模型 系统中使用唯一的一个线程处理网络数据的读,对请求数据的处理,以及发送响应。当一个网络请求到来时,工作线程通过accept获取到活动socket,接下来该线程顺序的读取数据、处理数据、生成响应数据、写回socket。这个模型有一个明显的缺点:当服务处理一个请求时,其它请求将阻塞得不到响应。它难以胜任高并发大吞吐量的web服务。
  2. 多线程模型 每当accept返回一个新的活动socket后,主线程就创建一个新线程进行数据的读、处理、响应。这样做的好处就是当一个线程处理请求时,其它线程可以接收新的请求并进行响应。它的缺点也很明显:当请求的并发量多的时候,系统会同时有多个线程工作。当线程非常多时,cpu需要在多个线程之间做切换,切换的开销将会增大,不是一个伸缩性很好的设计。我们可以使用线程池来优化这个设计,但是这么做也有一个问题:如果某些客户端与服务端建立连接后并不是马上发送数据,那么此时服务端会有大量的线程就都hang在socket的读上面(因为网络数据迟迟不发送),cpu使用率不高。

如何克服上面两种模型的弊端呢?我们的反应器模式开始大展身手。

反应器模式


  • Select/Epoll简介: 上面两种模型没有用到操作系统提供的更高级的网络数据处理机制:select模型/Epoll模型。这是一个能够同时监听多个socket句柄上的动作的机制,由操作系统支持。它维护一个监听列表,用户可以动态添加和删除需要监听的socket句柄。如果一个句柄被加入了它的监听列表,当句柄有新数据到来或者句柄可写时就会产生一个电位差提醒操作系统有socket句柄可以操作(读或者写),这时select/Epoll就会告诉用户可以在哪些socket句柄上做操作。它的优点是不会因为其中任何一个句柄的阻塞而忽略其它句柄上的可操作事件。

有了Select/Epoll这个利器,我们就可以设计反应器了:

  • Reactor(反应器)接口定义:

regist:在一个socket句柄上注册操作函数。

remove:从反应器中移除对某个句柄的监听。

handle_events:通过系统提供的select/epoll_wait获取所监听句柄上的事件通知,并调用对应句柄所注册的函数进行处理。

  • EventHandler接口定义:

handle_read: 对句柄上的读事件进行操作

handle_write: 对句柄上的写事件进行操作

SimpleRpc中的核心框架实现


Reactor使用Epoll实现,Epoll的具体使用方式这里就不赘述了,因为它不是本文的主要写作目的。这里面讲述一下SimpleRpc网络事件处理的最核心的三个类:Acceptor,UpstreamHandler,DownstreamHandler。

  • Acceptor接受器:
Acceptor::Acceptor(const InetAddr &addr, Reactor *reactor){
_reactor = reactor;
int sock_fd = socket(AF_INET, SOCK_STREAM, );
sockaddr sock_addr = addr.addr();
int ret = bind(sock_fd, &sock_addr, sizeof(sockaddr));
if(ret != ){
LOG("bind error");
exit();
}
ret = listen(sock_fd, );
if(ret != ) {
LOG("listen error");
exit();
}
_reactor->regist(sock_fd, this);
}

首先,服务端使用socket函数创建一个socket_fd,绑定好IP和端口后,acceptor把这个socket_fd加入到reactor监听列表中,当有客户端主动发起与该服务的连接时,服务端该socket_fd上会有读事件产生,Acceptor的handle_read函数将会被调用。

void Acceptor::handle_read(int sock_fd) {
struct sockaddr addr;
socklen_t size = sizeof(struct sockaddr_in);
int fd = accept(sock_fd, &addr, &size);
_reactor->regist(fd, new UpstreamHandler(fd, _reactor)); //UpstreamHandler用来处理客户端请求。
}

handle_read函数接收到这个socket_fd后,知道这是客户端的请求连接,于是调用accept函数获取这个新连接的socket句柄fd。站在服务端角度看,客户端就是它的上游,对于上游事件的处理需要用UpstreamHandler。Acceptor在reactor上绑定fd与UpstreamHandler,reactor等待后续的事件的到来。Acceptor就像一只老母鸡,不断的下蛋(蛋就是新生产出的fd),并把蛋放入到reactor等待进一步孵化。

  • UpstreamHandler:上游请求事件处理
void UpstreamHandler::handle_read(int fd) {
if(fd != _sock_fd){
return;
} StreamEvent e;
e.fd = _sock_fd;
e.type = 0; //客户端请求事件
_reactor->remove(fd); //移除fd
//由每个工作线程自己去读fd,客户端请求事件
ThreadPool<UpstreamEvent>::get_instance()->put_event(e); //放入队列
//自杀
delete this;
}

客户端请求服务端建立连接后,第一步就是要向服务端发送请求数据。客户端发送数据后,服务端reactor发现socket句柄上有读事件,就调用对应句柄上的事件处理函数(UpstreamHandler::handle_read())。该事件处理函数并不直接进行数据的读取和计算,而是先从reactor中移除该fd(防止后续数据到来,reactor重复获取读事件),之后把fd封装成一个事件结构体放入阻塞队列中,由共享该阻塞队列的线程池进行后续处理。

  • DownstreamHandler:下游事件处理
void DownstreamHandler::handle_read(int fd) {
char head[];
if(fd != _sock_fd){
return;
} _reactor->remove(fd);
Connection conn(fd);
conn.recv_n(head, );
int size = *((int *)head);
char *buf = new char[size];
conn.recv_n(buf, size);
close(fd);
printf("Downstream Handler close fd:%d\n", fd);
//下游响应
_response->deserialize(buf, size);
if(_result_handler != NULL) {
_result_handler->data_comeback();
} delete[] buf;
//自杀
delete this;
}

站在客户端的角度来看,服务端就是其下游,客户端对服务端的响应数据的处理需要使用DownstreamHandler。当服务端返回响应数据时,客户端的reactor会检测到对应socket句柄上的读事件,随后调用对应事件处理函数(handle_read)。该函数首先从reactor移除对该fd的监听,防止reactor重复检测事件并调用处理函数;之后就接收数据、反序列化得到响应结构体。

SimpleRpc-网络事件响应Reactor设计模式的更多相关文章

  1. 浅析Reactor设计模式

    简介:Reactor 设计模式是一种事件驱动的设计模式,将一个或者多个客户端请求分发到不同的处理器上,来提升事件处理的效率.主要的应用场景就是java NIO当中用户处理网络请求.使用的是异步非阻塞I ...

  2. 利用epoll写一个"迷你"的网络事件库

    epoll是linux下高性能的IO复用技术,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.另一点原因就是获取 ...

  3. 追踪app崩溃率、事件响应链、Run Loop、线程和进程、数据表的优化、动画库、Restful架构、SDWebImage的原理

    1.如何追踪app崩溃率,如何解决线上闪退 当 iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上.crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈 跟踪信 ...

  4. iOS中的事件响应链、单例模式、工厂模式、观察者模式

    学习内容 欢迎关注我的iOS学习总结--每天学一点iOS:https://github.com/practiceqian/one-day-one-iOS-summary iOS中事件传递和相应机制 i ...

  5. iOS事件响应链

    首先,当发生事件响应时,必须知道由谁来响应事件.在IOS中,由响应者链来对事件进行响应,所有事件响应的类都是UIResponder的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获 ...

  6. DuiLib事件分析(一)——鼠标事件响应

    最近在处理DuiLib中自定义列表行元素事件,因为处理方案得不到较好的效果,于是只好一层一层的去剥离DuiLib事件是怎么来的,看能否在某一层截取消息,自己重写. 我这里使用CListContaine ...

  7. mvc ajax dropdownlist onchang事件响应

    <script type="text/javascript"> $("#Cycle").on("change", functio ...

  8. Legolas工业自动化平台入门(三)交互事件响应动作

    在上一篇Legolas工业自动化平台入门(二)数据响应动作 一文中,我们介绍了"动作"相关内容,了解到"动作"分为多种,各种动作的添加方式相同,但是应用方式各自 ...

  9. JS代码的位置与事件响应代码块的封装问题

    JS代码的位置       我们可以将JavaScript代码放在html文件中任何位置,但是我们一般放在网页的head或者body部分.   放在<head>部分最常用的方式是在页面中h ...

随机推荐

  1. 转载_2016,Java成神初年

    原文地址:http://blog.csdn.net/chenssy/article/details/54017826 2016,Java成神初年.. -------------- 时间2016.12. ...

  2. 更符合面向对象的数据库操作方式-OrmLite

    1. jar包下载 下载地址:http://ormlite.com/releases/,一般用core和android包即可. 如果使用的是android studio,也可以直接通过module s ...

  3. .net 中常用的正则表达式整理

    相信很多伙伴都跟我一样有关于正则表达式的爱和恨,怎么说呢? 因为正则表达式规则繁多且复杂,想一个一个学 全部精通,需要耗费很长时间和精力, 但是我们用的地方并不是很多,所以我觉得这类东西需要做成类似工 ...

  4. 利用js实现禁用浏览器后退

    原博主链接为:http://blog.csdn.net/zc474235918/article/details/53138553 现在很多的内部系统,一些界面,都是用户手动点击退出按钮的.但是为了避免 ...

  5. Java基础---GUI

    第一讲     GUI(用户图形界面) 一.概述 1.GUI:GraphicalUser Interface,即图形用户界面,用于计算机与用户交互的一种方式. 2.计算机与用户交互的两种方式:GUI和 ...

  6. C# 根据Excel模版导出文件

    string fileName = Path.Combine(Application.StartupPath, "kfwh_yhxf.xls"); Microsoft.Office ...

  7. bat调用带参数存储过程

    @bat调用sql文件 sqlplus user/pass@orcl @F:\factory.sql @将所有的存储过程封装在sql中 factory.sql:exec pro_factory(&am ...

  8. python3 安装及项目管理安装

    python3 一.下载安装 地址:https://www.python.org/downloads/ 安装:傻瓜式安装:我的目录如下 二.环境配置 [右键计算机]-->[属性]-->[高 ...

  9. hdu2222 Keywords Search(AC自动机初步)

    题目大意: 给出多个模式串和一个主串,求多少个模式串在主串中出现过. 传送门 这是一道AC自动机的模板题. 在学习AC自动机之前,首先要学习WA自动机.TLE自动机和MLE自动机(雾 AC自动机是一种 ...

  10. 详解HTTPS加速原理

    HTTPS是什么? http叫超文本传输协议,使用TCP端口80,默认情况下数据是明文传送的,数据可以通过抓包工具捕获到,因此在interner上,有些比较重要的站点的http服务器需要使用PKI(公 ...