文章目录

  • 一、亮点
    • 1.采用了Reactor设计模式
      • 为什么选择Reactor?
      • WebServer选择的Reactor方案
      • WebServer对Reactor的具体实现
    • 2.EPOLLONESHOT
    • 3.基于小根堆实现了定时器
    • 4.实现了可以自动增长的缓冲区
      • 5.线程池
  • 二、难点
  • 三、有待改进的地方

面试被问到了这个问题,答得稀烂…但是我觉得这个问题真的问的很好,还是要好好想一想总结一下。

亮点:
并发模型为Reactor
使用Epoll水平触发+EPOLLONESHOT,非阻塞IO
为充分利用多核CPU的性能,以多线程的形式实现服务器,并实现线程池避免线程频繁创建销毁造成的系统开销
实现基于小根堆的定时器,用于断开超时连接
实现可以自动增长的缓冲区,作为HTTP连接的输入和输出缓冲区

一、亮点

1.采用了Reactor设计模式

为什么选择Reactor?

我们的目的是实现一个高并发的WebServer。
传统的方式(让服务器为每个客户端的连接都创建一个进程或线程)会存在两个主要问题:
1.线程或进程处理完连接上的业务逻辑后,就需要进行销毁,这样不停的创建和销毁,会造成服务器性能的开销和资源的浪费。
2.如果有几万个客户端请求,要创建几万个线程或进程去处理也是不现实的。

引入I/O多路复用技术:
I/O 多路复用技术会用一个系统调用函数来监听我们所有关心的连接,也就说可以在一个监控线程里面监控很多的连接。内核提供给了我们select、poll、epoll等多路复用的系统调用。

具体的处理三种方法不太一样,这里主要说一下epoll的方式。
我们把需要检测的事件注册到工作在内核里的epoll对象上,epoll以红黑树的方式组织需要检测的事件,epoll内有回调函数,当有新事件到来时就会调用回调函数,把就绪事件添加到就绪事件链表上,内核会返回就绪事件,我们的主进程就会在用户态中处理这些事件对应的业务。
Reactor 模式就是基于I/O 多路复用技术,Reactor 对I/O多路复用技术作了一层封装,让使用者不用考虑底层网络 API 的细节,只需要关注应用代码的编写。

WebServer选择的Reactor方案

wiki上的定义:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers.

从上述文字中我们可以看出以下关键点 :

  • 是一种设计模式(design pattern)
  • 事件驱动(event handling)
  • 可以处理一个或多个(one or more inputs)同时到达(delivered concurrently)的输入源
  • 通过Service Handler同步的将输入事件(Event)采用多路复用分发给相应的Request Handler(多个)处理

本项目采用的是单 Reactor 多线程方案的思想。
为什么说是思想呢?项目确实只有一个Reactor,以及线程池。但是实际实现和「单 Reactor 多线程」方案不完全一样。
「单 Reactor 多线程」方案的示意图如下:

先详细说一下这个方案:
Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 根据收到事件的类型进行分发,具体分发给 Acceptor 对象或者Handler 对象;
如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;
Handler 对象只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;
子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;

WebServer对Reactor的具体实现

先直接说区别吧,「单 Reactor 多线程」方案里Handler对象负责数据的接收和发送,也就是(read,write),子线程的Processor对象负责业务处理。但是项目里边对数据的接收和发送以及业务处理全都是子线程完成的。相当于Handler和Processor的职能全由子线程完成了。
具体的看项目:
首先,一个主进程负责监听,然后根据事件类型分别调用不同的函数进行处理。

int eventCnt = epoller_->Wait(timeMS);for(int i = 0; i < eventCnt; i++) {/* 处理事件 */int fd = epoller_->GetEventFd(i);uint32_t events = epoller_->GetEvents(i);if(fd == listenFd_) {DealListen_();//处理监听的操作,接受客户端连接        }else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {assert(users_.count(fd) > 0);CloseConn_(&users_[fd]);}else if(events & EPOLLIN) {assert(users_.count(fd) > 0);DealRead_(&users_[fd]); //处理读操作}else if(events & EPOLLOUT) {assert(users_.count(fd) > 0);DealWrite_(&users_[fd]); //处理写操作} else {LOG_ERROR("Unexpected event");}

若是新连接就直接调用函数进行处理

void WebServer::DealListen_() {struct sockaddr_in addr;   //保持连接的客户端信息socklen_t len = sizeof(addr);do {int fd = accept(listenFd_, (struct sockaddr *)&addr, &len);if(fd <= 0) { return;}else if(HttpConn::userCount >= MAX_FD) {SendError_(fd, "Server busy!");LOG_WARN("Clients is full!");return;}AddClient_(fd, addr);} while(listenEvent_ & EPOLLET);
}

若是读写事件,就交由线程池处理。

void WebServer::DealRead_(HttpConn* client) {assert(client);ExtentTime_(client);threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client));
}void WebServer::DealWrite_(HttpConn* client) {assert(client);ExtentTime_(client);threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client));
}

2.EPOLLONESHOT

epoll有两种触发的方式即LT(水平触发)和ET(边缘触发)两种,在前者,只要存在着事件就会不断的触发,直到处理完成,而后者只触发一次相同事件或者说只在从非触发到触发两个状态转换的时才触发。

这会出现下面一种情况,如果是多线程在处理,一个SOCKET事件到来,数据开始解析,这时候这个SOCKET又来了同样一个这样的事件,而你的数据解析尚未完成,那么程序会自动调度另外一个线程或者进程来处理新的事件,这造成一个很严重的问题,不同的线程或者进程在处理同一个SOCKET的事件,这会使程序的健壮性大降低而编程的复杂度大大增加!!即使在ET模式下也有可能出现这种情况!!

这里用EPOLLONESHOT这种方法进行解决,可以在epoll上注册这个事件,注册这个事件后,如果在处理写成当前的SOCKET后不再重新注册相关事件,那么这个事件就不再响应了或者说触发了。要想重新注册事件则需要调用epoll_ctl重置文件描述符上的事件,这样前面的socket就不会出现竞态这样就可以通过手动的方式来保证同一SOCKET只能被一个线程处理,不会跨越多个线程。

3.基于小根堆实现了定时器

实现了高并发的服务器通常会面临的一个问题就是有大量的连接建立,但是实际活跃的连接并不多,这样会造成服务器端的资源浪费,因此我们选择为每个连接添加一个定时器,如果该连接超过了定时时间仍然没有时间到达,服务器就主动关闭这个连接。

4.实现了可以自动增长的缓冲区

主要使用了散布读和集中写

#include <sys/uio.h>
/* Structure for scatter/gather I/O. */
struct iovec{void *iov_base; /* Pointer to data. */size_t iov_len; /* Length of data. */
};

成员iov_base指向一个缓冲区,这个缓冲区是存放readv所接收的数据或是writev将要发送的数据。
成员iov_len确定了接收的最大长度以及实际写入的长度。

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

参数:

fd是要在其上进行读或是写的文件描述符;
iov是读或写所用的I/O向量;
iovcnt是要使用的向量元素个数。

返回值:

readv所读取的字节数或writev所写入的字节数;
如果有错误发生,就会返回-1,错误代码存在errno中。

在项目的实现中根据readv()函数的返回值来进行相应操作,比如返回错误、返回下一次内存读入的位置或者用临时数组实现缓冲区的增长。writev()函数也同理。

5.线程池

采用生产者消费者模型。
用互斥量和条件变量实现了线程池。unique_lock与lock_guard的区别
而且notify_one()不会导致惊群。

二、难点

也许不是项目的难点而是我自己认为难的地方。

  1. Reactor思想的运用
  2. 为什么选择了socket非阻塞与epollET模式
    这里我之前也写过一篇博文WebServer为什么需要将socket设置为非阻塞?

三、有待改进的地方

「单 Reactor」的模式存在一个问题,因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。这个也是WebServer这个项目存在的瓶颈之一。

参考链接:
【项目】高性能web服务器
C++11实现的高性能静态web服务器
高性能网络模式:Reactor 和 Proactor
Reactor详解
epoll的LT和ET使用EPOLLONESHOT

WebServer项目的亮点和难点相关推荐

  1. 【SpringBoot商城秒杀系统项目总结25】 项目的亮点和难点及问题解决(源码地址)

    [商城限时秒杀系统总结] 在高并发情况下的秒杀优化,我们知道当并发数达到一定量的时候,会对数据库服务器带来很大的压力,那么如何缓解这些压力以及提高并发的QPS就是整个项目的解决重点,也是我们优化系统的 ...

  2. java项目难点_【体验感悟巧化难点为亮点】 java项目技术亮点难点

    教学片断一: 2005年我在教学<湖心亭看雪>时,先让学生反复诵读课文,然后通过自主学习疏通文意后,再让学生找出写景的句子,开始进行本课的难点的学习. 按照设计问学生:"你觉得张 ...

  3. Java核心常用API +JSD2103_SE 项目 + WebServer项目

    WebServer项目 JSD2103_SE 项目 安装Git Git的使用 JSD2103_SE 项目的创建 API目录: 一.文档注释 二.String类 三.Object类 四.Integer ...

  4. 项目没有亮点,如何应对面试官的提问?

    ↑↑ 点击上方关注「陈树义」↑↑ 这是陈树义的第 032 期分享 作者 l 陈树义 来源 l 陈树义(ID:Spark-tree) 转载请联系授权(ID:Spark-tree) 在树义的技术交流群里, ...

  5. web聊天室项目开发过程及重难点整理

    目录 一.需求分析 二.业务背景 1.张三要发消息给李四 2.WebSocket实现消息推送流程 三.前后端接口和数据库系统设计 1.用户相关的接口 2.频道相关接口 3.数据库表的设计 四.功能交互 ...

  6. 项目中遇到哪些难点,如何解决的

    Sanno限时秒杀抢票系统 亮点:在高并发情况下的秒杀优化,我们知道当并发数达到一定量的时候,会对数据库服务器带来很大的压力,那么如何缓解这些压力以及提高并发的QPS就是整个项目的重点.(不断的提高Q ...

  7. 听书项目开发过程及重难点总结(用户管理)

    文章目录 一.需求分析 二.前后端交互 1. 注册 **前端** **后端** 2.登录 前端 后端 三.项目开发重难点 1.编写一个Servlet 2.session和session会话机制 1)S ...

  8. 银行自动化运维项目前期规划八大难点

     难点一:自动化运维项目,如何进行技术路线的选择? 就笔者所了解的,自动化运维项目有以下四条技术路线供大家参考 1.基于开源工具自研 基于社区活跃度高.口碑好.功能强大的开源自动化工具,进行二次开发, ...

  9. WebServer项目介绍

    这是一个基于Linux平台下的轻量级的web服务器项目,可实现上万的并发连接,项目中主要用的技术有,线程池 + 非阻塞socket+epoll+事件处理的并发模型,状态基解析http请求,以及支持同步 ...

最新文章

  1. Sentinel-Go 集成 Nacos 实现外部动态数据源
  2. ABAP程序:查找TC相关的出口。
  3. c++ 分析core文件 在os x
  4. 算法精讲:分享一道值得分享的算法题
  5. 东芝硬盘插入台式机后滴滴响
  6. KM 最优匹配 讲解
  7. 橙白oj18训练作业2-题解、代码
  8. UIDatePicker的使用
  9. [面试] C/C++ 语法(五) —— extern
  10. 计算机网络基础系列(四)HTTP、七层模型及其内部对应协议
  11. 一句话说明java常量池及其存储的对象
  12. 2022智源大会议程公开 | 探索智能的原理,构建脑科学与AI的桥梁
  13. html audio缓冲效果实现
  14. 【每日分享】我做程序员那些年犯下的罪,此时此刻我自己的笑出猪叫~
  15. casual Convolution 和 dilated Convolution
  16. 基础设施即代码(IAC),Zalando Postgres Operator 简介
  17. 作业5管理用户、组及权限
  18. python 在屏幕上点击特定按钮或图像
  19. 大数据分析与实践 数据预处理-主成分分析
  20. Django入门超easy系列(一)——— 从一个简单的例子入门

热门文章

  1. 红帽linux安装intel(R) wifi link 5100 AGN总结
  2. 涨价不再“嘘声”一片,“爱优腾”集体“想开了”?
  3. Linux Bridge的IP NAT细节探析-填补又一坑的过程
  4. 「我的microNome组学分析流程」第1版
  5. oracle remapschema,remap schema多个用户
  6. ​微信小程序 获取地理位置(显示地图并显示经纬度)​
  7. 计算机二级考风考纪主题班会,计算机二级C语言上机题库及答案(100套).doc
  8. win7怎么看计算机显卡内存大小,Win7系统怎么看显存?
  9. 高光谱知识(1)-高光谱成像技术的理解
  10. AUTOSAR I-PDU的理解以及I-PDU的Callout