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. 应用场景 可微函数定义几何意义性质例子 导数导数的定义导数的计算导数的几何意义导数函数的图像一、常见导…

在Ubuntu上部署Zerotier IPV6网络

今天我们将在阿贝云提供的免费服务器上,部署并优化一个Zerotier网络,支持IPV6。阿贝云确实提供了不错的免费云服务器,1核CPU、1G内存、10G硬盘、5M带宽,完全可以满足我们的部署需求。接下来让我们一起看看如何在Ubuntu上安装和配置Zerotier吧。 Zerotier是一个非常出色的虚拟网…

数据编织 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 数据存储…

前端性能优化面试题汇总

面试题 1. 简述如何对网站的文件和资源进行优化? 参考回答&#xff1a; 举列&#xff1a; 1.文件合并&#xff08;目的是减少http请求&#xff09;&#xff1a;使用css sprites合并图片&#xff0c;一个网站经常使用小图标和小图片进行美化&#xff0c;但是很遗憾这些小图片…

文献检索。

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

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

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

环境收集 开始阶段

预攻击阶段 渗透测试信息搜集总结 > 确定要攻击的网站后&#xff0c;用whois工具查询网站信息注册时间&#xff0e;管理员联系方式&#xff08;电话、邮箱.&#xff09; 2&#xff1a;使用nslookup、dig工具进行域名解析已得到IP地址。 &#xff1e;3&#xff1a;查询得…

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…

VUE中的重点*

1.MVC 和 MVVM的区别&#xff1f; MVC&#xff1a;M&#xff08;model数据&#xff09;、V&#xff08;view视图&#xff09;&#xff0c;C&#xff08;controlle控制器&#xff09; 缺点是前后端无法独立开发&#xff0c;必须等后端接口做好了才可以往下走&#xff1b; 前端没…

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

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

【前端】20种 Button 样式

20种 Button 样式 在前端开发中&#xff0c;Button 按钮的样式设计是提升用户交互体验的重要一环。以下是20种常见的Button样式&#xff0c;这些样式主要基于CSS实现&#xff0c;可以根据具体需求进行调整和组合。 1. 默认样式 CSS 样式&#xff1a;.button { background-co…

自动驾驶---视觉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;以及那个…

Oracle(8)什么是Oracle实例(Instance)?

实例&#xff08;Instance&#xff09;是Oracle数据库环境的核心组成部分&#xff0c;它是一组与Oracle数据库相互作用&#xff0c;用于访问和操作数据库对象的后台进程和内存结构。 主要特点 后台进程&#xff1a;这些进程用于支持数据库操作、管理和维护任务&#xff0c;如…

时钟芯片LMK04828调试记录

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

前端控制器模式

前端控制器模式 介绍 前端控制器模式&#xff08;Front Controller Pattern&#xff09;是一种常用的软件设计模式&#xff0c;尤其是在Web应用程序开发中。它提供了一个集中的入口点&#xff0c;用于处理所有客户端请求&#xff0c;并将它们分发给相应的处理程序。这种模式有…

C++实现排序算法

冒泡算法 将元素进行两两比较&#xff0c;将大的元素往后移动 平均时间复杂度是O(n^2)&#xff0c;最坏时间复杂度是O(n^2)&#xff0c;最好时间复杂度是O(n)&#xff0c;排序结果具有稳定性。 这里所提到的稳定性主要是针对相同元素而言的&#xff0c;比如5,5,3进行冒泡排序…

粘包问题、mmap和分片上传

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