栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

muduo

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

muduo

文章目录
  • 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)。

channel

理解为通道,对文件描述符管理的一个类,封装了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 = vector;
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所属事件循环
};
EPollPoller

这个封装了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应用程序,让它起来做事情,相当于线程间一种通信机制

loop

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来唤醒

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/933177.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号