- Reactor模型
- channel
- Poller
- EPollPoller
- EventLoop(channel 和 epoll之间的桥梁)
- wakeupFd
- eventfd
- loop
EventLoop.*、Channel.*、Poller.*、EPollPoller.*等主要用于事件轮询检测,并实现了事件分发处理的底层实现方法。EventLoop负责轮询执行Poller,要进行读、写、错误、关闭等事件时需执行哪些回调函数,均绑定至Channel中,只需从中调用即可,事件发生后进行相应的回调处理即可 Thread.*、EventLoopThread.*、EventLoopThreadPool.*等将线程和EventLoop事件轮询绑定在一起,实现真正意义上的one loop per thread TcpServer.*、TcpConnection.*、Acceptor.*、Socket.*等是mainloop对网络连接的响应并轮询分发至各个subloop的实现,其中注册大量回调函数 Buffer.*为muduo网络库自行设计的自动扩容的缓冲区,保证数据有序性到达 技术亮点 EventLoop中使用了eventfd来调用wakeup(),让mainloop唤醒subloop的epoll_wait阻塞 在EventLoop中注册回调cb至pendingFunctors_,并在doPendingFunctors中通过swap()的方式,快速换出注册的回调,只在swap()时加锁,减少代码临界区长度,提升效率。(若不通过swap()的方式去处理,而是加锁执行pendingFunctors中的回调,然后解锁,会出现什么问题呢?1. 临界区过大,锁降低了服务器响应效率 2. 若执行的回调中执行queueInLoop需要抢占锁时,会发生死锁) Logger可以设置日志等级,调试代码时可以开启DEBUG打印日志;若启动服务器,由于日志会影响服务器性能,可适当关闭DEBUG相关日志输出 在Thread中通过C++lambda表达式以及信号量机制保证线程创建时的有序性,只有当线程获取到了其自己的tid后,才算启动线程完毕 TcpConnection继承自enable_shared_from_this,TcpConnection对象可以调用shared_from_this()方法给其内部回调函数,相当于创建了一个带引用计数的shared_ptr,可参考链接 link,同时muduo通过tie()方式解决了TcpConnection对象生命周期先于Channel结束的情况 muduo采用Reactor模型和多线程结合的方式,实现了高并发非阻塞网络库Reactor模型
多线程反应堆模型,mainReactor监听新用户的连接,处理acceptor,拿到新用户通信的fd,把fd和fd感兴趣的事件打包成一个channel,然后采用轮询的方式唤醒workReactor也就是工作线程,把channel扔给工作线程,工作线程上的Reactor就代表eventLoop。每一个工作线程的eventLoop都监听有一组channel,每一组channel发生都得在自己的eventLoop去执行(ThreadId)。
理解为通道,对文件描述符管理的一个类,封装了sockfd和其感兴趣的event 如EOLLIN、EPOLLOUT事件 还绑定了poller返回的具体事件
class EpollPoller : public Poller
channel update => eventLoop update_channel => Poller => epollPoller updateChannel => epoll_ctr_add
数据:
EventLoop *loop_; //所属loop
const int fd_;
int events_; //fd感兴趣事件
int real_events_; //poller 具体发生的事件
int index_;
weak_ptr tie_; //观察当前channel的存在状态
bool tied_; //判断tie_是否绑定过
//发生事件所要调用的具体事件的回调操作
ReadEventCallback read_callback_;
EventCallback write_callback_;
EventCallback close_callback_;
方法:
//fd得到poller通知以后,根据具体发生的事件,调用相应的回调
//其实调用的handle_event_withGuard
void handle_event(TimeStamp receive_time);
//防止channel被remove掉,channel还在执行回调
//一个tcpconnection新连接创建的时候,调用tie
void tie(const shared_ptr &);
//得到socket套接字
int get_fd();
//得到感兴趣事件
int get_events();
//设置真正发生的事件,poller监听到事件然后设置real_event
int set_revent(int event);
//判断该fd是否设置过感兴趣事件
bool is_noneEvent();
//返回所属eventloop
EventLoop *owner_loop();
//在channel所属的eventloop中删除自己
void remove();
public:
//设置fd感兴趣事件
void enable_reading();
void dis_enable_reading();
void enable_writing();
void dis_enable_writing();
void dis_enable_all();
public:
//返回fd当前感兴趣事件状态
bool is_none_event();
bool is_writting();
bool is_reading();
public:
//设置发生不同事件的回调操作
......
private:
//与poller更新fd所感兴趣事件
void update();
//根据发生的具体事件调用相应的回调操作
void handle_event_withGuard(TimeStamp receive_time);
Poller
channelMap
map的key:sockfd value:sockfd所属的channel通道
using ChannelList = vectorEPollPoller; using ChannelMap = unordered_map ; class Poller : NonCopyable { public: Poller(EventLoop *loop); virtual ~Poller() = default; //所有io复用保留统一接口 virtual TimeStamp poll(int timeout, ChannelList *active_channels) = 0; //判断channel是否在当前poller中 bool has_channel(Channel *channel) const; //获取这个事件循环的poller static Poller *new_defaultPoller(EventLoop *loop); virtual void update_channel(Channel *channel) = 0; virtual void remove_channel(Channel *channel) = 0; protected: //key: fd value:fd所属channel ChannelMap channels_; private: EventLoop *owner_loop_; //polllers所属事件循环 };
这个封装了epoll,也就是底层的demultiplex(多路事件分发),里面包含了一个指向Channel的指针,以及自己在内核事件表中的fd
fill_active_channels
因为文件描述符和channel绑定在一起,所以不仅要注册文件描述符还要把channel注册到epoll上面,当有事件发生时候,我们要返回这个channel,因为只有当我们返回这个channel时,才能知道执行回调函数。
数据:
int epollfd_;
//key: fd value:fd所属channel
ChannelMap channels_; //unordered_map;
方法:
//调用epoll_wait,并将发生事件的channel填写到形参active_channel中
TimeStamp poll(int timeout, ChannelList *active_channels) override;
//往channel_map中添加channel
void update_channel(Channel *channel);
//channel_map中删除channel
void remove_channel(Channel *channel);
private:
//填写活跃的链接
void fill_active_channels(int events_num, ChannelList *active_channels) const;
//更新channel,调用epoll_ctl
void update(int operation, Channel *channel);
EventLoop(channel 和 epoll之间的桥梁)
这个是事件循环类,主要包含两个组件 ----- Poller以及Channel
数据:
atomic_bool looping_;
atomic_bool quit_; //标志退出loop循环
const pid_t threadId_; //记录当前loop所在线程id
TimeStamp poll_return_time_; //poller返回的发生事件的时间点
unique_ptr poller_;
int wakeup_fd; //当main loop获取一个新用户的channel,通过轮询算法,选择一个subloopp,通过该成员唤醒subloop,处理channel
unique_ptr wakeup_channel_; //包装wakefd
ChannelList active_channels; //eventloop 所管理的所有channel
atomic_bool calling_pending_functors_; //标识当前loop是否有需要执行的回调操作
vector pending_Functors_; //loop所执行的所有回调操作
mutex functor_mutex_; //保护pending_functors
方法:
//开启事件循环
void loop();
//退出事件循环
void quit();
TimeStamp get_poll_returnTime() const { return poll_return_time_; }
//在当前loop中执行cb
void run_in_loop(Functor cb);
//把cb放入队列中,唤醒loop所在线程执行cb(pending_functor)
void queue_in_loop(Functor cb);
//唤醒loop所在线程
void wakeup();
//poller的方法
void update_channel(Channel *channel);
void remove_channel(Channel *channel);
bool has_channel(Channel *channel);
//判断eventloop对象是否在自己的线程中
bool is_in_loopThread() const;
private:
void handle_read(); //wake up
void do_pending_functors(); //执行回调
可以看到,其有一个指向Poller的指针,以及一个存储Channel的容器,ChannelList。
wakeupFd值得注意的是,这里还有一个wakeup_fd,这是干什么的呢?
mainReactor给subReactor如何分配新连接的所采用的负载算法–轮询操作。没有事件发生的话,loop所在的线程都是阻塞的,假如说mainReactor监听到一个新用户的连接,得到了新用户事件的fd和感兴趣的channel,那么这个channel怎么扔给subReactor呢,subReactor现在都在睡觉,假如我们通过轮询决定把新用户连接的channel分发给subReactor,那么怎么把它唤醒呢?试想一下,如果一个eventloop一直处于epoll_wait的阻塞状态,那么我main reactor怎么去给他分配新的连接?我不得叫醒它?
唤醒 eventloop用的!当main loop获取一个新用户的channel,通过轮询算法,选择一个subloopp,通过该成员唤醒subloop,处理channel
在libevent中也有相似的组件,不过用的是sockpair,相当于主loop和子loop创建sockpair,相当于双向sockfd,走的是网络通信,不如eventfd,而muduo中用的则是更加高效的eventfd(事件驱动,更快,8字节缓冲区,更省)。上层模块通过往eventfd中去写,触发写事件,内核就自然而然的将相应的eventloop唤醒了。
eventfd这是一个系统API,在内核可以直接notify应用程序,让它起来做事情,相当于线程间一种通信机制
Poller监听那些channel发生事件了,然后上报给EventLoop,通知channel处理相应的事件。
执行当前eventLoop事件循环需要处理的回调操作
mainloop 主要是接受新用户的连接 accept就会返回一个跟客户端通信的fd,然后用channel打包fd 已连接用户的channel分发给subLoop mainLoop 事先注册一个回调cb(需要subloop来执行) //监听两类fd 一种是client的fd,一种wakeup的fd 但 poll_return_time_ = poller_->poll(k_poll_timeout, &active_channels);现在还在睡觉,还在阻塞着 mainloop wakeup以后唤醒subloop 唤醒以后subloop做do_pending_functors(); //执行当前EventLoop事件循环需要处理的回调操作
没有再IO线程和worker线程之间像队列来缓冲一下,用队列来缓冲一下。mainloop拿到channel以后放到队列中,worker线程从队列中取出来消费。而是直接派发的 通过轮询的算法的派发,通过eventup来唤醒



