RabbitMQ的学习和模拟实现|muduo库的介绍和使用

muduo库

项目仓库:https://github.com/ffengc/HareMQ

  • muduo库
    • muduo库是什么
    • 快速上手搭建服务端
    • 快速上手搭建客户端
    • 上面搭建的服务端-客户端通信还有什么问题?
    • muduo库中的protobuf
    • 基于muduo库中的protobuf协议实现一个服务器

muduo库是什么

Muduo由陈硕大佬开发,是一个基于非阻塞10和事件驱动的C++高并发TCP网络编程库。 它是一款基于主从Reactor模型的网络库,其使用的线程模型是oneloop perthread,所谓one loop per thread指的是:

  • 一个线程只能有一个事件循环(EventLoop),用于响应计时器和IO事件。
  • 一个文件米哦啊舒服只能由一个线程进行读写,换句话说,就是一个TCP链接必须归属于某个EventLoop管理

我对reactor模式在项目中也是有详细的描述的,这里不再重复

  • Reactor模式: ffengc/Reactor-based-HyperWebServer
  • 事件驱动: ffengc/Event-Driven-Pipeline-Communication-System-Framework

快速上手搭建服务端

简单写一个英译汉服务器和客户端,快速上手Muduo库。

要先把东西准备好:第三方库如果没有像protobuf一样安装到系统目录下了,就统一放到HareMQ/libs里面去就行了。

muduo中我们需要的是build/release-install-cpp11/里面的includelib

这样包含就行了:

#ifndef __YUFC_DEMO_DICT_SERVER_USE_MUDUO__
#define __YUFC_DEMO_DICT_SERVER_USE_MUDUO__#include "../../libs/muduo/include/muduo/net/TcpServer.h"
#include "../../libs/muduo/include/muduo/net/EventLoop.h"
#include "../../libs/muduo/include/muduo/net/TcpConnection.h"#endif

直接写一个简单服务作为例子:

#ifndef __YUFC_DEMO_DICT_SERVER_USE_MUDUO__
#define __YUFC_DEMO_DICT_SERVER_USE_MUDUO__#include "../../libs/muduo/include/muduo/net/EventLoop.h"
#include "../../libs/muduo/include/muduo/net/TcpConnection.h"
#include "../../libs/muduo/include/muduo/net/TcpServer.h"
#include "../log.hpp"class translate_server {
private:muduo::net::EventLoop __base_loop; // 基本的事件循环(这个要传给server, 所以要放前面)muduo::net::TcpServer __server; // 服务器对象
private:// 新连接建立成功时的回调函数// 会在一个连接建立成功,以及关闭的时候被调用void onConnection(const muduo::net::TcpConnectionPtr& conn) {if (conn->connected() == true)LOG(INFO) << "new connection!" << std::endl;elseLOG(INFO) << "connection close" << std::endl;}// 通信连接收到请求时的回调函数void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) {std::string str = buf->retrieveAllAsString();std::string resp = translate(str);conn->send(resp); // 向客户端进行发送}std::string translate(const std::string& str) {// 用个简单例子就行static std::unordered_map<std::string, std::string> __dict_map = {{ "hello", "nihao" }, { "nihao", "hello" }};auto it = __dict_map.find(str);if (it == __dict_map.end())return "unknown";return it->second;}public:translate_server(int port): __server(&__base_loop,muduo::net::InetAddress("0.0.0.0", port),"translate_server",muduo::net::TcpServer::kReusePort) {__server.setConnectionCallback(std::bind(&translate_server::onConnection,this, std::placeholders::_1)); // 设置回调__server.setMessageCallback(std::bind(&translate_server::onMessage,this, std::placeholders::_1,std::placeholders::_2,std::placeholders::_3)); // 设置回调}void start() {__server.start(); // 开始事件监听__base_loop.loop(); // 开始事件监控,这是一个死循环阻塞接口}
};#endifint main() {translate_server server(8085);server.start();return 0;
}

这个例子很简单,不作过多描述,维护好事件loop对象和服务器对象即可。

makefile‼️

server: dict_server.ccg++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread
.PHONY:clean
clean:rm -f server

[!CAUTION]
这里要注意,用-L选项来指定库的地址。
Linux上静态库库的名称需要去掉头上的lib和后面的.a,具体为什么可以另外去了解linux库的机制
-I指定头文件路径‼️然后cc文件里面的头文件路径就可以相应修改了

服务器小例子完整代码

#ifndef __YUFC_DEMO_DICT_SERVER_USE_MUDUO__
#define __YUFC_DEMO_DICT_SERVER_USE_MUDUO__// #include "../../libs/muduo/include/muduo/net/EventLoop.h"
// #include "../../libs/muduo/include/muduo/net/TcpConnection.h"
// #include "../../libs/muduo/include/muduo/net/TcpServer.h"
#include "../log.hpp"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpConnection.h" // 因为makefile指定了头文件路径,所以这里直接这样用
#include "muduo/net/TcpServer.h"
#include <unordered_map>class translate_server {
private:muduo::net::EventLoop __base_loop; // 基本的事件循环(这个要传给server, 所以要放前面)muduo::net::TcpServer __server; // 服务器对象
private:// 新连接建立成功时的回调函数// 会在一个连接建立成功,以及关闭的时候被调用void onConnection(const muduo::net::TcpConnectionPtr& conn) {if (conn->connected() == true)LOG(INFO) << "new connection!" << std::endl;elseLOG(INFO) << "connection close" << std::endl;}// 通信连接收到请求时的回调函数void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) {std::string str = buf->retrieveAllAsString();LOG(INFO) << "recv a mesg: " << str << std::endl;std::string resp = translate(str);conn->send(resp); // 向客户端进行发送}std::string translate(const std::string& str) {// 用个简单例子就行static std::unordered_map<std::string, std::string> __dict_map = {{ "hello", "nihao" }, { "nihao", "hello" }};auto it = __dict_map.find(str); // 这里的str包含了\n,需要额外处理,不过这里只是为了学习使用服务器,不处理了if (it == __dict_map.end())return "unknown\n";return it->second;}public:translate_server(int port): __server(&__base_loop,muduo::net::InetAddress("0.0.0.0", port),"translate_server",muduo::net::TcpServer::kReusePort) {__server.setConnectionCallback(std::bind(&translate_server::onConnection,this, std::placeholders::_1)); // 设置回调__server.setMessageCallback(std::bind(&translate_server::onMessage,this, std::placeholders::_1,std::placeholders::_2,std::placeholders::_3)); // 设置回调}void start() {__server.start(); // 开始事件监听__base_loop.loop(); // 开始事件监控,这是一个死循环阻塞接口}
};#endifint main() {translate_server server(8085);server.start();return 0;
}

[!WARNING]
注意:因为我们对发送的字符串没有做处理,我们只是学习muduo的使用方法,所以发送hello其实是发送hello\n,所以服务器返回一直是unknown,这个我也不进行处理了,知道这个原因即可,我们的重点不在这。

快速上手搭建客户端

和服务端基本是一样的,但是需要注意:

[!CAUTION]
客户端不能完全非阻塞,客户端建立连接一定要成功才能继续往下走的
但是因为muduo库里面所以操作都是异步非阻塞的
因此需要使用CountDownLatch组件来维持同步‼️

客户端完整代码如下所示。

#ifndef __YUFC_DEMO_DICT_CLIENT_USE_MUDUO__
#define __YUFC_DEMO_DICT_CLIENT_USE_MUDUO__#include "../log.hpp"
#include "muduo/base/CountDownLatch.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/TcpConnection.h"/* 注意,客户端连接服务器是需要阻塞等待连接建立成功之后才返回的,所以才需要使用 CountDownLatch */class translate_client {
private:muduo::CountDownLatch __latch;muduo::net::EventLoopThread __loop_thread;muduo::net::TcpClient __client;muduo::net::TcpConnectionPtr __conn;private:// 连接成功的回调void onConnection(const muduo::net::TcpConnectionPtr& conn) {if (conn->connected()) {// 如果连接建立成功了,就计数器--__latch.countDown();LOG(INFO) << "connection to server success" << std::endl;__conn = conn; // 保存这个连接} else {// 连接关闭LOG(INFO) << "connection to server end" << std::endl;__conn.reset(); // 清空}}// 收到服务器发来的消息的回调void onMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buf, muduo::Timestamp) {std::cout << "server# " << buf->retrieveAllAsString() << std::endl;}public:translate_client(const std::string& sip, int sport): __latch(1), __client(__loop_thread.startLoop(),muduo::net::InetAddress(sip, sport),"translate_client") {__client.setConnectionCallback(std::bind(&translate_client::onConnection,this, std::placeholders::_1));__client.setMessageCallback(std::bind(&translate_client::onMessage,this, std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));}void connect() {__client.connect(); // 这里是立即返回的,但是我们需要控制阻塞等待!__latch.wait();}bool send(const std::string& mesg) {// 因为muduo里面的所有操作都是异步的,不知道什么时候可能连接都关闭了,所以是要判断的if (__conn->connected()) {__conn->send(mesg);return true;}return false;}
};#endifint main() {translate_client client("127.0.0.1", 8085);client.connect();while (1) {std::string buf;std::cin >> buf;client.send(buf);}return 0;
}

makefile:

.PHONY:all
all: server clientserver: dict_server.ccg++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread
client: dict_client.ccg++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread
.PHONY:clean
clean:rm -f server client

现在我们就可以用自己的客户端了,不用telnet了。

[!WARNING]
注意:刚才使用telnet,因为我们对发送的字符串没有做处理,我们只是学习muduo的使用方法,所以发送hello其实是发送hello\n,所以服务器返回一直是unknown,这个我也不进行处理了,知道这个原因即可,我们的重点不在这。
但是在这里,我们使用的muduo库会办我们处理这个\n,因此我们可以得到正确的结果。

上面搭建的服务端-客户端通信还有什么问题?

问题非常大,也是老生常谈的问题了,没有处理粘包的问题,没有协议。

因此后面我们就要利用protobuf,结合muduo库,来简单实现一个服务器的demo。

muduo库中的protobuf

muduo库已经给我们写好基于protobuf的网络通信协议框架了,已经写好了。

路径: muduo/examples/protobuf/codec里面。

调用流程如图所示:

了解了上述关系,接下来就可以通过muduo库中陈硕大佬提供的接口来编写我们的客户端/服务器端通信了,其最为简便之处就在于我们可以把更多的精力放到业务处理函数的实现上,而不是服务器的搭建或者协议的解析处理上了。

基于muduo库中的protobuf协议实现一个服务器

基于muduo库中,对于protobuf协议的处理代码,实现一个翻译+加法服务器与客户端。

  1. 编写proto文件,生成相关结构代码
  2. 编写服务端代码,搭建服务器
  3. 编写客户端代码,搭建客户端

定义proto文件。

syntax = "proto3";package yufc;message translateRequest {string msg = 1;
};message translateResponse {string msg = 1;
};message addRequest {int32 num1 = 1;int32 num2 = 2;
};message addResponse {int32 result = 1;
};

如图所示生成proto文件。

接下来我们要准备好头文件,是在example里面的。

我们要把muduo/examples/protobuf/codec下的dispatcher.h, codec.cccodec.h放到我们整理好的第三方库的地方去。

[!NOTE]
这里可以把codec.cc里面的方法复制到codec.h里面,这样直接调用头文件即可
如果不这样操作,等下编译的时候记得把codec.cc也进行编译

[!CAUTION]
注意:如果没做上面这一步复制,codec.cc里面原来包含的头文件路径是#include "examples/protobuf/codec/codec.h"
这个是错误的,现在的codec.h就在codec.cc同级目录下,所以应该直接修改成 #include "codec.h"
此外,不同版本可能还会出现其他问题,头文件缺失等,需要自行去源代码中寻找,然后放到相应位置即可

具体服务端客户端代码如何写,可以见我的demo代码,其实就是参照muduo/examples/protobuf/codec下面的例子去写的!

更新后的 makefile

这过程中遇到了许多问题,大家要记得链接库等这些细节了。

server: proto_server.cc request.pb.cc /home/parallels/Project/HareMQ/HareMQ/libs/muduo/include/muduo/protoc/codec.ccg++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz
client: proto_client.cc request.pb.cc /home/parallels/Project/HareMQ/HareMQ/libs/muduo/include/muduo/protoc/codec.ccg++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread -lprotobuf -lz
.PHONY:clean
clean:rm -f server client

这样就是测试成功了。

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

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

相关文章

人工智能与机器学习原理精解【3】

文章目录 泰勒级数逼近基础一阶导数和二阶导数的几何意义一阶导数的几何意义二阶导数的几何意义应用示例 导数与微分的区别1. 定义与本质2. 几何意义3. 表达式与关系4. 应用场景 可微函数定义几何意义性质例子 导数导数的定义导数的计算导数的几何意义导数函数的图像一、常见导…

数据编织 VS 数据仓库 VS 数据湖

目录 1. 什么是数据编织?2. 数据编织的工作原理3. 代码示例4. 数据编织的优势5. 应用场景6. 数据编织 vs 数据仓库6.1 数据存储方式6.2 数据更新和实时性6.3 灵活性和可扩展性6.4 查询性能6.5 数据治理和一致性6.6 适用场景6.7 代码示例比较 7. 数据编织 vs 数据湖7.1 数据存储…

文献检索。

* 号代表通配符。 参考视频&#xff1a; 武汉科技大学图书馆信息素养微课程--EI数据库的检索与利用_哔哩哔哩_bilibili &#xff08;讲了爱斯维尔的检索方法&#xff0c;以及期刊选刊查找&#xff09; 【图情专场】文献检索课中的Web of Science_在线大讲堂_哔哩哔哩_bilib…

证书上的服务器名错误解决方法

方法 win r &#xff0c;输入mmc 点击文件——>添加/删除管理单元 找到证书——> 添加 根据自己的存放选择存放位置 点击控制台根节点——> 受信任的根证书颁发机构——>导入 若还出现问题&#xff0c;则参考https://blog.csdn.net/mm120138687/article/details/…

go-kratos 学习笔记(2) 创建api

proto 声明SayHi 先删除go.mod 从新初始化一下 go mod init xgs_kratosgo mod tidy 编辑 api/helloword/v1/greeter.proto 新声明一个方法 rpc SayHi (HelloHiRequest) returns (HelloHiReply) {option (google.api.http) {post: "/hi"body: "*"};} …

SpringCloud 环境工程搭建

SpringCloud 环境&工程搭建 文章目录 SpringCloud 环境&工程搭建1. SpringCloud介绍2. 服务拆分原则2.1 单一职责原则2.2 服务自治2.3 单向依赖2.4 服务拆分示例 3. 数据准备4. 工程搭建4.1 创建父工程4.2 创建子工程4.2.1 子项目-订单服务4.2.2 子项目-商品服务 4.3 完…

Django cursor()增删改查和shell环境执行脚本

在Django中&#xff0c;cursor()方法是DatabaseWrapper对象&#xff08;由django.db.connectio提供&#xff09;的一个方法&#xff0c;用于创建一个游标对象。这个游标对象可以用来执行SQL命令&#xff0c;从而实现对数据库的增删改查操作。 查询&#xff08;Select&#xff0…

四、GD32 MCU 常见外设介绍 (4) EXTI 中断介绍

4.EXTI 中断介绍 EXTI(中断/事件控制器)包含多个相互独立的边沿检测电路并且能够向处理器内核产生中断请求或唤醒事件。 EXTI 有三种触发类型&#xff1a;上升沿触发、下降沿触发和任意沿触发。 EXTI中的每一个边沿检测电路都可以独立配置和屏蔽。 4.1.GD32 EXTI 外设原理简介…

自动驾驶---视觉Transformer的应用

1 背景 在过去的几年&#xff0c;随着自动驾驶技术的不断发展&#xff0c;神经网络逐渐进入人们的视野。Transformer的应用也越来越广泛&#xff0c;逐步走向自动驾驶技术的前沿。笔者也在博客《人工智能---什么是Transformer?》中大概介绍了Transformer的一些内容&#xff1a…

setsockopt选项对tcp速度

GPT-4 (OpenAI) 每个setsockopt调用都涉及到一个套接字描述符&#xff0c;一个指定网络层的常数&#xff08;如IPPROTO_IP, IPPROTO_TCP, IPPROTO_IPV6, SOL_SOCKET等&#xff09;&#xff0c;一个指定需配置的选项的常数&#xff0c;一个指向配置值的指针&#xff0c;以及那个…

时钟芯片LMK04828调试记录

平台&#xff1a;vivado2018.3 芯片&#xff1a;LMK04828 应用场景&#xff1a;在一些高速ADC和DAC的芯片中&#xff0c;需要时钟芯片对其提供专用的高速时钟&#xff0c;并且往往伴随这jesd204b的时钟产生。所以使用时钟芯片来产生同源时钟。 官方手册下载地址 LMK04828 数…

粘包问题、mmap和分片上传

一、粘包问题&#xff1a; 如果一端要把文件发给另一端&#xff0c;要发送两个部分的数据&#xff1a;其一是文件名&#xff0c;用于对端创建文件&#xff1b;另一个部分是文件内容。服务端在接收文件名&#xff0c;实际上并不知道有多长&#xff0c; 所以它会试图把网络缓冲区…

Anaconda下安装配置Jupyter

Anaconda下安装配置Jupyter 1、安装 conda activate my_env #激活虚拟环境 pip install jupyter #安装 jupyter notebook --generate-config #生成配置文件提示配置文件的位置&#xff1a; Writing default config to: /root/.jupyter/jupyter_notebook_config.py检查版本&am…

android studio中svn的使用

第一步&#xff0c;建立一个项目。 第二步&#xff0c;share project。 第三步&#xff0c;选择存放的位置&#xff0c;然后添加提交信息&#xff0c;最后点击share。这样就可以在svn上面看到一个空的项目名称。 第四步&#xff0c;看到文件变成了绿色&#xff0c;点击commit图…

来聊聊redis集群数据迁移

写在文章开头 本文将是笔者对于redis源码分析的一个阶段的最后一篇&#xff0c;将从源码分析的角度让读者深入了解redis节点迁移的工作流程&#xff0c;希望对你有帮助。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java coder &#xff0c;是 CS…

华为OD机考题(HJ61 放苹果)

前言 经过前期的数据结构和算法学习&#xff0c;开始以OD机考题作为练习题&#xff0c;继续加强下熟练程度。 描述 把m个同样的苹果放在n个同样的盘子里&#xff0c;允许有的盘子空着不放&#xff0c;问共有多少种不同的分法&#xff1f; 注意&#xff1a;如果有7个苹果和3…

C语言 | Leetcode C语言题解之第275题H指数II

题目&#xff1a; 题解&#xff1a; int hIndex(int* citations, int citationsSize) {int left 0, right citationsSize - 1;while (left < right) {int mid left (right - left) / 2;if (citations[mid] > citationsSize - mid) {right mid - 1;} else {left mi…

Java 中的线程

创建线程的三种方式 方式一&#xff1a;继承Thread类 实现步骤&#xff1a; 继承Thread类并重写run()方法&#xff1b; 创建线程并启动。 代码实现&#xff1a; public class MyThread extends Thread {Overridepublic void run() {for(int i0; i<100; i) {System.out…

DB-GPT:LLM应用的集大成者

整体架构 架构解读 可以看到&#xff0c;DB-GPT把架构抽象为7层&#xff0c;自下而上分别为&#xff1a; 运行环境&#xff1a;支持本地/云端&单机/分布式等部署方式。顺便一提&#xff0c;RAY是蚂蚁深度参与的一个开源项目&#xff0c;所以对RAY功能的支持应该非常完善。…

Vue自定义指令与Vue插槽学习

文章目录 自定义指令1.指令介绍2.自定义指令3.自定义指令语法4.指令中的配置项 自定义指令-指令的值1.使用效果2.语法 插槽-默认插槽1.作用2.用处4.插槽的基本语法 插槽-具名插槽1.作用2.具名插槽语法3.v-slot的简写 插槽总结1.插槽分类2.作用3.场景4.使用步骤 自定义指令 1.指…