Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update design doc #143

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added datum/arch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added datum/web-arch-uml.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 52 additions & 8 deletions 并发模型.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
本程序使用的并发模型如下图所示:

![并发模型](https://github.com/linyacool/WebServer/blob/master/datum/model.png)
![并发模型](/datum/arch.png)

MainReactor只有一个,负责响应client的连接请求,并建立连接,它使用一个NIO Selector。在建立连接后用Round Robin的方式分配给某个SubReactor,因为涉及到跨线程任务分配,需要加锁,这里的锁由某个特定线程中的loop创建,只会被该线程和主线程竞争。

Expand All @@ -31,14 +32,57 @@ epoll的触发模式在这里我选择了ET模式,muduo使用的是LT,这两

每个SubReactor持有一个定时器,用于处理超时请求和长时间不活跃的连接。muduo中介绍了时间轮的实现和用stl里set的实现,这里我的实现直接使用了stl里的priority_queue,底层是小根堆,并采用惰性删除的方式,时间的到来不会唤醒线程,而是每次循环的最后进行检查,如果超时了再删,因为这里对超时的要求并不会很高,如果此时线程忙,那么检查时间队列的间隔也会短,如果不忙,也给了超时请求更长的等待时间。

## 核心结构

程序中的每一个类和结构体当然都必不可少,其中能体现并发模型和整体架构的,我认为是有两个:

* Channel类:Channel是Reactor结构中的“事件”,它自始至终都属于一个EventLoop,负责一个文件描述符的IO事件,在Channel类中保存这IO事件的类型以及对应的回调函数,当IO事件发生时,最终会调用到Channel类中的回调函数。因此,程序中所有带有读写时间的对象都会和一个Channel关联,包括loop中的eventfd,listenfd,HttpData等。
* EventLoop:One loop per thread意味着每个线程只能有一个EventLoop对象,EventLoop即是时间循环,每次从poller里拿活跃事件,并给到Channel里分发处理。EventLoop中的loop函数会在最底层(Thread)中被真正调用,开始无限的循环,直到某一轮的检查到退出状态后从底层一层一层的退出。

## Log
## 核心结构设计
![Web结构类图](/datum/web-arch-uml.png)

程序中的每一个类和结构体当然都必不可少,其中能体现并发模型和整体架构的,我认为是有下面几个类:
#### Server类
Server是http服务器的包装类,主要负责开启端口监听和启动事件主循环(Main Reactor). Sub Reactor是一个线程池,每个线程都是不断获取事件并处理。其中事件主循环会将新的客户端连接accept,并从sub reactor中选择一个新的线程,并放入这个线程监控的fd队列,由这个线程来处理后续的读写事件。
#### EventLoopThreadPool 线程池
这个类作用是管理线程
#### EventLoopThread 实现了reactor的可运行实体
这个类作用是将reactor实体化,变成机器中可运行的线程
#### EventLoop 对应于反应堆理论模型中的reactor
**Poller** 事件获取器
不断调用epoll_wait系统调用,获取新的active事件
**Event Handler** 事件处理器
对于上一步poll出的事件,回调响应的处理接口,这一层是和业务处理和结合点.
总的来看,EventLoop即是事件循环,每次从poller里拿活跃事件,并给到Channel里分发处理。
具体见代码
```
void EventLoop::loop() {
assert(!looping_);
assert(isInLoopThread());
looping_ = true;
quit_ = false;
// LOG_TRACE << "EventLoop " << this << " start looping";
std::vector<SP_Channel> ret;
while (!quit_) {
// cout << "doing" << endl;
ret.clear();
//事件获取器
ret = poller_->poll();
eventHandling_ = true;
//事件处理器
for (auto& it : ret) it->handleEvents();
eventHandling_ = false;
doPendingFunctors();
poller_->handleExpired();
}
looping_ = false;
}
```

#### Epoll类
提供操作epoll系统调用的工具类
#### HttpData类 业务类的处理接口
HttpData是用来读写socket数据,转换解析http 协议的数据,并做对应的业务处理,具体的read/write handler在事件主循环接受新连接时,在HttpData的构造函数中传入,具体见
```
Server::handNewConn()
```
#### Channel类 表示通信模型中的信道,向下对接网络通信的,向上对接业务层
Channel是Reactor结构中的“事件”,它自始至终都属于一个EventLoop,负责一个文件描述符的IO事件,在Channel类中保存这IO事件的类型以及对应的回调函数,当IO事件发生时,最终会调用到Channel类中的回调函数。因此,程序中所有带有读写事件的对象都会和一个Channel关联,包括loop中的eventfd,listenfd,HttpData等。
## 日志记录设计
Log的实现了学习了muduo,Log的实现分为前端和后端,前端往后端写,后端往磁盘写。为什么要这样区分前端和后端呢?因为只要涉及到IO,无论是网络IO还是磁盘IO,肯定是慢的,慢就会影响其它操作,必须让它快才行。

这里的Log前端是前面所述的IO线程,负责产生log,后端是Log线程,设计了多个缓冲区,负责收集前端产生的log,集中往磁盘写。这样,Log写到后端是没有障碍的,把慢的动作交给后端去做好了。
Expand Down