diff --git a/datum/arch.png b/datum/arch.png new file mode 100644 index 0000000..3d641cb Binary files /dev/null and b/datum/arch.png differ diff --git a/datum/web-arch-uml.png b/datum/web-arch-uml.png new file mode 100644 index 0000000..da98ca5 Binary files /dev/null and b/datum/web-arch-uml.png differ diff --git "a/\345\271\266\345\217\221\346\250\241\345\236\213.md" "b/\345\271\266\345\217\221\346\250\241\345\236\213.md" index d64d3db..d7be1e0 100755 --- "a/\345\271\266\345\217\221\346\250\241\345\236\213.md" +++ "b/\345\271\266\345\217\221\346\250\241\345\236\213.md" @@ -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创建,只会被该线程和主线程竞争。 @@ -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 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写到后端是没有障碍的,把慢的动作交给后端去做好了。