muduo库的模拟实现——muduo库的介绍

文章目录

  • 一、muduo库介绍
  • 二、背景知识
    • 1.epoll
    • 2.Reactor模式
  • 三、功能模块划分
    • 1.工具部分
    • 2.Reactor部分
    • 3.TCPServer部分

一、muduo库介绍

muduo库是在Linux环境下使用C++实现的一个多Reactor多线程的高性能网络服务器,作者陈硕,他还出了一本书《Linux多线程服务端编程:使用muduo C++网络库》来介绍muduo库的使用以及设计。有兴趣的读者可以阅读一下书中关于muduo库的设计部分,本篇文章就是基于这本书来介绍如何模拟实现一个muduo网络库。因为我认为学习技术,有了初步的了解以后就要想办法复刻模仿,学习别人的设计思路,复刻的过程就是动手实践,只有在实践中我们才能真正感受到难点在哪,以及优秀的地方在哪,这对我们学习相关知识比如计算机网络、IO多路转接、Reactor模式是很有帮助的。

比如在模拟实现muduo库的过程中,我最大的感受就是这个库很难,至少对于我这么一个刚学习完计算机网络、多路转接的学生来说是有挑战的,代码中使用了大量的回调函数,以及多线程并发执行的逻辑,我刚开始看的时候是很绕很乱的,理不清这些关系。但模拟实现完成以后,现在再回头看这些代码,确实是非常优秀的设计。本篇文章的目的是记录这几个月学习并模拟实现muduo库的过程,分享一下我个人对muduo库的认识,如果有不正确的地方欢迎指正。

二、背景知识

muduo库是基于Reactor模式下使用epoll多路转接的方式设计出来的,所以在模拟实现moduo库之前,非常有必要补充一下这方面的背景知识。

1.epoll

epoll是多路转接中使用最多并且是最高效的方式,多路转接又叫多路复用,其实我认为多路复用比较形象好理解,多路复用是用来解决一个服务器如何更好地服务多个用户的问题。试想一下,如果我们写的服务器只能服务一个用户,那么效率太低了,而且也很浪费资源。

那如果要处理多个客户端的请求,首先想到的是多进程模型,也就是每来一个用户,服务器就创建一个新进程来为用户服务,但这种方式有明显的劣势,因为进程的开销太大了。如果进程不行的话,就有多线程模型,每来一个用户就创建一个线程,或者使用线程池的方式,可以避免频繁创建和销毁线程。但多线程也只能解决用户少的情况,如果用户量很大,连接数很多,一个服务器要维护成千上万个线程,其实开销也是特别大的,即使线程比进程更轻量级。

IO多路复用就和多进程模型、多线程模型不一样,因为一个服务就分配一个线程或者进程开销太大,所以IO多路复用是让多个事件复用同一个进程。用户与服务器的交互无非就是建立连接请求、发送IO请求,这些请求在服务器层面看来,本质都是一个个的事件,建立连接事件以及IO处理事件,IO多路复用是让一个进程管理多个事件,其实更进一步说明应该是,进程调用操作系统提供的多路复用的接口,比如select、poll和epoll,通过这些接口进程将需要管理的事件交给操作系统内核去监听,一旦有事件就绪,操作系统内核会以不同的方式通知进程,进程再去做相应的处理,这就是IO多路复用的原理。

epoll就是操作系统为我们提供的IO多路复用接口,在使用epoll多路复用时,操作系统会为我们创建一个epoll模型,这个模型由三部分组成:红黑树、就绪队列以及回调机制。epoll的底层实现原理是当进程向epoll模型输入需要管理的事件时,epoll模型会创建一个相应的红黑树节点,将该事件记录在红黑树上。然后操作系统会为红黑树上的每一个事件注册一个回调函数,当事件就绪时,红黑树上的事件节点会被删除,然后向就绪队列中插入一个新的节点,通过回调函数告诉上层的进程某某事件已经就绪了,就绪事件就放在就绪队列里,上层进程就可以对就绪事件进行相应的进一步处理了。这就是epoll模型的基本原理。

在这里插入图片描述

这里只是作为背景知识简单地介绍了一下epoll模型,详细的可以看我之前写过的博客:Linux多路转接之epoll

2.Reactor模式

Reactor模式是对多路复用的进一步设计,如果单纯使用epoll的多路复用,进程调用epoll接口监听事件,如果有事件就绪还是由这个进程来执行就绪事件的对应操作。也就是说单纯的多路复用是将事件监听和就绪事件的处理合在一起的,而Reactor模式就是将它们分开来处理。Reactor模式又叫做dispatcher模式,dispatcher有分派的意思,其实是比较形象地形容了Reactor模式的,因为Reactor模式中一般可以分出两类角色,一类是Reactor角色,一类是handler角色,其中Reactor角色负责的是监听和分发事件,handler角色负责的是处理就绪事件。Reactor角色会等待多路复用返回就绪事件,一旦被通知有事件就绪,Reactor角色就会把就绪的事件交给handler角色去处理,然后Reactor角色就可以继续等待下一轮的就绪事件,这就是Reactor模式的原理。

Reactor的模式是灵活多变的,在不同的业务场景下,我们可以选择单个或者多个reactor角色,同时也可以选择单个或多个handler角色。其实handler角色通常情况下就是额外的进程和线程,因为Reactor角色接收到就绪事件以后肯定是分派给其它执行进程或者线程去处理就绪事件。所以Reactor模式又可以分为单Reactor单进程/线程单Reactor多进程/线程多Reactor单进程/线程多Reactor多进程/线程4种方案。

由于muduo库使用的就是多Reactor多线程的方案,所以这里只介绍这种方案,其实这种方案听起来很复杂,但我个人认为是最好理解、最好实现的方案,因为它能做到分工明确。首先来看一下多Reactor多线程的模型图:

在这里插入图片描述

通过模型图可以看到,多Reactor多线程模型分了一个主Reactor和多个从属Reactor,当然也有一个主线程和多个子线程。它的执行逻辑是,主线程的主Reactor通过epoll监听连接事件,并且主Reactor只监听连接事件,当连接事件到来的时候,主线程获取连接,获取到的连接又是一个新的文件描述符,也就是一个新的事件,这个事件可能还会有新的IO事件,所以也必须被管理起来。这些新连接就被主Reactor分派给某个子线程的从属Reactor去管理。同样的,从属Reactor就负责监听这些连接事件的IO事件,当这些连接有IO请求的时候,就让Handler去处理这些请求。这就是多Reactor多线程模型的执行逻辑。

多Reactor多线程模型在muduo库中的体现是,首先会有一个主Reactor,它只负责监听连接事件,然后会维护一个线程池,线程池里的线程可以指定数量,当主Reactor监听到连接事件以后,就选定线程池里的一个线程,将该事件的文件描述符与子线程的从属Reactor绑定,子线程的从属Reactor就负责监听该文件描述符的IO事件,而主Reactor就继续去监听等待新的连接事件到来。这样分工是非常明确的,也是非常好理解。

三、功能模块划分

介绍完两个重点的背景知识以后,我们可以对自己模拟实现的muduo库做一个功能划分,这里首先划分TCP服务器层的功能模块,因为这才是整个网络库的核心,所有的多Reactor多线程模型,所有的高性能的实现都是在TCP服务器里面体现的,这一层做好了,上层应用层选择需要的协议就可以了,比如HTTP协议,搭建一个HTTP协议并不是什么难点,所以放在最后再来说,重点还是放在TCP服务器上。

TCP服务器的功能模块我个人认为可以划分出三个部分,分别是工具部分、Reactor部分和最上层的TCPServer部分。工具部分有Buffer模块、Socket模块、Acceptor模块、定时器模块、线程池模块。之所以这些模块被我划分为工具部分,是因为我认为在这个项目中这些模块更多的是起到一个工具的辅助作用,最重点的还是Reactor部分。Reactor部分有Channel模块、Poller模块、Connection模块和EventLoop模块,这部分的四个模块,就是实现多Reactor多线程、完成连接管理的模块,我认为是项目的核心模块。最上层的TcpServer模块当然就是将这些各个模块整合起来,形成一个类或者接口,提供给外部调用。下面将分别介绍每个模块的大致功能,后面会详细介绍每一个模块的原理、作用以及具体实现。

1.工具部分

Buffer模块:
Buffer模块是TCP服务器的缓冲区模块,缓冲区这个概念在计算机里是非常常见的,我们平时使用的软件比如操作系统、数据库,以及一些第三方库、组件等等,都有缓冲区的存在。缓冲区就是一段缓存数据的内存空间,在我们这个TCP服务器中,我们需要接收缓冲区来保存对方发送的请求报文,也需要发送缓冲区保存我们需要发送给对方的响应报文。所以Buffer模块就是为我们的TCP服务器维护了一段缓冲区。

Socket模块:
这个模块比较简单,就是对TCP服务器中要使用到的套接字接口进行封装,因为要符合面向对象的思想,让开发调用更方便,也为了让代码更美观和结构化,所以很有必要将socket套接字操作封装成一个单独的模块。

Acceptor模块:
Acceptor模块会跟主Reactor有比较大的联系,因为Acceptor模块是负责监听事件管理的模块,它通过Socket模块实现监听套接字的操作,创建了监听套接字以后,它就会accept监听获取连接,当有连接到来的时候,它就会通知主Reactor去处理连接。

定时器模块:
我们知道网络连接有长连接和短连接,我们的服务器也是可以接收长连接和短连接的,但是长连接不能占用套接字的时间太长,如果一个连接到来但一直没有与服务器有其它IO数据交互,就只是占用着连接不销毁,这其实是非常消耗服务器资源的,虽然一个连接长时间占用资源看起来影响不大,但如果有成千上万个连接到来,服务器的资源是有限的,就比如文件描述符的资源是有限的,如果有长连接长时间不通信还占用着文件描述符,那么如果文件描述符都被占满了,新的连接就无法到来了,所以我们必须设置一个定时器机制,虽然我支持你的长连接,但你不能占用我的连接资源那么长时间。既然你自己不关闭连接,我就设置一个定时功能,多长时间你还没跟我服务器通信我就直接关闭你这个连接,下次要通信你再重新发起连接吧。

这就是定时器模块的功能,它会记录每一个连接距离上一次发送数据给服务器的时间,然后设置一个超时时间,如果这个时间超过了超时时间,就自动销毁该连接,这就是muduo库的超时自动销毁连接机制。

线程池模块:
这个模块就是维护一个线程池,因为我们实现的muduo库是多Reactor多线程模型的,所以需要一个线程池来管理多个子线程,主Reactor接收到连接事件之后,就从线程池中选一个线程,让它去管理新连接后续的IO事件。

2.Reactor部分

Channel模块:
Channel模块是用来管理监控事件的,其实我们服务器要监控的事件就是这四类:可读事件、可写事件、错误事件、连接关闭事件,所以Channel模块要对这些监控的事件进行管理,比如你要设置监听的事件是可读事件还是可写事件,还要设置每一类事件触发以后的回调函数,这样事件触发以后才能够调用对应的回调函数去处理。

Poller模块:
Poller模块是对epoll函数操作的封装,但这个不是简单的封装,事实上Poller模块和Channel模块是关联起来的,Poller模块需要管理所有epoll模型要监控的文件描述符,这些文件描述符其实就是一个个的事件,所以每个文件描述符都对应一个Channel对象,Poller模块监控这些文件描述符,一旦有事件就绪,就调用这些文件描述符的Channel对象函数,去执行相应的回调函数,因为Channel模块是设置了每一种事件的回调函数的。

EventLoop模块:
EventLoop模块就是实现Reactor的模块了,它是对Channel模块和Poller模块的整体封装,在服务器中每创建一个EvevtLoop对象,其实就是创建一个Reactor,但是EventLoop也不是简单封装Reactor操作,事实上EventLoop模块是特别核心的一个模块,因为它关联了很多其他模块,比如Poller模块、Channel模块、Connection模块等,所以这个模块简述不清,后面会详细介绍。

Connection模块:
Connection模块是管理连接的模块,主Reactor监听到新连接到来后,就会创建一个Connection对象,用这个对象来管理这个新来的连接。这些管理包括读取数据、发送数据、启动非活跃连接超时销毁任务、释放连接、关闭连接等操作。另外,Connection模块还有一个很重要的成员就是EventLoop对象,因为主Reactor监听到新连接到来,创建Connection对象以后,需要把这个连接交给从属Reactor去监听事件,所以这个EventLoop对象就是从属Reactor。

3.TCPServer部分

TCPServer模块:
这个模块就是整个TCP服务器最上层的模块,它实现了对另外两个部分所有模块的封装,然后提供接口给外界,外界只需要调用相应的接口就能启动这个TCP服务器。

由于篇幅有限,我也不希望全部挤在一篇文章里来写,所以我分开几个文章来记录这个项目,这篇文章只是初步对muduo库做一个介绍,简单了解muduo库的背景知识以及基本的结构,接下来将针对每个模块详细地介绍项目的设计和实现过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/648879.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于差分进化算法(Differential Evolution Algorithm,DE)的移动边缘计算的任务卸载与资源调度研究(提供MATLAB代码)

一、优化模型介绍 移动边缘计算的任务卸载与资源调度是指在移动设备和边缘服务器之间,将部分计算任务从移动设备卸载到边缘服务器,并合理分配资源以提高系统性能和降低能耗。 在本文所研究的区块链网络中,优化的变量为:挖矿决策&…

热门应用滥用苹果 iPhone 推送通知,暗中窃取用户数据

移动研究人员 Tommy Mysk 近日揭露,部分热门应用利用 iPhone 推送通知功能秘密发送用户数据,这引发了用户隐私安全担忧。 许多 iOS 应用程序正在使用由推送通知触发的后台进程来收集设备的用户数据,从而有可能创建用于跟踪的指纹档案。 Mys…

Azure AI - 沉浸式阅读器,阅读障碍用户福音

目录 一、什么是沉浸式阅读器将内容划分开来提高可读性显示常用字词的图片突出显示语音的各个部分朗读内容实时翻译内容将单词拆分为音节 二、沉浸式阅读器如何工作?环境准备创建 Web 应用项目设置身份验证配置身份验证值安装标识客户端 NuGet 包更新控制器以获取令…

《ORANGE’S:一个操作系统的实现》读书笔记(三十八)尾声(三)

这篇文章是尾声的第三部分,也是《ORANGE’S:一个操作系统的实现》读书笔记的最后一篇文章,本篇文章记录如何将我们开发的OS安装到真实的计算机(建议在虚拟机中进行)。 将OS安装到真实的计算机 其实安装到真实的硬盘和…

JS slice() 方法总结

在JavaScript中,有一种数组方法叫做slice(),它基于给定的起始和结束位置,创建一个新的数组副本。该方法能够将数组的一部分切成另一个数组。 语法 array.slice(start, end) start: 可选参数,表示切片起始位置的索引。如果没有指…

《Linux C编程实战》笔记:信号的屏蔽

在《Linux C编程实战》笔记:信号的捕捉和处理-CSDN博客的sigaction的sa_mask成员,它的类型就是一个信号集,下面我们来介绍它 信号集 信号的总数目达64个,所以不能用一个整数表示它们的集合,int类型通常是4字节32位&a…

Linux | makefile简单教程 | Makefile的工作原理

前言 在学习完了Linux的基本操作之后,我们知道在linux中编写代码,编译代码都是要手动gcc命令,来执行这串代码的。 但是我们难道在以后运行代码的时候,难道都要自己敲gcc命令嘛?这是不是有点太烦了? 在vs中…

力扣646. 最长数对链

动态规划 思路: 思路与 力扣354. 俄罗斯套娃信封问题 类似将序列进行排序,然后假设 dp[i] 为第 i 个元素的最长数对链个数;则其状态转移方程: 第 i 个元素之前的某一个元素(假设是下标是 j),如…

KVM部署Alibaba Cloud Linux操作系统

下载镜像文件 下载链接:https://mirrors.aliyun.com/alinux/image/?spma2c4g.11186623.0.0.79ed5af6pehv54 下载文件:aliyun_3_x64_20G_nocloud_alibase_20230727.qcow2 部署KVM虚拟化环境 yum -y install qemu libvirt rr-testsuite systemctl star…

[SUCTF 2019]CheckIn1

黑名单过滤后缀’ph&#xff0c;并且白名单image类型要有对应文件头 对<?过滤&#xff0c;改用GIF89a<script languagephp>eval($_POST[cmd]);</script>&#xff0c;成功把getshell.gif上传上去了 尝试用.htaccess将上传的gif当作php解析&#xff0c;但是失败…

美工前端和数据对接一起做的可视化大屏开发项目工期一周足矣

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 可视化大屏已经成为企业和组织中不可或缺的一部分。它不仅可以帮助企业更好地展示业务数…

[docker] Docker的私有仓库部署——Harbor

一、Docker原生私有仓库—— Registry 1.1 Registry的简单了解 关于Docker的仓库分为私有库和公有仓库&#xff0c;共有仓库只要在官方注册用户&#xff0c;登录即可使用。但对于仓库的使用&#xff0c;企业还是会有自己的专属镜像&#xff0c;所以私有库的搭建也是很有必要的…

jQuery实现选择方法和保护信息方法

最近呢&#xff01;一直在学习jQuery语法&#xff0c;也没时间发布文章&#xff0c;现在学的差不多了&#xff0c;先跟大家分享下学习感受吧&#xff01;JavaScript学过后&#xff0c;再学习jQuery语法&#xff0c;应该是简单的&#xff0c;但我总是容易把它们搞混&#xff0c;…

开源模型部署及使用

开源模型部署及使用 1.Langchain-Chatchat1.环境2.运行3.效果 2.facefusion1.环境2.运行3.效果 3.Aquila1.环境2.运行 1.Langchain-Chatchat Langchain-Chatchat这里面可以调用许多模型&#xff0c;我本地下载了chatglm3模型文件&#xff0c;所以就用这个模型。 1.环境 根据…

牛客网---------[USACO 2016 Jan S]Angry Cows

题目描述 Bessie the cow has designed what she thinks will be the next big hit video game: "Angry Cows". The premise, which she believes is completely original, is that the player shoots cows with a slingshot into a one-dimensional scene consistin…

实现负载均衡

1.安装依赖 sudo apt insta11 libgd-dev 2.下载nginx wget http://nginx.org/download/nginx-1.22.1.tar.gz 3.解压nginx tar -zvxf nginx-1.22.1.tar.g2 4.编译安装 cd nginx-1.22.1 5.编译并指定安装位置&#xff0c;执行安装之后会创建指定文件夹/www/env/nginx ./configure…

【大数据】流处理基础概念(二):时间语义(处理时间、事件时间、水位线)

流处理基础概念&#xff08;一&#xff09;&#xff1a;Dataflow 编程基础、并行流处理流处理基础概念&#xff08;二&#xff09;&#xff1a;时间语义&#xff08;处理时间、事件时间、水位线&#xff09;流处理基础概念&#xff08;三&#xff09;&#xff1a;状态和一致性模…

使用pysimplegui+opencv编写一个摄像头的播放器

需求 使用pysimplegui和opencv实现一个播放器&#xff0c;播放 摄像头的画面。 代码实现 import cv2 import time from typing import Iterable, NamedTuple, Optionalimport PySimpleGUI as sgclass CameraSpec(NamedTuple):name: strindex: intwidth: intheight: intfps: i…

c# ADODB.Recordset实例调用Fields报错

代码&#xff1a; using System; using System.CodeDom; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using ADODB;namespace ConsoleApp1 {internal class Programre{static ADODB.Recordset recordsetInstance…

代码随想录刷题笔记 DAY15 | 翻转二叉树 No.226 | 对称二叉树 No.101

Day 15 01. 翻转二叉树&#xff08;No. 226&#xff09; 题目链接 代码随想录题解 1.1 题目 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9…