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

本文是Linux多线程服务端编程读书笔记,介绍了从0开始实现类似muduo的基于Reactor模式的C++网络库。涉及Evenloop类、Channel class类、Poller class等多个类的设计,还包括TimerQueue定时器、EventLoop::runInLoop()函数等功能,以及TCP网络库的实现和Epoll机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

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

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

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

  1. muduo的定时器功能由三个class实现,TimeId、Timer、TimeQueue。用户只能看到第一个class,另外两个都是内部实现的细节
  2. 使用的数据结构是std::set
  3. TimerQueue使用了一个Channel来观察timerfd_上的readable事件
5、 EventLoop::runInLoop()函数
  1. EvenLoop在它的IO线程内执行某个用户任务回调即runInLoop(),如果用户在当前IO线程调用这个函数,回调会同步进行;如果用户在其他线程调用该函数,回调函数cb会加入到队列,IO线程会被唤醒来调用这个Functor

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

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

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

  3. EventLoopThread class

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

    2. 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网络库

  1. Acceptor class

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

  2. TcpServer class

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

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

    • 是muduo里面唯一默认使用shared_ptr来管理的class,也是唯一继承enable_shared_from_this的class
    • muduo里面只有一种关闭连接的方式,被动关闭,即对方先关闭连接,本地read(2)返回0,触发关闭逻辑
7 、Epoll
  1. epoll(4)是Linux独有的高效的IO多路复用机制,与poll的不同之处在于poll每次返回整个文件的描述符数组,用户代码需要遍历数组以找到哪些文件描述符上面有IO事件,而epoll_wait返回的是活动的fd的列表,需要遍历的数组通常会小很多
  2. 在并发连接数大而活动连接比例不高时候,epoll(4)比poll(2)更加高效
注意: 本部分主要设计到网络库的开发过程,具体需结合课本与源码结合起来看
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值