websevere服务器从零搭建到上线(四)|muduo网络库的基本原理和使用

文章目录

  • muduo源码编译安装
  • muduo框架讲解
  • muduo库编写服务器代码示例
    • 代码解析
      • 用户连接的创建和断开回调函数
      • 用户读写事件回调
    • 使用vscode编译程序
      • 配置c_cpp_properties.json
      • 配置tasks.json
      • 配置launch.json
      • 编译
  • 总结

muduo源码编译安装

muduo依赖Boost库,所以我们应该先编译安装boost,方法如下:
https://blog.csdn.net/QIANGWEIYUAN/article/details/88792874
随后可以编译安装muduo,方法如下:
https://blog.csdn.net/QIANGWEIYUAN/article/details/89023980

muduo的底层其实就是采用nonblock io + one loop per thread 以及 epoll + 线程池的框架,采用sub-Reactor模式。
该方案的特点就是one loop per thread,有一个main reactor负载accept连接,然后把连接分发到某个sub reactor(采用round-robin的方式选择sub reactor),该链接的所有操作都在那个sub reactor所处的线程中完成,多个链接可能被分派到多个线程中,以充分利用CPU。
Reactor poll的大小是固定的,根据CPU的核心数目确定

//设置EventLoop的线程个数,底层通过EventLoopThreadPool线程池管理线程类EventLoopThread
_server.setThreadNum(10);

一个Base IO thread负责accept新的连接,接收到新的连接以后,采用轮询的方式在reactor pool中找到合适的sub reactor将这个链接挂载上去,这个链接上的所有任务都在这个sub reactor上完成。
如果有过多的CPU I/O的计算任务,可以提交到创建的ThreadPool线程池中专门处理耗时的计算任务

muduo框架讲解

在这里插入图片描述
无论是什么语言,想要做到高并发,
首先要有一个IO线程,它里面有一个epoll,我们叫做main Reactor,它的作用就是建立新用户的连接,新用户的连接上之后,会把这些连接通过一定的负载算法分发给不同的工作线程。

这些工作线程就是用来处理已连接用户的读写事件,一般这些线程的数量会和CPU的核数对等。尽量做到高并发。
用IO复用的好处就是一个线程可以监听多个套接字,尤其是对于连接量大而活跃量少(一般都是这样的场景),所以epoll有非常大的性能优势。
如果工作线程的每一个epoll所监听的连接用户要做比较耗时的IO操作(传送文件、音视频),我们可以在工作线程内单独开一个线程。如果我们不单独开线程的话,工作线程会阻塞在IO操作中,无法监听其他依然注册在epoll树上的fd的读写时间

muduo源代码有很多非常好的封装思想,里面大量得使用了智能指针、绑定器、函数对象、回调机制等等,可以做到模块化的解耦和软件的高内聚低耦合

muduo库编写服务器代码示例

muoduo库的使用需要链接 libmuduo_base.so libmuduo_net.so libpthread.so
动态库在linux系统默认的路径是
/usr/lib
/usr/local/lib
如果动态库放在这个路径下不需要在cmake中添加搜索路径,因为他们已经处于环境变量中。

//由于依赖关系的存在,net依赖于base,base依赖于pthread,连接顺序不能改变
-lmuduo_net -lmuduo_base -lpthread

使用实例代码如下:

/*
muduo网络库给用户提供了两个主要的类
TcpServer:用于编写服务器程序的
TcpClient:用于编写客户端程序的epoll + 线程池
好处:能够把网络IO的代码和业务代码区分开,muduo库已经把网络端的代码封装好了。
业务代码暴露在两个,我们也只关心这两件事: 用户的连接和断开  用户的可读写事件
*/#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <iostream>
#include <functional>
#include <string.h>
using namespace std;
using namespace muduo;
using namespace muduo::net;
using namespace placeholders;/*基于muduo网络库开发服务器程序
1.组合TcpServer对象
2.创建EventLoop时间循环对象的指针
3.明确TcpServer的构造函数需要什么参数,输出ChatServer的构造函数
4.在当前服务器类的构造函数中,注册处理连接的回调函数和处理读写事件的回调函数
5.设置合适的服务端线程数量,muduo库会自己划分I/O线程和worker线程
*/ 
class ChatServer {
public:ChatServer(EventLoop* loop, //事件循环const InetAddress& listenAddr, //IP+Portconst string& nameArg)  //服务器名字:_server(loop, listenAddr, nameArg), _loop(loop) {//给服务器注册用户连接的创建和断开回调_server.setConnectionCallback(std::bind(&ChatServer::onConnection, this, _1));//给服务器注册用户读写事件回调_server.setMessageCallback(std::bind(&ChatServer::onMessage, this, _1, _2, _3));//设置服务器端的服务线程  设置为2,1个IO线程,1个worker线程_server.setThreadNum(2);    }// 开启事件循环void start() {_server.start();}
private://专门处理用户的链接创建和断开void onConnection(const TcpConnecitonPtr &conn) {if (conn->connected()) {cout << conn->peerAddress().toIpPort() << " -> " <<<< conn->localAddress().toIpPort() << "state:online" <<endl;} else {cout << conn->peerAddress().toIpPort() << " -> " <<<< conn->localAddress().toIpPort() << "state:offline" <<endl;conn->shutdown(); //连接断开后,回收fd资源close(fd)//_loop->quit(); //通知事件循环停止运行,这通常是在准备关闭服务器或者结束程序时执行的操作}}//专门处理用户的读写事件void onMessage(const TcpConnection &conn, //这是我们的连接,可以读数据也可以写数据 Buffer *buf, // 用户的缓冲区Timestamp time) {    //接受数据的时间信息string buf = buffer->retrieveAllAsString(); //可以把其中接收到的数据全部接收到自己的字符串当中cout << "recv data: " << buf << " time: " << time.toString() << endl;conn->send(buf);}TcpServer _server; //#1EventLoop *_loop;  //#2 把它看作epoll}int main () {EventLoop loop; //epollInetAddress addr("127.0.0.1", 10000);ChatServer server(&loop, addr, "ChatServer");server.start(); //listenfd epollctl=>epollloop.loop;      //epoll_wait以阻塞方式等待新用户连接,已连接用户的读写事件等return 0;
}

代码解析

以上代码就已经实现了一个高性能高并发的服务器程序,muduo库已经为我们把网络端封装好了,我们只需要进行应用层业务逻辑的代码编写。

在例子中,其实我们只需要关注两个函数的编写:

void onConnection(const TcpConnecitonPtr &conn);
void onMessage(const TcpConnection &conn, //这是我们的连接,可以读数据也可以写数据 Buffer *buf, // 用户的缓冲区Timestamp time) //接受数据的时间信息

也就是用户连接的创建和断开回调函数、用户读写事件回调。

用户连接的创建和断开回调函数

//专门处理用户的链接创建和断开
void onConnection(const TcpConnecitonPtr &conn) {if (conn->connected()) {cout << conn->peerAddress().toIpPort() << " -> " <<<< conn->localAddress().toIpPort() << "state:online" <<endl;} else {cout << conn->peerAddress().toIpPort() << " -> " <<<< conn->localAddress().toIpPort() << "state:offline" <<endl;conn->shutdown(); //连接断开后,回收fd资源close(fd)//_loop->quit(); //通知事件循环停止运行,这通常是在准备关闭服务器或者结束程序时执行的操作}}

本案例中,我们在连接后打印远端的相关信息,断开后打印远端相关信息(这里就是典型的业务层逻辑)。

用户读写事件回调

这里写的是接受读缓冲区(读缓冲区的数据是客户端写入的)的数据,并且打印到终端。

//专门处理用户的读写事件
void onMessage(const TcpConnection &conn, //这是我们的连接,可以读数据也可以写数据 Buffer *buf, // 用户的缓冲区Timestamp time) {    //接受数据的时间信息string buf = buffer->retrieveAllAsString(); //可以把其中接收到的数据全部接收到自己的字符串当中cout << "recv data: " << buf << " time: " << time.toString() << endl;conn->send(buf);}

如果是浏览器的话,在这里我们在读写事件回调中去解析浏览器的http消息,然后根据http的请求,组织http响应在发送回给浏览器(这就是所谓的业务代码)。

使用vscode编译程序

配置c_cpp_properties.json

按F1,这里可以把配置的对话框打印出来

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**$"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "c11", "intelliSenseMode": "clang-x64"}],"version": 4
}

这里的配置文件中,若果我们去执行一条编译命令,需要写

//gcc -I头文件搜索路径 -L库文件搜索路径 -lmuduo_net库名称

一般我们可以在"includePath": []中加头文件、库文件的搜索路径(/usr/include /usr/local/include默认包含),库的名称在配置tasks.json;
还可以添加C++标准"cppStandard": "c++17"

配置tasks.json

ctrl+shift+B(build)可以看到构建项目的配置文件,点击齿轮即可
在这里插入图片描述
在这里有一个tasks.json配置文件。

	"version": "2.0.0","tasks": [{"type": "shell","label": "g++ build active file","command": "user/bin/g++","arg": ["-g","${file}","-o","${fileDirname}/${fileBasenameNoExtension}",]"options": {"cwd": "/bsr/bin"},"problemMatcher": ["$gcc"],"group": "build" }]

我们的-lmuduo_net库名称就是写在tasks.json文件中的"arg"

"arg": ["-g","${file}","-o","${fileDirname}/${fileBasenameNoExtension}","-lmuduo_net","-lmuduo_base","-lpthread"
]	

配置launch.json

只有调试的时候采用,我们最好使用gdb调试

编译

配置好c_cpp_properties.json和配置tasks.json后。
ctrl+shift+B(build)然后直接点击蓝色的地方而不是点击齿轮,就可以进行编译啦。
在这里插入图片描述

总结

这样,我们就很简单得写出了一个健壮的、基于事件驱动的、IO复用的高并发高性能服务器。

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

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

相关文章

webpack与vite

webpack 使用步骤&#xff1a; 初始化项目 pnpm init -y安装依赖webpack、webpack-cli在项目中创建src目录&#xff0c;然后编写代码&#xff08;index.js&#xff09;执行pnpm weboack来对代码进行打包&#xff08;打包后观察dist文件夹&#xff09; 配置古文件&#xff08;w…

使用ThemeRoller快速实现前端页面风格美化

使用ThemeRoller快速实现前端页面风格美化 文章目录 使用ThemeRoller快速实现前端页面风格美化一、ThemeRoller二、使用方法1.基本操作面板介绍2.直接用现成的配色风格——Gallery画廊3.自定义风格——Roll Your Own4.下载风格包并应用到页面 一、ThemeRoller ThemeRoller是jQ…

基于java的CRM客户关系管理系统的设计与实现(论文 + 源码 )

【免费】基于Java的CRM客户关系管理系统的设计和实现.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89273409 基于Java的CRM客户关系管理系统的设计与实现 摘 要 随着互联网的高速发展&#xff0c;市场经济的信息化&#xff0c;让企业之间的竞争变得&#xff0…

纯血鸿蒙APP实战开发——页面间共享组件实例的案例

介绍 本示例提供组件实例在页面间共享的解决方案&#xff1a;通过Stack容器&#xff0c;下层放地图组件&#xff0c;上层放Navigation组件来管理页面&#xff0c;页面可以共享下层的地图组件&#xff0c;页面中需要显示地图的区域设置为透明&#xff0c;并参考触摸交互控制&am…

各城市-人口就业和工资数据(1978-2022年)

这份数据收集了1978年至2022年间300多个地级市的人口、就业和工资等数据。涵盖的指标包括从业人员数量、平均工资水平、人口密度等&#xff0c;通过这些数据可以深入了解中国各地城市的人口结构、就业状况以及工资水平的变化趋势。这些数据对于研究城市发展、劳动力市场以及区域…

论文架构介绍

论文架构 背景&#xff1a;建议2段左右完成&#xff0c;字数控制在500左右为佳&#xff0c;对应子题目1过渡段&#xff1a;写150字左右的过渡段&#xff0c;承上启下&#xff0c;回答部分子题目2、3的要求正文实践部分&#xff1a;一般3-7个论点&#xff0c;根据题目的要求来看…

C++构造函数和析构函数的调用顺序

一般情况下&#xff0c;调用析构函数的次序正好与调用构造函数的次序相反&#xff0c;也就是最先被调用的构造函数&#xff0c;其对应的析构函数最后被调用&#xff0c;而最后被调用的构造函数&#xff0c;其对应的析构函数最先被调用。 当然对象的构造函数和析构函数调用时机和…

力扣100284. 有效单词(C++)

【题解】 (实际在力扣中运行的代码只需要把下方的check函数放到力扣作答区给的模板中就可以) #include <bits/stdc.h> #include <iostream> #include <vector> #include <string> #include <cctype> #include <cstring> #include <st…

融知财经:期货交易的规则和操作方法

期货交易是指在未来的某一特定时期&#xff0c;买卖双方通过签订合约的方式&#xff0c;约定以某种价格买卖一定数量的某种商品或资产的行为。期货交易的规则和操作方法如下&#xff1a; 期货交易的规则和操作方法 1、双向交易&#xff1a; 期货市场允许投资者进行多头&#xf…

Python ArcPy批量将大量栅格文件的投影坐标系转为地理坐标系

本文介绍基于Python语言中的ArcPy模块&#xff0c;批量将多个遥感影像由投影坐标系转为地理坐标系的方法。 在之前的文章中&#xff0c;我们介绍过将单独1景遥感影像的投影坐标系转为地理坐标系的方法&#xff0c;大家可以参考文章投影坐标系转为地理坐标系&#xff1a;GDAL命令…

笔记86:关于【#ifndef + #define + #endif】的用法

当你在编写一个头文件&#xff08;例如 pid_controller.h&#xff09;时&#xff0c;你可能会在多个源文件中包含它&#xff0c;以便在这些源文件中使用该头文件定义的函数、类或其他声明。如果你在多个源文件中都包含了同一个头文件&#xff0c;那么当你将整个工程统一编译&am…

第六节课《Lagent AgentLego 智能体应用搭建》

PDF链接&#xff1a;https://pan.baidu.com/s/1JFtvBWgEGFWJq8pHafvIUg?pwd6666 提取码&#xff1a;6666 Lagent & AgentLego 智能体应用搭建_哔哩哔哩_bilibili https://github.com/InternLM/Tutorial/blob/camp2/agent/README.md InternStudio 一、为什么需要agent…

基于JSP的酒店客房管理系统(三)

目录 第四章 系统各模块的实现 4.1客房管理系统首页的实现 4.1.1 客房管理系统首页概述 4.2客房管理系统前台的实现 4.2.1 客房管理系统前台概述 4.2.2 客房管理系统前台实现过程 4.2.3 预定客房信息及客房信息的查询 4.3客房管理系统后台的实现 4.3.1 客房管理系统后…

微搭低代码入门05文件的上传和下载

目录 1 创建数据源2 创建应用3 创建页面4 设置导航功能5 文件上传6 文件下载总结 小程序中&#xff0c;我们通常会有文件的上传和下载的需&#xff0c;在微搭中&#xff0c;文件是存放在云存储中&#xff0c;每一个文件都会有一个唯一的fileid&#xff0c;我们本篇就介绍如何通…

Unity类银河恶魔城学习记录 17-1,2 p166 Aliments fx p167 Blackhole additional vfx

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Entity.cs using System.Collections; using System.Collections.Generic; …

Vue3(管理系统)-封装axios(utils)

一、在utils下编写request.js实例 1.添加基地址&#xff0c;设置超时时间 import axios from axios const baseURL http://big-event-vue-api-t.itheima.net const instance axios.create({// TODO 1. 基础地址&#xff0c;超时时间baseURL,timeout: 3000 }) 2.添加请求拦截…

[C++][数据结构]红黑树的介绍和模拟实现

前言 之前我们简单学习了一下搜索树和平衡搜索树&#xff0c;今天我们来学习一下红黑树 简介 概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着…

面试官:关于HTTPS/HTTP2/HTTP3你懂多少?

公众号&#xff1a;程序员白特&#xff0c;欢迎一起交流学习~ HTTPS是什么 HTTP为什么不安全&#xff1f; https被认为是通信安全的http&#xff0c;除了http多了s和默认端口改成了443之外&#xff0c;其他都是沿用的http&#xff08;除了明文和不安全&#xff09;&#xff0…

基于FPGA的数字信号处理(9)--定点数据的两种溢出处理模式:饱和(Saturate)和绕回(Wrap)

1、前言 在逻辑设计中&#xff0c;为了保证运算结果的正确性&#xff0c;常常需要对结果的位宽进行扩展。比如2个3bits的无符号数相加&#xff0c;只有将结果设定为4bits&#xff0c;才能保证结果一定是正确的。不然&#xff0c;某些情况如77 14(1110)&#xff0c;如果结果只…

SOLIDWORKS Electrical由3D布局生成2D机柜布局图

想要转换3D装配体到Electrical中需要在3D打开Electrical插件&#xff0c;并使用工程管理器打开需转换工程图的装配体 1、创建2D图纸 打开后在上方工具选项卡中选择-SOLIDWORKS Electrical选项卡-的创建2D图纸 2、选择图纸视角 使用创建2D图纸后就会进入工程图界面&#xff0c;在…