Muduo网络库核心梳理
Muduo网络库
Muduo网络库本身并不复杂,是一个新手入门C++面向对象网络编程的经典实战项目。但是,新手在刚刚上手读代码的时候,非常容易陷入代码的汪洋大海,迷失方向。本文旨在简要梳理Muduo网络库的核心内容,帮助初学者快速上手源码阅读。
第一部分:概述
通过学习Muduo网络库的源码,初学者可以掌握的主要知识点有:
- EventLoop事件循环的实现方式;
- Reactor反应堆模式在TCP网络通信编程中的基本实现方式;(可以从《UNIX网络编程》中参考各种通信模型)
- Socket API,Epoll/Poll 等I/O多路复用API的封装方式;(进一步探究需要读《UNIX网络编程》)
- 多线程编程原语在Linux环境下的封装方式(仅针对
pthread
接口的封装,如果希望使用C++11std::thread
库实现的话可以跳过) - 日志类库,时间处理函数库,异步文件日志库等实用功能模块的基本实现方式;
- CMake源码构建工具的使用;
以上知识点,是建立在读者已经充分理解C++语言的基础上来讲的。在Muduo网络库中,大量使用了C++11的高级特性,例如智能指针std::unique_ptr
与std::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的回调
最后执行任务队列中的FunctorrunInLoop
在IO线程中执行用户回调Functor,若调用者非IO线程,则会调用queueInLoopqueueInLoop
当调用者并非当前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_
一个多路复用实例
- wakeupchannel_
Poller
I/O多路复用接口(抽象)类,对I/O多路复用API的封装
主要接口
poll
是Poller的核心功能,使用派生类的poll或者epoll_wait来阻塞等待IO事件发生
通过派生类的实现来填充EventLoop的activeChannelList_static createNewPoller:
工厂函数,创建一个Poller实例
在EpollPoller中,每个实例对应一个epollfdupdate
更新I/O多路复用的状态,例如epoll_ctl的ADD,MOD,DEL
主要成员
- loop
控制当前Poller的EventLoop指针 - 其余成员由派生类实现
- loop
Channel
I/O事件派发器,封装了发生I/O事件的文件描述符,关心的事件类型,以及事件对应的回调
主要接口
setter,getter
设置关注读写操作,获取实际发生的读写事件update
根据回调的执行情况,通过EventLoop的updateChannel接口更新事件状态
主要成员
- loop
Channel拥有者的EventLoop指针
每个EventLoop可以控制若干个Channel - fd_
这个Channel所维护的文件描述符 - events_
这个Channel所关心的IO事件 - revents_
这个Channel的文件描述符上实际发生的事情
- loop
TimerQueue
通过timerfd实现的定时器功能,为EventLoop扩展了一系列runAt,runEvery,runEvery等函数
TimerQueue中通过std::set维护所有的Timer,也可以使用优先队列实现
主要接口
- addTimer
向定时器中添加Timer
Timer是一个封装了回调函数和时间的类
通过内部实现addTimerInLoop保证线程安全 - cancel
从定时器中移除某个Timer - 核心实现:getExpired
从timers_集合中移除已经到期的Timer - 核心实现:handleRead
向timerfdChannel注册的回调函数
在timerfd触发可读时间时,也就是定时器到期的时候执行
会调用getExpired,并依次执行返回的所有Timer中的回调
- addTimer
主要成员
- timerfdChannel_
用来唤醒计时器的Channel,维护了一个timerfd,注册了TimerQueue::handleRead回调 - std::set<std::pair<Timestamp, Timer*>> timers_
使用std::set容器来取得最近将要超时的Timer,从而决定是否resetTimerfd
- timerfdChannel_
TimerQueue执行用户回调的时序图:
注意事项
任何在IO线程(也就是具有EventLoop)中绑定的Channel中发生的IO事件,最终都会在EventLoop中的handleEvent环节依次处理。
理解这一点非常重要,如果出问题需要debug,基本上都是在handleEvent环节中有线程安全的问题。
其他辅助类
EventLoopThread
封装了一个EventLoop的独立线程EventLoopThreadPool
封装了若干个EventLoopThread的线程池,所有者是一个外部的EventLoopTimer
定时器相关的类
2. 网络通信封装
TcpConnection
封装了一个TCP链接
主要接口
send
发送数据的主要接口,最终通过内部实现在runInLoop中发送数据回调setter
connectionEstablished
当连接建立时,应当只执行一次
将自身的shared_from_this指针与Channel绑定
令Channel激活对可读IO事件的关注connectionDestroyed
当连接断开时,应当只执行一次
将自己的Channel从所属EventLoop中移除
主要成员
loop
主要回调都通过EventLoop所在线程处理Channel
通过Channel的回调调用自己的回调Socket
连接所属的套接字fdlocaladdr,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的newConnectionserverAddr
connector
connect_
非常重要的一个标记,决定了是否retry
主要接口
- 回调setters
这些回调函数会在新连接建立时,通过newConnection
内部实现方法传递给TcpConnction
对象 - 核心实现:newConnection
在构造时将这个函数作为回调注册给connector_对象
在Connector中的Channel执行本回调后,创建一个新的TcpConnection对象 - connect
调用Connector的start接口 - stop
调用Connector的stop接口
- 回调setters
主要成员
- 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注册的回调
- listen
- 主要成员
- loop
- channel
- idlefd
非常巧妙的设计,在服务器压力过大,无法新建文件描述符时,通过这个idlefd拒绝连接
来自libevent的设计
- 主要接口
主要接口
- 回调setters
这些回调函数会在新连接建立时传递给TcpConnction对象 - start
启动threadPool_线程池
在runInLoop中执行acceptor的listen
这里专门设置了一个started_标记,防止多次运行start - 核心实现:newConnection
从accept回调中获得的fd动态创建新的TcpConnection对象
为连接对象注册各类回调函数
将连接对象存入connectionMap_映射表里
- 回调setters
主要成员
loop
这个loop也是acceptor的loopacceptor
threadPool
一个EventLoopThreadPool,用来存放io线程的EventLoopThreadsocket
服务端用来监听的socketConnectionMap
一个连接名和实例(TcpConnectionPtr)的映射容器
TcpServer中回调的传递示意简图:
Buffer
封装了一个可变长的buffer,支持廉价的前插操作,以及内部挪腾操作避免额外申请空间
3. Socket API封装
Socket
封装了一个sockfd
SocketOps
对socket设置API的封装
InetAddress
对sockaddr系列的封装
Endian
封装了字节序转换工具函数
4. 基础类库封装
Logging
通用标准输出日志
AsyncLogging
异步文件日志
Date
日期类库封装
Muduo网络库核心梳理相关推荐
- muduo网络库学习(八)事件驱动循环线程池EventLoopThreadPool
muduo是支持多线程的网络库,在muduo网络库学习(七)用于创建服务器的类TcpServer中也提及了TcpServer中有一个事件驱动循环线程池,线程池中存在大量线程,每个线程运行一个Event ...
- muduo网络库学习(四)事件驱动循环EventLoop
muduo的设计采用高并发服务器框架中的one loop per thread模式,即一个线程一个事件循环. 这里的loop,其实就是muduo中的EventLoop,所以到目前为止,不管是Polle ...
- 基于C++11的muduo网络库
文章目录 写在前面 项目编译问题 库安装的问题 项目测试代码 关于压力测试 项目概述 muduo网络库的reactor模型 muduo的设计 muduo各个类 辅助类 NonCopyable Time ...
- muduo网络库源码复现笔记(十七):什么都不做的EventLoop
Muduo网络库简介 muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕.它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程 ...
- muduo网络库的封装
一.基础socket编程 网络编程的底层离不开socket,其处理流程表示如下: int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockadd ...
- muduo网络库使用心得
上个月看了朋友推荐的mudo网络库,下完代码得知是国内同行的开源作品,甚是敬佩.下了mudo使用手冊和035版的代码看了下结构,感觉是一个比較成熟并且方便使用的网络库.本人手头也有自己的网络库,尽管不 ...
- muduo网络库学习总结:基本架构及流程分析
muduo网络库学习:基本架构及流程分析 基本架构 Basic Reactor Mutiple Reactor + ThreadPool muduo库的基本使用 基本结构介绍 EventLoop类 P ...
- muduo网络库学习(1)
muduo网络库学习(1) 文章目录 muduo网络库学习(1) 前言 一.muduo是什么? 二.代码结构 1.base库 2.net库 3.附属库 二.网络库结构 总结 前言 本章节主要介绍mud ...
- muduo网络库设计与实现(二)
muduo网络库设计与实现(一) 文章目录 muduo网络库设计与实现(一) base InetAddress Socket 单线程网络库 Acceptor TcpServer TcpConnecti ...
最新文章
- zabbix nginx php postgresql,debian10安装zabbix4.2+nginx+postgresql
- lol简介/html
- nginx 配置文件
- 外卖排序系统特征生产框架
- 24.command-executor
- 避免许多if块进行验证检查
- mysql csdn 知乎_CSDN 怎么样?
- Java 遍历HashTable
- arm中的.a文件如何产生的_如何在IPFS中Pin一个文件?
- 【渝粤教育】国家开放大学2018年春季 0007-21T文书档案管理 参考试题
- poj1273:Drainage Ditches
- 目录 1. Java中使用Ognl表达式引擎	1 1.1.1. 一、Ognl简介	1 1.1.2. 二、Ognl应用场景	1 1.2. 基本介绍 vs 模板语言	2 1.Java中使用Ognl表达
- Xilinx平台SRIO介绍(六)SRIO收发测试
- 网页页面缩小放大的快捷键
- Android Camera动态人脸识别+人脸检测基于OpenCV(无需OpenCVManager)
- spark视频-第二期:Shark、SparkSQL
- Java中HashMap常见问题 -- 扩容、树化、死链问题
- 工业虚拟现实解决方案
- 【Unity打包崩溃】安卓包遇到CrashReport-Native: Faile to open comm file(/system/build.prop)就闪退
- 五一后“实在高校行”紧锣密鼓走进四所高校,校企合作硕果累累!
热门文章
- 订餐网的商业模式之Opentable
- Learning-Pixel-level-Semantic-Affinity-with-Image-level-Supervision
- Ubuntu安装redis详细教程
- SpringBoot接收前端传来的json数据
- Cordova + vue 打包安卓(Android) apk
- Python遗传和进化算法框架(二)Geatpy库函数和数据结构
- 论文剽窃者“自爆家门”?CVPR 最后一天上演“一出好戏 ”!
- NBA 本周五将空场复赛,球迷可用微软 Teams 现场互动
- lexical or preprocessor issue
- 二次优化问题dfp_最优化之DFP算法考试题