Muduo网络库核心梳理

2023-11-13

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执行用户回调的时序图:
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连接对象

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

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中回调的传递示意简图:
TcpServer回调简图

Buffer

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

3. Socket API封装

Socket

封装了一个sockfd

SocketOps

对socket设置API的封装

InetAddress

对sockaddr系列的封装

Endian

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

4. 基础类库封装

Logging

通用标准输出日志

AsyncLogging

异步文件日志

Date

日期类库封装

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Muduo网络库核心梳理 的相关文章

  • 从父类调用子类方法

    a doStuff 方法是否可以在不编辑 A 类的情况下打印 B did stuff 如果是这样 我该怎么做 class Program static void Main string args A a new A B b new B a
  • 未解决的包含:“cocos2d.h” - Cocos2dx

    当我在 Eclipse 中导入 cocos2dx android 项目时 我的头文件上收到此警告 Unresolved inclusion cocos2d h 为什么是这样 它实际上困扰着我 该项目可以正确编译并运行 但我希望这种情况消失
  • 如何忽略“有符号和无符号整数表达式之间的比较”?

    谁能告诉我必须使用哪个标志才能使 gcc 忽略 有符号和无符号整数表达式之间的比较 警告消息 gcc Wno sign compare 但你确实应该修复它警告你的比较
  • WPF 中的调度程序和异步等待

    我正在尝试学习 WPF C 中的异步编程 但我陷入了异步编程和使用调度程序的困境 它们是不同的还是在相同的场景中使用 我愿意简短地回答这个问题 以免含糊不清 因为我知道我混淆了 WPF 中的概念和函数 但还不足以在功能上正确使用它 我在这里
  • 在 Visual Studio 2008 上设置预调试事件

    我想在 Visual Studio 中开始调试程序之前运行一个任务 我每次调试程序时都需要运行此任务 因此构建后事件还不够好 我查看了设置的 调试 选项卡 但没有这样的选项 有什么办法可以做到这一点吗 你唯一可以尝试的 IMO 就是尝试Co
  • WPF TabControl,用C#代码更改TabItem的背景颜色

    嗨 我认为这是一个初学者的问题 我搜索了所有相关问题 但所有这些都由 xaml 回答 但是 我需要的是后台代码 我有一个 TabControl 我需要设置其项目的背景颜色 我需要在选择 取消选择和悬停时为项目设置不同的颜色 非常感谢你的帮助
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • 从路径中获取文件夹名称

    我有一些路c server folderName1 another name something another folder 我如何从那里提取最后一个文件夹名称 我尝试了几件事 但没有成功 我只是不想寻找最后的 然后就去休息了 Thank
  • 将自定义元数据添加到 jpeg 文件

    我正在开发一个图像处理项目 C 我需要在处理完成后将自定义元数据写入 jpeg 文件 我怎样才能做到这一点 有没有可用的图书馆可以做到这一点 如果您正在谈论 EXIF 元数据 您可能需要查看exiv2 http www exiv2 org
  • for循环中计数器变量的范围是多少?

    我在 Visual Studio 2008 中收到以下错误 Error 1 A local variable named i cannot be declared in this scope because it would give a
  • 尝试安装 LESS 时出现“请尝试以 root/管理员身份再次运行此命令”错误

    我正在尝试在我的计算机上安装 LESS 并且已经安装了节点 但是 当我输入 node install g less 时 出现以下错误 并且不知道该怎么办 FPaulMAC bin paul npm install g less npm ER
  • 如何将单个 char 转换为 int [重复]

    这个问题在这里已经有答案了 我有一串数字 例如 123456789 我需要提取它们中的每一个以在计算中使用它们 我当然可以通过索引访问每个字符 但是如何将其转换为 int 我研究过 atoi 但它需要一个字符串作为参数 因此 我必须将每个字
  • clang 实例化后静态成员初始化

    这样的代码可以用 GCC 编译 但 clang 3 5 失败 include
  • 当操作繁忙时,表单不执行任何操作(冻结)

    我有一个使用 C 的 WinForms 应用程序 我尝试从文件中读取一些数据并将其插入数据表中 当此操作很忙时 我的表单冻结并且无法移动它 有谁知道我该如何解决这个问题 这可能是因为您在 UI 线程上执行了操作 将文件和数据库操作移至另一个
  • 无法使用 wget 在 CentOS 机器上安装 oracle jdk

    我想在CentOS上安装oracle java jdk 8 我无法安装 java jdk 因为当我尝试使用命令安装 java jdk 时 root ADARSH PROD1 wget no cookies no check certific
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • Validation.ErrorTemplate 的 Wpf 动态资源查找

    在我的 App xaml 中 我定义了一个资源Validation ErrorTemplate 这取决于动态BorderBrush资源 我打算定义独特的BorderBrush在我拥有的每个窗口以及窗口内的不同块内
  • 如何使用 std::string 将所有出现的一个字符替换为两个字符?

    有没有一种简单的方法来替换所有出现的 in a std string with 转义 a 中的所有斜杠std string 完成此操作的最简单方法可能是boost字符串算法库 http www boost org doc libs 1 46
  • 使用按位运算符相乘

    我想知道如何使用按位运算符将一系列二进制位相乘 但是 我有兴趣这样做来查找二进制值的十进制小数值 这是我正在尝试做的一个例子 假设 1010010 我想使用每个单独的位 以便将其计算为 1 2 1 0 2 2 1 2 3 0 2 4 虽然我
  • 两种情况或 if 哪个更快? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我必须制作一个 非常 轻的脚本 它将接受用户的选项并调用脚本中的函数来执行一些任务 现在我可以使用 IF 和 CASE 选项 但我想知道两

随机推荐

  • Aix上的压缩与解压

    1 compress生成一个后缀为 Z的压缩文件 可用compress d或者uncompress解压后缀为 Z的文件 若压缩的文件是文本文件 可用zcat直接查看压缩文件 不需要先解压 再用cat 2 gzip生成一个后缀为 gz的压缩文
  • python backtrace注意事项

    1 当python异常时 web服务器一般会记录异常日志 比如uwsgi 如果用python自带的web server 那么启动时要把输出重定向一下 2 有时要把异常backtrace与普通日志记录在一起 可以在logging xxx 函数
  • js浮点数计算精度问题

    js语言在进行数字计算时会发生计算精度失真的情况 因为javascript使用了IEEE 745浮点数表示法 在运算是会将浮点数转换为二进制数字计算 例如 0 1 gt 0 0001100110011001 无限 0 2 gt 0 0011
  • 【干货】今日头条的新闻推荐算法原理

    信息越来越海量 用户获取信息越来越茫然 而推荐算法则能有助于更好的匹配海量内容和用户需求 使之更加的 有的放矢 为让产业各方更好的了解算法分发的相关技术和原理 我们特整理了当下最具影响力的平台的相关干货 和各方分享 本期微信 我们将推荐影视
  • pip安装pytorch 清华镜像

    每次要搭配环境的时候就头大 资源基本是国外 下载起来特别慢 有些连服务器都访问不了 不用镜像 按照书上或者网上的正常流程搭配环境 基本上都是以超时告终 然后就在网上疯狂找资源 网上有人说是个程序员都会翻墙 我可能是个假的程序员吧 什么都不会
  • Arthas watch命令使用

    目录 属性遍历深度 1 watch 类全限定名 方法名 遍历深度为1的入参 对象 返回信息 2 watch 类全限定名 方法名 x n 观察遍历深度为n的入参 对象 返回信息 观察事件点 1 watch 类全限定名 方法名 params x
  • Python爬虫从入门到精通:(39)增量式爬虫_Python涛哥

    概念 检测网络数据更新的情况 以便于爬取到最新更新出来的数据 实现核心 去重 实战中去重的方式 记录表 记录表需要记录什么 记录的一定是爬取过的相关信息 例如某电影网 爬取过的相关信息 每一部电影详情页的url 只需要使用某一组数据 该组数
  • 一个产品的商业目标应该从哪些方面制定?

    产品面向社区治理相关服务 比如信息发布 事件调解 活动发布 数据统计等等 但感觉缺少合理的商业策略和商务模式 文心一言 一个产品的商业目标应该从以下几个方面制定 市场定位 确定你的产品的目标客户是谁 这可以是你的社区治理服务所面向的人群 也
  • 【数学公式】Mathpix和MathType等等

    MathML MathML指 数学标记语言 是XML语言的一个子集 用来在web网页 甚至部分软件中显示数学公式 简言之 就是使用特殊的类似HTML的标记在网页中显示数学公式 MathType公式编辑器 MathType是一个强大的数学公式
  • JSP的原理

    Tomcat的lib目录下的jasper jar包 这个包里面有一个HttpJspBase类 这个类我们看一下源码 Source code recreated from a class file by IntelliJ IDEA power
  • CC++ 标头和源文件:它们如何工作?

    本文将向您展示将程序划分为C中的组件部分或正确使用标头和源文件C 诀窍 介绍 我主要是为我的一个朋友写这篇文章的 但是 如果我不与大家分享这一点 我会对社区造成伤害 所以就在这里 我们将探索标头和源文件以及它们的作用 这些代码的大部分在 C
  • SSH框架简介篇

    文章目录 概述 目录结构 struts Spring Hibernate 总结 概述 SSH框架 Struts Spring Hibernate 是一种广泛应用的Java企业级开发框架组合 它将Struts Spring和Hibernate
  • LVM原理及配置

    1 简介 1 1 什么是LVM LVM是 Logical Volume Manager 逻辑卷管理 的简写 它由Heinz Mauelshagen在Linux 2 4内核上实现 目前最新版本为 稳定版1 0 5 开发版 1 1 0 rc2
  • 中国天气网接口

    中国天气weather com http m weather com cn data 101110101 html 六天预报 http www weather com cn data sk 101110101 html 实时天气信息 其中1
  • RSA pkcs1与pkcs8 java获取私钥

    RSA pkcs1与pkcs8 java获取私钥 目录 RSA pkcs1与pkcs8 java获取私钥 获取秘钥 获取pkcs1 格式秘钥 获取pkcs8格式秘钥 读取秘钥信息 解密 获取秘钥 maven依赖
  • 【网络】几种常见的协议

    几种常见的协议 DNS Domain Name System 域名解析协议 端口号 53 通过域名解析获得域名所对应的IP FTP File Transfer Protocol 文件传输协议 端口号 21 用户可通过客户机程序向远程主机上传
  • el-input正则限制

    el input限制只能输入1 9且只保留一位小数 return rules deductionPrice required true message 请输入折扣力度 trigger blur validator this valuePri
  • 栈溢出学习

    前言 跟着ctfwiki学习 所有题目都在ctfwiki上可以找到 加油加油 栈溢出原理 栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数 因而导致与其相邻的栈中的变量的值被改变 看一个简单的程序 include
  • 【枚举的定义;枚举变量的定义、初始化和赋值】(学习笔记16--枚举)

    目录 枚举的定义 枚举变量的定义 枚举变量的初始化与赋值 使用枚举类型 可以提高程序代码的健壮性和可读性 并且枚举成员属于常量 甚至可以使用枚举成员名作为维的大小 来进行数组的定义 枚举的定义 定义枚举的格式为 enum 枚举名 枚举成员1
  • Muduo网络库核心梳理

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