Linux 多线程服务端编程读书笔记(八)

    xiaoxiao2025-05-06  24

    Linux 多线程服务端编程读书笔记(八)

    第八章 muduo 网络库设计与实现

    ​ 这一章从0开始实现一个类似muduo的基于Reactor模式的C++网络库

    1、 Evenloop类
    one loop per thread 顾名思义每个线程都只能有一个Evenloop对象。故构造之前要检查当前线程是否已经创建其构造函数会记住本对象所属的线程,创建了Evenloop对象的线程时IO线程,主要功能是运行事件循环除此之外,该类还拥有一个Poller类对象和一个ChannelList对象(用于保存Poller对象返回的所有的活跃fd)。
    2、Channel class类
    每一个Channel对象自始至终只属于一个EvenLoop,因此每个Channel对象都只属于某一个IO线程每个Channel对象自始至终只负责一个文件描述符(fd)的IO事件分发,但不拥有这个fd,也不会再析构的时候关闭这个fd.Channel会把不同的IO事件分发为不同的回调,例如ReadCallback、WriteCallback等Channel的成员函数都只能在IO线程调用,因此更新数据都不需要加锁
    3 、 Poller class
    Poller class 是IO多路复用的封装,同时支持poll(2)和epoll(4)两种多路复用机制Poller 是EvenLoop的间接成员,只供其owner EvenLoop在IO线程中调用,因此无需加锁。其生命期与EvenLoop相等。Poller并不拥有Channel,Channel在析构之前必须自己unregister(EventLoop::removeChannel()),避免空悬指针
    4、TimerQueue定时器

    ​ 传统的Reactor通过控制select(2)和poll(2)的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以处理IO事件相同的方式来处理定时,代码的一直性要求的更好

    muduo的定时器功能由三个class实现,TimeId、Timer、TimeQueue。用户只能看到第一个class,另外两个都是内部实现的细节使用的数据结构是std::setTimerQueue使用了一个Channel来观察timerfd_上的readable事件
    5、 EventLoop::runInLoop()函数

    EvenLoop在它的IO线程内执行某个用户任务回调即runInLoop(),如果用户在当前IO线程调用这个函数,回调会同步进行;如果用户在其他线程调用该函数,回调函数cb会加入到队列,IO线程会被唤醒来调用这个Functor

    void EventLoop::runInLoop(const Functor& cb) { if (isInLoopThread()) { cb(); } else { queueInLoop(cb); } }

    有了这个功能,我们就能够轻易地在线程间调配任务,比方说吧TimerQueue的成员函数调用移动到IO线程,这样可以在不用锁的情况下保证线程安全性

    IO线程平时阻塞到事件循环EvenLoop::loop()的poll(2)调用中,为了让IO线程立刻唤醒它,传统的方法是使用pipe(2),IO线程始终监视此管道的可读事件,需要唤醒的时候,其他线程网管道里写一个字节。** 现在的Linux有了eventfd(2),可以更加高效的唤醒,因为不必管理缓冲区

    EventLoopThread class

    一个线程可以有不止一个IO线程,可以按照优先级将不同的Socket分给不同的IO线程,避免优先级反转,为了方便将来使用故定义了此类

    EventLoopThread 会启动自己的线程,并在其中运行EventLoop::loop();

    ventLoop* EventLoopThread::startLoop() { assert(!thread_.started()); thread_.start(); { MutexLockGuard lock(mutex_); while (loop_ == NULL) { cond_.wait(); } } return loop_; } void EventLoopThread::threadFunc() { EventLoop loop; { MutexLockGuard lock(mutex_); loop_ = &loop; cond_.notify(); } loop.loop(); //assert(exiting_); }
    6 、实现TCP网络库

    本节开始,逐步实现一个非阻塞TCP网络库

    Acceptor class

    用于accept(2)新TCP连接,并通过回调通知使用者,是内部类,供TcpServer使用,声明周期由后者控制。

    TcpServer class

    它的功能是管理accept(2)获得的TcpConnection。Tcpserver是供用户直接使用的,生命周期由用户控制用户只需要设置好callback,调用start()即可TcpServer内部使用Acceptor来获得新连接的fd。它保存用户提供的Connection callback和MessageCallBack。每个TcpConnection对象都有一个名字,这个名字是由其所属的TcpServer在创建TcpConnection对象时生成的,名字是ConnectionMap的key

    TcpConnecttion class

    该类时muduo里面最复杂,最核心的class

    是muduo里面唯一默认使用shared_ptr来管理的class,也是唯一继承enable_shared_from_this的classmuduo里面只有一种关闭连接的方式,被动关闭,即对方先关闭连接,本地read(2)返回0,触发关闭逻辑
    7 、Epoll
    epoll(4)是Linux独有的高效的IO多路复用机制,与poll的不同之处在于poll每次返回整个文件的描述符数组,用户代码需要遍历数组以找到哪些文件描述符上面有IO事件,而epoll_wait返回的是活动的fd的列表,需要遍历的数组通常会小很多在并发连接数大而活动连接比例不高时候,epoll(4)比poll(2)更加高效

    注意: 本部分主要设计到网络库的开发过程,具体需结合课本与源码结合起来看

    最新回复(0)