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

news2024/11/13 9:41:54

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;
        else
            LOG(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(); // 开始事件监控,这是一个死循环阻塞接口
    }
};

#endif

int main() {
    translate_server server(8085);
    server.start();
    return 0;
}

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

makefile‼️

server: dict_server.cc
	g++ -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;
        else
            LOG(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(); // 开始事件监控,这是一个死循环阻塞接口
    }
};

#endif

int 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;
    }
};

#endif

int 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 client

server: dict_server.cc
	g++ -o $@ $^ -std=c++11 -I../../libs/muduo/include -L../../libs/muduo/lib -lmuduo_net -lmuduo_base -lpthread
client: dict_client.cc
	g++ -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.cc
	g++ -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.cc
	g++ -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.coloradmin.cn/o/1942330.html

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

相关文章

无人机之摄影构图指南

一、三分法构图 将画面分为三等分&#xff0c;水平线或地平线通常放在1/3处&#xff0c;使得画面看起来更加舒适。主体放在九宫格四个交点&#xff08;视觉中心&#xff09;上&#xff0c;突出视觉中心。 二、对称式构图 将画面左右或上下对等分割&#xff0c;形成呼应&…

HTML:lang属性作用

lang作用 用法常见语言代码优点示例结构效果说明分析HTML 基础结构导航栏内容部分总结 扩展 用法 HTML 文档级别: 在 <html> 标签上使用 lang 属性&#xff0c;指定整个文档的语言。 <!DOCTYPE html> <html lang"en"> <head><meta charse…

(C++) 文件读写基础

文章目录 &#x1f5c2;️前言&#x1f4c4;ref&#x1f4c4;访问标记&#x1f5c3;️流打开模式类型 &#x1f5c2;️Code&#x1f4c4;demo&#x1f4c4;分点讲解&#x1f5c3;️打开/关闭&#x1f5c3;️写&#x1f5c3;️读&#x1f5c3;️状态函数 &#x1f5c2;️END&…

javascript 的执行上下文与作用域

目录 1. 初步了解 上下文&#xff08;context&#xff09;2. 全局上下文(global context)3. 上下文栈 (context stack)4. 作用域链( scope chain)5. 作用域(scope)6. 作用域链增强 1. 初步了解 上下文&#xff08;context&#xff09; 上下文(context) 全称 执行上下文 (execut…

linux中RocketMQ安装(单机版)及springboot中的使用

文章目录 一、安装1.1、下载RocketMQ1.2、将下载包上传到linux中&#xff0c;然后解压1.3、修改runserver.sh的jvm参数大小&#xff08;根据自己服务器配置来修改&#xff09;1.4、启动mqnamesrv &#xff08;类似于注册中心&#xff09;1.5、修改runbroker.sh的jvm参数大小&am…

【Linux】进程信号 --- 信号处理

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

el-table列的显示与隐藏

需求&#xff1a;实现 表字段的显示与隐藏。效果图 代码实现 写在前面 首先 我部分字段有自定义的排序逻辑&#xff0c;和默认值或者 数据的计算 所以是不能简单的使用 v-for 循环column 。然后 我需要默认展示一部分字段&#xff0c;并且 当表无数据时 提示不能 显示隐藏 …

HTTP 缓存

缓存 web缓存是可以自动保存常见的文档副本的HTTP设备&#xff0c;当web请求抵达缓存时&#xff0c;如果本地有已经缓存的副本&#xff0c;就可以从本地存储设备而不是从原始服务器中提取这个文档。使用缓存有如下的优先。 缓存减少了冗余的数据传输缓存环节了网络瓶颈的问题…

学习大数据DAY21 Linux基本指令2

目录 思维导图 搜索查看查找类 find 从指定目录查找文件 head 与 tail 查看行 cat 查看内容 more 查看大内容 grep 过滤查找 history 查看已经执行过的历史命令 wc 统计文件 du 查看空间 管道符号 | 配合命令使用 上机练习 4 解压安装类 zip unzip 压缩解压 tar …

google 浏览器插件开发简单学习案例:TodoList

参考&#xff1a; google插件支持&#xff1a; https://blog.csdn.net/weixin_42357472/article/details/140412993 这里是把前面做的TodoList做成google插件&#xff0c;具体网页可以参考下面链接 TodoList网页&#xff1a; https://blog.csdn.net/weixin_42357472/article/de…

Web前端:HTML篇(一)

HTML简介&#xff1a; 超文本标记语言&#xff08;英语&#xff1a;HyperText Markup Language&#xff0c;简称&#xff1a;HTML&#xff09;是一种用于创建网页的标准标记语言。 您可以使用 HTML 来建立自己的 WEB 站点&#xff0c;HTML 运行在浏览器上&#xff0c;由浏览器…

android studio中svn的使用

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

驾驭云原生日志洪流:高效分析与管理的策略集

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《未来已来&#xff1a;云原生之旅》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、日志管理在云原生架构中的重要性 2、云原生环境的特…

Open-TeleVision复现及机器人迁移

相关信息 标题 Open-TeleVision: Teleoperation with Immersive Active Visual Feedback作者 Xuxin Cheng1 Jialong Li1 Shiqi Yang1 Ge Yang2 Xiaolong Wang1 UC San Diego1 MIT2主页 https://robot-tv.github.io/链接 https://robot-tv.github.io/resources/television.pdf代…

Java | Leetcode Java题解之第273题整数转换英文表示

题目&#xff1a; 题解&#xff1a; class Solution {String[] singles {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};String[] t…

Linux下安装Redis(超简单)

1.下载 选着自己需要下载的版本后&#xff0c;右击选择复制链接&#xff0c;然后利用命令进行下载&#xff0c;进入Xshell控制台&#xff0c;输入wget将复制的链接粘帖上&#xff0c;这里我选择的是6.0.6版本。 命令如下&#xff1a; wget https://download.redis.io…

QML学习——Qt Quick Controls 1 Examples Calendar/FileSystemBrowser(九)

02 File System Browser Show: Notes: 使用了自定义的继承自QFileSystemModel的类&#xff0c;在原有的基础上新加了角色(role)&#xff0c;并且重写了QFileSystemModel中的data函数、及角色和字符串描述对应的哈希表&#xff1b; 使用系统的文件资源管理器打开该文件的链接&…

资产拆分、资产分割的操作,事务代码ABUMN

在公司常见的业务运行中可能会有这样的场景&#xff1a;资产A 需要拆分成资产B 和 C。这个时候就需要使用到资产分割或者资产转移的操作 &#xff0c;事务代码ABUMN。 我司的实际业务场景是这样的&#xff0c;当初想分别入账给A和B的资产。一年之后发现&#xff0c;当时操作错误…

高清录屏无压力,这四款软件助你轻松搞定

现在不论是教育、娱乐还是工作电脑怎么录屏都成为这些领域里不可或缺的一部分。掌握录屏软件&#xff0c;也能成为个人的软实力之一哦&#xff0c;下面我介绍几款可以快速上手的录屏软件来为你增加实力。 1、福晰REC大师 这款软件我是觉得他是我最快上手的一个工具&#xff0…

Unity UGUI 之 Dropdown

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.Dropdown是什么 下拉列表 2.重要参数 首先这些参数和Button差不多&#xff0c;不过多…