Muduo网络库

Muduo网络库本身并不复杂,是一个新手入门C++面向对象网络编程的经典实战项目。但是,新手在刚刚上手读代码的时候,非常容易陷入代码的汪洋大海,迷失方向。本文旨在简要梳理Muduo网络库的核心内容,帮助初学者快速上手源码阅读。

第一部分:概述

通过学习Muduo网络库的源码,初学者可以掌握的主要知识点有:

  • EventLoop事件循环的实现方式;
  • Reactor反应堆模式在TCP网络通信编程中的基本实现方式;(可以从《UNIX网络编程》中参考各种通信模型)
  • Socket API,Epoll/Poll 等I/O多路复用API的封装方式;(进一步探究需要读《UNIX网络编程》)
  • 多线程编程原语在Linux环境下的封装方式(仅针对pthread接口的封装,如果希望使用C++11 std::thread库实现的话可以跳过)
  • 日志类库,时间处理函数库,异步文件日志库等实用功能模块的基本实现方式;
  • CMake源码构建工具的使用;

以上知识点,是建立在读者已经充分理解C++语言的基础上来讲的。在Muduo网络库中,大量使用了C++11的高级特性,例如智能指针std::unique_ptrstd::shared_ptr;lambda表达式,std::function仿函数;移动语义等等。如果读者对这些语言特性还未掌握,读源码的话会比较吃力,因此建议在语言学习基础充分扎实之后再来做源码的项目实践。但是总的来说,学习Muduo网络库对于C++面向对象编程的“继承,封装,多态”三大特性中的封装特性会有一个全面且深入的认识。

然而在这Muduo的源码中,对于多态和继承特性的使用仅仅出现在了Poller类的处理上。Poller抽象类提供接口,并通过多态的方式实现了EpollPoller和PollPoller等多路复用类。初学者可以从Poller类及其派生类的实现方式,学习到如何利用多态,对接口和实现进行分离(面向对象的设计原则之一)。

Muduo网络库的设计理念非常简单,就是为了用最简单的实现方式完成一个高性能、且功能完整的网络通信程序库,因此在设计上并没有采用过于复杂的语言特性,这是一个优点也是一个遗憾。如果读者在学习完Muduo网络库之后有更多想法,可以尝试通过继承和多态的特性重写Muduo网络库,使其更加灵活和可扩展。实际上,有许多开源的网络库就是这么做的,例如著名的C++ Web框架drogon中使用的trantor网络库,就是基于Muduo的设计进行重写的,加入了继承和多态的特性,有兴趣的可以自行研究trantor的源码。

此外,Muduo网络库使用的CMake源码构建工具是非常出色的。它基本上完整地体现了一个标准工程项目应该具有的CMakeLists.txt文件的形式和书写方法。初学者可以用Muduo网络库学习并深入掌握CMake构建工具的使用方式。

第二部分:核心类库梳理

(细节持续更新)

1. Reactor的核心机制:事件分发与事件循环

EventLoop流程简图:

EventLoop

一个事件循环,注意,一个创建了EventLoop对象的线程是IO线程

  • 主要接口

    • loop
      死循环,阻塞在Poller的poll函数,等待唤醒
      唤醒后执行ChannelList中每个Channel的回调
      最后执行任务队列中的Functor

    • runInLoop
      在IO线程中执行用户回调Functor,若调用者非IO线程,则会调用queueInLoop

    • queueInLoop
      当调用者并非当前EventLoop所在线程时,将Functor存入EventLoop的任务队列
      从而保证Functor由IO线程执行,这是线程安全的保证之一

    • updateChannel与removeChannel
      核心中的核心,通过这个公有接口建立起Channel和Poller沟通的桥梁
      Channel通过这个接口向Poller注册或者移除自己的fd
      实现了Poller和Channel两端的解耦

    • 核心实现:handleEvent
      遍历所有的activeChannelList_,并依次执行这些Channel中注册的回调函数
      这个环节非常非常关键,是一切事件派发机制中回调执行的地方

  • 主要成员

    • wakeupchannel_
      通过eventfd唤醒的channel
      EventLoop可以通过这个Channel唤醒自己执行定时任务
    • activeChannelList_
      通过一次poll获得的所有发生事件的Channel指针列表
    • pendingFunctors_
      所有非IO线程调用的用户回调都会存放在这个队列中,通过mutex互斥量保护
    • poller_
      一个多路复用实例

Poller

I/O多路复用接口(抽象)类,对I/O多路复用API的封装

  • 主要接口

    • poll
      是Poller的核心功能,使用派生类的poll或者epoll_wait来阻塞等待IO事件发生
      通过派生类的实现来填充EventLoop的activeChannelList_

    • static createNewPoller:
      工厂函数,创建一个Poller实例
      在EpollPoller中,每个实例对应一个epollfd

    • update
      更新I/O多路复用的状态,例如epoll_ctl的ADD,MOD,DEL

  • 主要成员

    • loop
      控制当前Poller的EventLoop指针
    • 其余成员由派生类实现

Channel

I/O事件派发器,封装了发生I/O事件的文件描述符,关心的事件类型,以及事件对应的回调

  • 主要接口

    • setter,getter
      设置关注读写操作,获取实际发生的读写事件

    • update
      根据回调的执行情况,通过EventLoop的updateChannel接口更新事件状态

  • 主要成员

    • loop
      Channel拥有者的EventLoop指针
      每个EventLoop可以控制若干个Channel
    • fd_
      这个Channel所维护的文件描述符
    • events_
      这个Channel所关心的IO事件
    • revents_
      这个Channel的文件描述符上实际发生的事情

TimerQueue

通过timerfd实现的定时器功能,为EventLoop扩展了一系列runAt,runEvery,runEvery等函数
TimerQueue中通过std::set维护所有的Timer,也可以使用优先队列实现

  • 主要接口

    • addTimer
      向定时器中添加Timer
      Timer是一个封装了回调函数和时间的类
      通过内部实现addTimerInLoop保证线程安全
    • cancel
      从定时器中移除某个Timer
    • 核心实现:getExpired
      从timers_集合中移除已经到期的Timer
    • 核心实现:handleRead
      向timerfdChannel注册的回调函数
      在timerfd触发可读时间时,也就是定时器到期的时候执行
      会调用getExpired,并依次执行返回的所有Timer中的回调
  • 主要成员

    • timerfdChannel_
      用来唤醒计时器的Channel,维护了一个timerfd,注册了TimerQueue::handleRead回调
    • std::set<std::pair<Timestamp, Timer*>> timers_
      使用std::set容器来取得最近将要超时的Timer,从而决定是否resetTimerfd

TimerQueue执行用户回调的时序图:

注意事项

任何在IO线程(也就是具有EventLoop)中绑定的Channel中发生的IO事件,最终都会在EventLoop中的handleEvent环节依次处理。
理解这一点非常重要,如果出问题需要debug,基本上都是在handleEvent环节中有线程安全的问题。

其他辅助类

  • EventLoopThread
    封装了一个EventLoop的独立线程

  • EventLoopThreadPool
    封装了若干个EventLoopThread的线程池,所有者是一个外部的EventLoop

  • Timer
    定时器相关的类

2. 网络通信封装

TcpConnection

封装了一个TCP链接

  • 主要接口

    • send
      发送数据的主要接口,最终通过内部实现在runInLoop中发送数据

    • 回调setter

    • connectionEstablished
      当连接建立时,应当只执行一次
      将自身的shared_from_this指针与Channel绑定
      令Channel激活对可读IO事件的关注

    • connectionDestroyed
      当连接断开时,应当只执行一次
      将自己的Channel从所属EventLoop中移除

  • 主要成员

    • loop
      主要回调都通过EventLoop所在线程处理

    • Channel
      通过Channel的回调调用自己的回调

    • Socket
      连接所属的套接字fd

    • localaddr,peeraddr
      本地和对端socketaddr

    • 各种回调callback

    • inputbuffer,outputbuffer
      应用层输入,输出缓冲区

TcpClient

客户端封装,需要注意的是,muduo的client类只负责一个连接

  • Connector - TcpClient逻辑上的内部类

    对于连接方法的封装

    • 主要接口

      • setNewConnectionCallback
        设置TcpClient交给的回调函数

      • start
        最后通过loop的runInLoop调用
        调用connect内部实现

      • stop
        最终通过loop的queueInLoop调用
        回收Channel控制的套接字(如果有的话)
        设置connect_标记为false

      • retry
        connect_标记为true,则重连

      • 核心实现:connect
        调用Socket::connect方法连接服务端
        连接成功后,创建一个Channel
        将自身的handleWrite回调注册到Channel上,并激活可写事件关注

      • 核心实现:handleWrite
        根据getSockError的情况决定调用创建连接回调,或是错误回调,或retry操作
        其中包含了TcpClient注册的回调newConnection

    • 主要成员

      • loop

      • channel
        unique_ptr指针,仅在连接建立时动态创建Channel对象
        当channel触发可写事件时,执行handleWrite
        并在handleWrite中执行TcpClient的newConnection

      • serverAddr

      • connector

      • connect_
        非常重要的一个标记,决定了是否retry

  • 主要接口

    • 回调setters
      这些回调函数会在新连接建立时,通过newConnection内部实现方法传递给TcpConnction对象
    • 核心实现:newConnection
      在构造时将这个函数作为回调注册给connector_对象
      在Connector中的Channel执行本回调后,创建一个新的TcpConnection对象
    • connect
      调用Connector的start接口
    • stop
      调用Connector的stop接口
  • 主要成员

    • loop
    • connector
      TcpClient所维护的一个连接器
    • retry_
    • TcpConnection connection_
      TcpClient所维护的一个TCP连接对象

关于连接中回调的传递,参考下面的简图:

TcpServer

服务端封装 - muduo的server端维护了多个tcpconnection
注意TcpServer本身不带Channel,而是使用Acceptor的Channel

  • Acceptor - 逻辑上的内部类

    接受器封装,实质上就是对Channel的多一层封装

    • 主要接口

      • listen
        监听连接
        当新连接进入时,调用Socket::accept创建套接字,触发TcpServer的回调
      • setNewConnectionCallback
        TcpServer通过该接口设置回调,当新连接套接字创建后,创建TcpConnection对象
      • 核心实现:
        通过socket::accept接受新连接,获得套接字fd
        这个fd作为参数调用TcpServer注册的回调
    • 主要成员
      • loop
      • channel
      • idlefd
        非常巧妙的设计,在服务器压力过大,无法新建文件描述符时,通过这个idlefd拒绝连接
        来自libevent的设计
  • 主要接口

    • 回调setters
      这些回调函数会在新连接建立时传递给TcpConnction对象
    • start
      启动threadPool_线程池
      在runInLoop中执行acceptor的listen
      这里专门设置了一个started_标记,防止多次运行start
    • 核心实现:newConnection
      从accept回调中获得的fd动态创建新的TcpConnection对象
      为连接对象注册各类回调函数
      将连接对象存入connectionMap_映射表里
  • 主要成员

    • loop
      这个loop也是acceptor的loop

    • acceptor

    • threadPool
      一个EventLoopThreadPool,用来存放io线程的EventLoopThread

    • socket
      服务端用来监听的socket

    • ConnectionMap
      一个连接名和实例(TcpConnectionPtr)的映射容器

TcpServer中回调的传递示意简图:

Buffer

封装了一个可变长的buffer,支持廉价的前插操作,以及内部挪腾操作避免额外申请空间

3. Socket API封装

Socket

封装了一个sockfd

SocketOps

对socket设置API的封装

InetAddress

对sockaddr系列的封装

Endian

封装了字节序转换工具函数

4. 基础类库封装

Logging

通用标准输出日志

AsyncLogging

异步文件日志

Date

日期类库封装

Muduo网络库核心梳理相关推荐

  1. muduo网络库学习(八)事件驱动循环线程池EventLoopThreadPool

    muduo是支持多线程的网络库,在muduo网络库学习(七)用于创建服务器的类TcpServer中也提及了TcpServer中有一个事件驱动循环线程池,线程池中存在大量线程,每个线程运行一个Event ...

  2. muduo网络库学习(四)事件驱动循环EventLoop

    muduo的设计采用高并发服务器框架中的one loop per thread模式,即一个线程一个事件循环. 这里的loop,其实就是muduo中的EventLoop,所以到目前为止,不管是Polle ...

  3. 基于C++11的muduo网络库

    文章目录 写在前面 项目编译问题 库安装的问题 项目测试代码 关于压力测试 项目概述 muduo网络库的reactor模型 muduo的设计 muduo各个类 辅助类 NonCopyable Time ...

  4. muduo网络库源码复现笔记(十七):什么都不做的EventLoop

    Muduo网络库简介 muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕.它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程 ...

  5. muduo网络库的封装

    一.基础socket编程 网络编程的底层离不开socket,其处理流程表示如下: int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockadd ...

  6. muduo网络库使用心得

    上个月看了朋友推荐的mudo网络库,下完代码得知是国内同行的开源作品,甚是敬佩.下了mudo使用手冊和035版的代码看了下结构,感觉是一个比較成熟并且方便使用的网络库.本人手头也有自己的网络库,尽管不 ...

  7. muduo网络库学习总结:基本架构及流程分析

    muduo网络库学习:基本架构及流程分析 基本架构 Basic Reactor Mutiple Reactor + ThreadPool muduo库的基本使用 基本结构介绍 EventLoop类 P ...

  8. muduo网络库学习(1)

    muduo网络库学习(1) 文章目录 muduo网络库学习(1) 前言 一.muduo是什么? 二.代码结构 1.base库 2.net库 3.附属库 二.网络库结构 总结 前言 本章节主要介绍mud ...

  9. muduo网络库设计与实现(二)

    muduo网络库设计与实现(一) 文章目录 muduo网络库设计与实现(一) base InetAddress Socket 单线程网络库 Acceptor TcpServer TcpConnecti ...

最新文章

  1. zabbix nginx php postgresql,debian10安装zabbix4.2+nginx+postgresql
  2. lol简介/html
  3. nginx 配置文件
  4. 外卖排序系统特征生产框架
  5. 24.command-executor
  6. 避免许多if块进行验证检查
  7. mysql csdn 知乎_CSDN 怎么样?
  8. Java 遍历HashTable
  9. arm中的.a文件如何产生的_如何在IPFS中Pin一个文件?
  10. 【渝粤教育】国家开放大学2018年春季 0007-21T文书档案管理 参考试题
  11. poj1273:Drainage Ditches
  12. 目录 1. Java中使用Ognl表达式引擎 1 1.1.1. 一、Ognl简介 1 1.1.2. 二、Ognl应用场景 1 1.2. 基本介绍 vs 模板语言 2 1.Java中使用Ognl表达
  13. Xilinx平台SRIO介绍(六)SRIO收发测试
  14. 网页页面缩小放大的快捷键
  15. Android Camera动态人脸识别+人脸检测基于OpenCV(无需OpenCVManager)
  16. spark视频-第二期:Shark、SparkSQL
  17. Java中HashMap常见问题 -- 扩容、树化、死链问题
  18. 工业虚拟现实解决方案
  19. 【Unity打包崩溃】安卓包遇到CrashReport-Native: Faile to open comm file(/system/build.prop)就闪退
  20. 五一后“实在高校行”紧锣密鼓走进四所高校,校企合作硕果累累!

热门文章

  1. 订餐网的商业模式之Opentable
  2. Learning-Pixel-level-Semantic-Affinity-with-Image-level-Supervision
  3. Ubuntu安装redis详细教程
  4. SpringBoot接收前端传来的json数据
  5. Cordova + vue 打包安卓(Android) apk
  6. Python遗传和进化算法框架(二)Geatpy库函数和数据结构
  7. 论文剽窃者“自爆家门”?CVPR 最后一天上演“一出好戏 ”!
  8. NBA 本周五将空场复赛,球迷可用微软 Teams 现场互动
  9. lexical or preprocessor issue
  10. 二次优化问题dfp_最优化之DFP算法考试题