[muduo网络库]——muduo网络库精华总结(剖析muduo网络库核心部分、设计思想)

在此之前,我们剖析了muduo库的各个模块,以及从使用muduo库搭建一个EchoServer,剖析了其内在的联系,分析了如何启动,连接建立,消息收发,连接断开的过程。到此,muduo库也算是一个小的完结了,不知道是否帮助到了大家~
但是muduo库依旧有很多思想值得我们去学习,所以在此做一个梳理总结,希望在以后的学习中,也可以借鉴起来。

one loop per thread

保证one loop per thread,就要满足以下两点:

  1. 一个线程只有一个EventLoop对象
    使用__thread关键字(__thread变量每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。)为了保证一个线程只有一个EventLoop对象,muduo定义了一个用该关键字修饰的EventLoop*变量,即__thread EventLoop *t_loopInThisThread = nullptr,当某个子线程启动时,我们只需要在子线程执行函数(即)中构造一个EventLoop对象,EventLoop的构造函数会把t_loopInThisThread初始化为this,指向这个对象。这样一来,如果该线程中再次创建一个EventLoop对象时,指针不为空就会在其构造函数中终止程序。
if(t_loopInThisThread)
{
   LOG_FATAL("Another EvnetLoop %p exists in this thread %d \n",t_loopInThisThread, threadId_);    
}
else
{
   t_loopInThisThread = this;
}
  1. 不该跨线程调用的函数不会跨线程调用
    muduo网络库保证one loop per thread的核心就是:每个线程(EventLoop)都准备一个待执行队列pendingFunctors_,用于存储待执行函数,当其他线程需要调用某个不能跨线程调用的函数时(比如TcpConnection::sendInLoopTcpConnection::shutdownInLoopTcpConnection::connectEstablished,因为他们都涉及到对fd的操作),只需要把该函数通过runInLoop或者queInLoop加入到本线程的待执行队列中,然后由本线程从队列中取出该函数并执行。例如:TcpConnection::sendInLoop(),在TcpConnection::send()调用TcpConnection::sendInLoop()发送数据之前,会先判断调用send函数的线程和TcpConnection所属loop_绑定的线程是否是同一个
if(loop_->isInLoopThread())
{
     sendInLoop(buf.c_str(),buf.size());
}
else
{
    loop_->runInLoop(std::bind(&TcpConnection::sendInLoop
                  , this
                  , buf.c_str()
                  , buf.size()
            ));
}

//EventLoop.h
bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }

threadId_ 是调用createEventfd()函数,底层调用eventfd得到的,这个在三大核心组件之EventLoop类描述过。

  1. 如果满足,说明没有跨线程调用TcpConnection::send,因此可以直接调用TcpConnection::sendInLoop发送数据;
  2. 如果不满足,则调用EventLoop::runInLoop,在EventLoop::runInLoop中,如果满足isInLoopThread(),直接执行回调函数cb,否则,就调用EventLoop::queueInLoopcb加入到待执行队列pendingFunctors_中,并在必要的时候通过wakeup()唤醒EventLoop绑定的线程。
  • 如何唤醒呢?
    谁会执行pendingFunctors_呢?EventLoop::doPendingFunctors(),那谁调用它呢?EventLoop::loop()。在这个函数中,如果子线程管理的fd上没有任何事件发生,那么该线程就会一直阻塞在epoller_->poll函数,无法执行doPendingFunctors函数了。所以是需要唤醒的。在构造EventLoop实例的时候,每一个EventLoop都有一个wakeupFd_,同样把该fd封装成Channel,变成wakeupChannel_
EventLoop::EventLoop()
       :省
       , wakeupFd_(createEventfd())
       , wakeupChannel_(new Channel(this,wakeupFd_))
  • 当在queueInLoop函数中调用wakeup()时,就会在wakeupFd_上写入一个八字节的数据,这样Poller就会检测到wakeupFd_可读,于是就解除了阻塞,进而执行doPendingFunctors,即取出pendingFunctors_的函数执行相应的回调。

Channel中的tie_

如果没有tie_,当客户端主动关闭连接时,服务器上该连接对应的sockfd的Channel会调用Channel::handleEvent()来关闭连接,从而释放TcpConnection,进而Channel对象也会析构,这就造成了Channel::handleEvent()还只执行到一半,Channel就已经被析构了,引发不可预测的后果。为了避免这个问题,muduo库使用弱指针tie_绑定到TcpConnection的共享指针上TcpConnectionPtr,如果tie_能够被转化为强共享指针,这就延长了TcpConnection的生命周期,使之长过Channel::handleEvent(),这样一来,Channel::handleEvent()来关闭连接时,Channel::handleEventguard变量依然持有一份TcpConnection,使其的引用计数不会减为0,去执行Channel::handleEventWithGuard()函数,也保证了在触发TcpConnection关闭机制后,先让TcpConnection把数据发送完再释放TcpConnection对象的资源,也就是说Channel不会在执行完Channel::handleEvent()之前被析构。这一部分我们在[muduo网络库]——EchoServer之连接断开(剖析muduo网络库核心部分、设计思想)也剖析过。

保证关闭连接时数据不会漏收

首先要强调的一点是:muduo永远都是被动关闭连接:即等待对方关闭后,自己才关闭连接,即使muduo主动关闭连接,也只关闭自己的写端,等对方关闭后,再关闭自己的读端。所以这种关闭连接的方式要求对方read到0字节后(read到0字节表示对方已经关闭了),服务器也会调用TcpConnection::handleClose()关闭连接。为什么是关闭写端,我们在TcpConnection类也进行了分析。

代码地址:https://github.com/Cheeron955/mymuduo/tree/master

好了~ 到此为止,我们对于muduo库的剖析就结束了,一个真真的完结撒花。但是muduo库中仍然有很多值得我们去学习,在这里我们只是会它的核心方法进行了剖析,对muduo库的学习也不会到此结束,在之后的学习中,又任何需要更改以及补充的地方会进一步完善此专栏。最后再次感谢大家的支持与陪伴~~

在这里插入图片描述

  • 65
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值