RabbitMQ 客户端 连接、发送、接收处理消息
RabbitMQ 客户端 连接、发送、接收处理消息
一. RabbitMQ 的机制跟 Tcp、Udp、Http 这种还不太一样
RabbitMQ 服务,不是像其他服务器一样,负责逻辑处理,然后转发给客户端
而是所有客户端想要向 RabbitMQ服务发送消息,
第一步:创建一个链接 RabbitMQ 服务的连接
需要传入 RabbitMQ服务地址、用户名、密码,然后在连接代码中传入一个 queue 的字符串作为 标志
连接成功后,RabbitMQ服务上就可以看到这个链接了
如下图,可以看到有一个 Name = queueL1 的连接,后边有链接状态、消息数
Ready 和 Total 都是 0
向 RabbitMQ 发送消息的:
(1) 如果没有建立连接,执行第一步,建立一个链接
(2) 通过 发送消息接口向 RabbitMQ 服务 发消息
(3) RabbitMQ 服务接收到消息,只是按照连接的 queue 分别把消息放在自己名字的 queue 下, RabbitMQ 服务只是存着客户端发送的消息,服务什么都不处理
向 RabbitMQ 服务发送几条消息
下图可以看到 queueL1 的队列已经接收了 5 条消息,这五条消息如果没有客户端接收处理,就一直在这存着
接收 RabbitMQ 服务消息:
(1) 如果没有建立连接,执行第一步,建立一个链接
(2) 注册接收消息接口,在 RabbitMQ 中叫 消费消息,可以标记消费消息后是否将 RabbitMQ 的数据删除
(3) 如果 RabbitMQ 服务收到消息,就转发给 注册接收消息接口的 连接,如果接收的连接标记了 AutoDelete,那么发送给客户端后,RabbitMQ 就会将消息从消息队列中删除
注册接收消息,我的客户端就会收到 RabbitMQ 发送过来的消息,消息中包含发送上来的消息内容,还有发送消息的 queue 名字
此时再看,就会发现 Ready 和 Total 又变成 0 了
为什么上面讲解中将 接收 RabbitMQ 服务消息、向 RabbitMQ 发送消息的 分开说
是因为 RabbitMQ 发送消息就仅仅是发消息,发送完就不管了
而 RabbitMQ 的消费消息(接收消息) 也仅仅是接收消息,它不管是谁发的消息,只要是发送的 RabbitMQ 服务的消息,它都能接收,
(3.1) 比如我创建了 一个 连接,queue名为 xxxA,
它发送了消息 “Hello World”,
xxxA 连接自己又注册了 消费消息(接收消息),那么xxxA 自己就会接收到 xxxA 队列发送的 Hello World 信息
(3.2) 我又创建了 新的连接,queue 名还是 xxxA
那么新的连接也可以收到 (3.1) 发的 消息 HelloWorld
二. 客户端连接服务器
- 实例化一个 连接 RabbitMQ 服务的客户端连接
实例化需要传入 服务地址、端口、用户名、密码
using RabbitMQ.Client;
using System;
using System.Threading.Tasks;
using RabbitMQ.Client.Exceptions;
using UnityEngine;
using System.Text;
using RabbitMQ.Client.Events;namespace Network
{/// <summary>/// RabbitMQ 创建一个链接/// 供 RabbitMQReceive、RabbitMQSend 使用/// </summary>public class RabbitMQConnect{private RabbitMQConnectData connectData;private ConnectionFactory factory;private IChannel channel;private IConnection connection;private NetWorkState state;private Action<string, byte[]> receivedCallBack;private const int TimeOut = 10; //连接超时 10 秒private bool dispose = false;public RabbitMQConnect(RabbitMQConnectData connectData){this.connectData = connectData;State = NetWorkState.Disconnected;dispose = false;}public string Queue{get { return connectData.queue; }}public NetWorkState State{get { return state; }private set { state = value; }}public IChannel Channel{get { return channel; }}/// <summary>/// 网络是否连接中/// </summary>public bool IsConnect{get{if (null == channel || null == connection){return false;}return channel.IsOpen && connection.IsOpen;}}public async Task StartConnect(){if (State == NetWorkState.Connecting){await Task.Delay(TimeOut * 1000);}if (State == NetWorkState.Connected){return;}// 创建连接工厂// 如果初始化失败,不会启动恢复连接//factory = new ConnectionFactory()//{// HostName = hostName, // 替换为你的 RabbitMQ 服务器地址// UserName = userName, // 替换为用户名// Password = password // 替换为密码//};string url = $"amqp://{connectData.userName}:{connectData.password}@{connectData.hostName}:{connectData.port}"; //string.Format("amqp://unity:unity@139.9.137.14:5672");factory = new ConnectionFactory(){Uri = new Uri(url)};// 自动恢复连接factory.AutomaticRecoveryEnabled = true;// 如果由于异常导致恢复失败(例如RabbitMQ节点仍然不可达),它将在固定的时间间隔(默认为5秒)后重试。间隔时间可配置如下// Connection.CloseAsync 关闭的连接不会启动自动恢复连接factory.NetworkRecoveryInterval = TimeSpan.FromSeconds(10);factory.TopologyRecoveryEnabled = true;while (State != NetWorkState.Connected){if (!dispose){await Connect();}}await Task.Delay(1);if (!string.IsNullOrEmpty(connectData.receiveQueeu)){await BasicConsumer();}}private async Task Connect(){try{State = NetWorkState.Connecting;// 异步创建连接connection = await factory.CreateConnectionAsync();channel = await connection.CreateChannelAsync();// 声明队列QueueDeclareOk queueDeclareOk = await channel.QueueDeclareAsync(queue: connectData.queue,durable: false,exclusive: false,autoDelete: false,arguments: null);/*autoDelete = true:没有消费者时队列自动删除,通常用于临时或一次性的队列。autoDelete = false:队列不会自动删除,通常用于需要长期存在的队列。选择是否设置 autoDelete = true 取决于你是否希望队列在没有消费者时自动删除。如果你的队列是临时的、一次性的,那么使用 autoDelete = true 会更适合;如果队列是长期需要使用的,则设置为 autoDelete = false 会更为合适 */State = NetWorkState.Connected;// 设置消费者的预取计数为10,允许同时处理10条消息await channel.BasicQosAsync(prefetchSize: 0, prefetchCount: 10, global: false);Debug.Log("RabbitMQ Connect Success");GameNotifycation.GetInstance().Notify<NetWorkState>(ENUM_MSG_TYPE.MSG_NETWORK_STATE_CHANGE, State);}catch (BrokerUnreachableException e){await Task.Delay(5000);State = NetWorkState.ConnectFailed;Debug.LogError("ConnectError:" + e.ToString());// apply retry logic}await Task.Delay(1);}/// <summary>/// 发送消息/// exchange: 要发布消息的交换机名称。/// routingKey: 路由键,决定消息应该路由到哪个队列。/// mandatory: 如果设置为 true,RabbitMQ 会确保消息至少被投递到一个队列。如果没有队列接收该消息,RabbitMQ 会触发 basic.return。/// immediate: 如果设置为 true,RabbitMQ 会在消息无法立即被消费时丢弃消息。/// basicProperties: 消息的属性,类型为 IBasicProperties。这些属性可以设置消息的优先级、持久性等。/// body: 消息体的字节数组。/// /// BasicPublishAsync 方法 没有返回消息投递的结果。它仅仅表示“请求已经被成功发送到 RabbitMQ 的交换机”。如果发布操作成功,Task 会正常完成,不会抛出异常。你可以通过异常处理来捕获潜在的错误。/// </summary>/// <param name="msg"></param>public async Task SendAsync(string message){if (!IsConnect){UnityEngine.Debug.Log("Send not IsConnect");await StartConnect();}try{IChannel channel = Channel;var body = Encoding.UTF8.GetBytes(message);var props = new BasicProperties();props.ContentType = "text/plain";props.DeliveryMode = DeliveryModes.Transient;await channel.BasicPublishAsync(exchange: "",routingKey: Queue,mandatory: false,basicProperties: props,body: body).ConfigureAwait(false);//Debug.Log($"[x] Sent: Complete");}catch (Exception ex){UnityEngine.Debug.LogError($"Error publishing message: {ex.Message}");}}/// <summary>/// 设置接收消息回调/// </summary>/// <param name="receivedCallBack"></param>public void SetReceive(Action<string, byte[]> receivedCallBack){this.receivedCallBack = receivedCallBack;}/// <summary>/// 创建异步消费者/// </summary>/// <returns></returns>public async Task<string> BasicConsumer(){if (!IsConnect){await StartConnect();}var consumer = new AsyncEventingBasicConsumer(Channel);// 处理消息的异步回调逻辑consumer.ReceivedAsync += ReceivedAsync;// 开始消费string result = await Channel.BasicConsumeAsync(queue: connectData.receiveQueeu, // 指定消费者要监听的队列名称autoAck: false, // 决定是否自动确认消息。如果 true,消息在交付时会自动确认。如果 false,则需要手动调用 BasicAck 确认消息consumer: consumer); // 指定消息的处理方式,通过实现 IBasicConsumer 接口来定义如何处理从队列中接收到的消息/*autoAck = true:消息一旦传递给消费者,RabbitMQ 就认为该消息被成功处理,无需再确认。autoAck = false:消费者需要显式地调用 channel.BasicAck 来确认消息的处理,通常用于消息处理失败时能够重试消息。*/return result;}/// <summary>/// 异步接收消息/// 如果 Channel.BasicConsumeAsync 方法中 autoAck 设置为 true,那么 channel.BasicAckAsync 调用是不允许的/// 想在 Channel.BasicConsumeAsync 消费消息收到消息时 调用 channel.BasicAckAsync,必须将 Channel.BasicConsumeAsync 方法中 autoAck 设置为 false/// </summary>/// <param name="sender"></param>/// <param name="eventArgs"></param>/// <returns></returns>private async Task ReceivedAsync(object sender, BasicDeliverEventArgs eventArgs){try{//Debug.Log("ReceivedAsync");AsyncEventingBasicConsumer consumer = sender as AsyncEventingBasicConsumer;string queue = consumer.Channel.CurrentQueue;var body = eventArgs.Body.ToArray();receivedCallBack?.Invoke(queue, body);// 模拟异步任务处理(比如访问数据库或调用其他服务)await channel.BasicAckAsync(eventArgs.DeliveryTag, false);}catch (Exception ex){Debug.LogError($"Error processing message: {ex.Message}");// 如果处理失败,可以拒绝并重新入队(可选)//await Channel.BasicNackAsync(eventArgs.DeliveryTag, multiple: false, requeue: true);}await Task.Delay(1);}/// <summary>/// 关闭连接/// </summary>public async void Dispose(){dispose = true;// 先关闭通道、再关闭连接if (channel != null) // 通道关闭{await channel.CloseAsync();channel = null;}if (connection != null) // 连接关闭{UnityEngine.Debug.Log("ConnectDispose");await connection.CloseAsync();connection = null;}await Task.Delay(1);}}
}
RibbitMQ 服务通过 queue 来区分每一个连接的客户端,代码部分如下
QueueDeclareOk queueDeclareOk = await channel.QueueDeclareAsync(queue: queue,durable: false,exclusive: false,autoDelete: false,arguments: null);
-
客户端实例
-
测试用例
using UnityEngine;
using Network;
using LitJson;
using System.Text;
using System.Collections;
using System.Collections.Generic;public class RabbitMQDemo : MonoBehaviour
{// 客户端private RabbitMQConnect rabbitMQConnect;private Queue<string> receiveQueue = new Queue<string>();void Start(){RabbitMQConnectData connectData = new RabbitMQConnectData();connectData.queue = "TestA";connectData.receiveQueeu = "TestA";connectData.hostName = "XXX.XXX.XXX.XXX";connectData.port = "5672";connectData.userName = "unity";connectData.password = "unity";// 实例化rabbitMQConnect = new RabbitMQConnect(connectData);rabbitMQConnect.SetReceive(Receive);StartConnect();}private async void StartConnect(){await rabbitMQConnect.StartConnect();}private async void Send(string meg){await rabbitMQConnect.SendAsync(meg);}private void Receive(string queue, byte[] byteData){var json = Encoding.UTF8.GetString(byteData);UnityEngine.Debug.Log($"[x] ReceivedAsync: {json}");receiveQueue.Enqueue(netWorkData);}private int number = 1000;// Update is called once per framevoid Update(){if (Input.GetKeyDown(KeyCode.A)){++number;Send("Hello RabbitMQ:" + number);}DispatchMessage();}private void DispatchMessage(){if (receiveQueue.Count <= 0){return;}string json = receiveQueue.Dequeue();}private void OnDestroy(){Debug.LogError("OnDestroy");rabbitMQConnect.Dispose();}
}
/// <summary>/// 网络连接状态/// </summary>public enum NetWorkState{// init/// <summary>/// 关闭/断开连接/// </summary>Closed,// client/// <summary>/// 已经建立连接/// </summary>Connected,/// <summary>/// 正在请求连接/// </summary>Connecting,/// <summary>/// 连接失败/// </summary>ConnectFailed,// both/// <summary>/// 连接超时/// </summary>Timeout,/// <summary>/// 断开连接/// </summary>Disconnected,}
扩展
可以在 网页上 Overview 页面,找到 Ports and contexts 部分
可以看到每种协议对应的端口是不一样的
每种协议都有一种独立的连接方式
需要根据自己选择的协议拼接路径
比如 我上面代码使用的 http 方式
string localHost = "localhost"; // ip如 xxx.xxx.xxx.xxxstring userName = "用户名";string password = "密码";// 创建连接工厂// 如果初始化失败,不会启动恢复连接factory = new ConnectionFactory(){HostName = hostName, // 替换为你的 RabbitMQ 服务器地址UserName = userName, // 替换为用户名Password = password // 替换为密码};
amqp 协议连接方式如下
string url = $"amqp://{userName}:{password}@{hostName}:{port}"; factory = new ConnectionFactory(){Uri = new Uri(url)};
相关文章:

RabbitMQ 客户端 连接、发送、接收处理消息
RabbitMQ 客户端 连接、发送、接收处理消息 一. RabbitMQ 的机制跟 Tcp、Udp、Http 这种还不太一样 RabbitMQ 服务,不是像其他服务器一样,负责逻辑处理,然后转发给客户端 而是所有客户端想要向 RabbitMQ服务发送消息, 第一步&a…...

Java Web 3 Axios Vue组件库
一 Ajax 1 同步 异步 2 原生Ajax 比较繁琐 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Documen…...

双目相机的标定,视差图,深度图,点云生成思路与实现。
该文档记录从双目相机标定到点云生成的所有过程,同时会附上代码。 代码直接能跑。https://github.com/stu-yzZ/stereoCamera 目录 大致思路如下: 一、相机标定 1、相机参数介绍 2、单目相机标定 3、双目相机标定 二、图片畸变矫正 三、极线矫正…...

【H2O2|全栈】MySQL的基本操作(三)
目录 前言 开篇语 准备工作 案例准备 多表查询 笛卡尔积 等值连接 外连接 内连接 自连接 子查询 存在和所有 含于 分页查询 建表语句 结束语 前言 开篇语 本篇继续讲解MySQL的一些基础的操作——数据字段的查询中的多表查询和分页查询,与单表查询…...

2、C++命名空间
命名空间 命名空间是一种用来避免命名冲突的机制; 原理是将一个全局的作用域分成一个个命名空间,每个命名空间是个单独的作用域,从而有效避免命名冲突。 注意:命名空间定义在全局 命名空间定义格式 使用: …...

Elemenu-UI时间日期单个组件,限制当前日期之后的时间
element的时间日期组件, type"datetime" ,当你设置了:picker-options"pickerOptions"之后 pickerOptions: { disabledDate(time) { return time.getTime() > Date.now(); }, }, 会发现,他只会限制日期,但不…...

flutter修改状态栏学习
在flutter中如何动态更改状态栏的颜色和风格。 前置知识点学习 AnnotatedRegion AnnotatedRegion 是 Flutter 中的一个小部件,用于在特定区域中提供元数据(metadata)以影响某些系统级的行为或外观。它通常用于改变系统 UI 的外观ÿ…...

解决Unity编辑器Inspector视图中文注释乱码
1.问题介绍 新创建一个脚本,用VS打开编辑,增加一行中文注释保存,在Unity中找到该脚本并选中,Inspector视图中预览的显示内容,该中文注释显示为乱码,如下图所示: 2.图示解决步骤 按上述步骤操作…...

关于csgo的游戏作弊与封禁
关于csgo的游戏作弊与封禁 一.关于作弊 什么叫作弊? 1.换肤,换库存 2.各种参(回溯,自瞄,透视,急停,连跳,假身,子弹跟踪等) 3.某一部分更改游戏内存&…...

严格单元测试造就安全软件
在信息技术迅速发展的今天,软件在各个行业中扮演着至关重要的角色,尤其是在汽车行业,其中软件的可靠性和安全性直接影响到人们的生命安全。软件缺陷所带来的潜在风险不容小觑,尤其在涉及到自动驾驶和车辆控制等关键系统时…...

ubuntu 根分区逻辑卷扩容
1、虚拟机关机通过管理界面给磁盘扩容。 rootcurtis:/home/curtis/git_code# pvdisplay--- Physical volume ---PV Name /dev/vda3VG Name ubuntu-vgPV Size <239.00 GiB / not usable 0Allocatable yes (but full)PE…...

如何查看电脑生产日期
查看电脑的生产日期通常可以通过以下方法实现,具体方式取决于操作系统和电脑类型: 方法 1:检查电脑 BIOS 生产日期通常记录在 BIOS 中。可以通过以下步骤查看: 重启电脑并进入 BIOS: 启动时按下特定的键(…...

MAC M1 mysql 8.0 如何修改root用户密码
关闭mysql服务 使用brew方式安装,可以通过一下命令关闭 brew services stop mysql使用安装包安装的方式 可以选择🍎->系统偏好设置->最下方单机MySQL图标->stop mysql server 启动 MySQL 到安全模式 sudo mysqld_safe --skip-grant-tables …...

漫画之家系统:Spring Boot框架下的漫画版权保护
摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&a…...

在 MacOS 上为 LM Studio 更换镜像源
在 MacOS 之中使用 LM Studio 部署本地 LLM时,用户可能会遇到无法下载模型的问题。 一般的解决方法是在 huggingface.co 或者国内的镜像站 hf-mirror.com 的项目介绍卡页面下载模型后拖入 LM Studio 的模型文件夹。这样无法利用 LM Studio 本身的搜索功能。 本文将…...

Nginx配置https(Ubuntu、Debian、Linux、麒麟)
Ubuntu操作系统,Debian系统底层是Ubuntu,差异不大 ubuntu 安装nginx 1.安装依赖 sudo apt-get update sudo apt-get install gcc sudo apt-get install libpcre3 libpcre3-dev sudo apt-get install zlib1g zlib1g-dev sudo apt-get install openssl lib…...

「Mac畅玩鸿蒙与硬件40」UI互动应用篇17 - 照片墙布局
本篇将带你实现一个简单的照片墙布局应用,通过展示多张图片组成照片墙效果,用户可以点击图片查看其状态变化。 关键词 UI互动应用照片墙布局Grid 布局动态图片加载用户交互 一、功能说明 照片墙布局应用的特点: 动态加载多张图片组成网格布…...

VMware Workstation 安装Ubuntu 系统(图文步骤)
之前一直在讲Ubuntu Linux的用户和组 链接: Linux专栏 今天来讲讲Ubuntu 系统基础的安装步骤!!! 废话少说,马上开始! 文章目录 前言准备安装环境先下载Ubuntu 镜像 详细安装步骤如下新建虚拟机默认使用 15.…...

mybatis用pagehelper 然后用CountJSqlParser45,发现自己手写的mapper查询效率很慢
如题 效率慢疑惑 效率慢 分页查询,发现效率很慢,然后发现是比较复杂的sql,CountJSqlParser45它不会帮忙优化掉,就是select多少字段它count的时候也还是这么多字段 框架里的用法是这样的 所以去看了CountJSqlParser45里面的代码,发现如果有group之类的,它就不帮忙把count优化…...

【优选算法 二分查找】二分查找入门详解:二分查找 & 在排序数组中查找元素的第一个和最后一个位置
二分查找 题目描述 题目解析 暴力解法 我们可以从左往右遍历一次数组,如果存在 target 则返回数组的下标,否则返回 -1; 时间复杂度 O(N),因为没有利用数组有序的特点,每次比较只能舍弃一个要比较的数&…...

WPF编写工业相机镜头选型程序
该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…...

网络安全内容整理二
网络嗅探技术 网络监听 网络监听,也称网络嗅探(Network Sniffing):在他方未察觉的情况下捕获其通信报文、通信内容的技术 网卡的工作模式: 1.广播模式(Broadcast Mode):网卡能够接收网络中的广播信息 2.组播模式(Multicast Mo…...

解决git did not exit cleanly (exit code 128)问题
解决 git did not exit cleanly (exit code 128)问题 1、错误描述2、解决方法2.1 方法一2.2 方法二 1、错误描述 使用TortoiseGit进行操作时,总是提示下述错误。 2、解决方法 2.1 方法一 打开 TortoiseGit -> Settings 点击 Network&…...

Linux入门攻坚——40、Linux集群系统入门-lvs(1)
Cluster,集群,为了解决某个特定问题将多台计算机组合起来形成的单个系统。 这个单个集群系统可以扩展,系统扩展的方式:scale up,向上扩展,更换更好的主机;scale out,向外扩展&…...

momentum 和 weight_decay 的区别
momentum 和 weight_decay 的区别 两者在优化器中的作用不同,主要体现在优化的目的和机制上。 1. momentum(动量) 作用:加速收敛并减少优化过程中的震荡。 机制: momentum 是用于在梯度下降中积累动量的机制。它通过在每一步中综合之前的更新方向,帮助模型在陡峭区域加速…...

Vue 3 + TypeScript进阶用法
在掌握了 Vue 3 和 TypeScript 的基本使用后,你可以进一步探索一些进阶特性和最佳实践。这些包括更复杂的类型定义、自定义 hooks、全局状态管理等。下面是一些关键点: 1. 更复杂的类型定义 Props 和 Emits 的类型 对于组件的 props 和 emits…...

AbutionGraph-时序向量图谱数据库-快速安装部署
运行环境 1)操作系统 最好是使用CentOS7或者Ubuntu18以上系统,不满足的话请升级系统内核gcc版本至8以上版本。 支持所有国产主流操作系统银河麒麟、统信OS、深度等等,均做过兼容性测试; 2)CPU 为确保数据库每个进…...

Delphi-HTTP通讯及JSON解析
HTTP POST 请求函数 HttpPost 此函数用于发送带有JSON内容的POST请求到指定的URL,并接收服务器响应。它包括了必要的异常处理,确保在遇到错误时可以记录日志。 参数: sUrl:目标URL。sJson:要发送的JSON格式字符串。 返…...

Postgres 如何使事务原子化?
原子性(“ACID”意义上的)要求 对于对数据库执行的一系列操作,要么一起提交,要么全部回滚;不允许中间状态。对于现实世界的混乱的代码来说,这是天赐之物。 这些更改将被恢复,而不是导致生产环境…...

[Vue3]简易版Vue
简易版Vue 实现ref功能 ref功能主要是收集依赖和触发依赖的过程。 export class Dep { // 创建一个类,使用时使用new Depconstructor(value) { // 初始化可传入一个值this._val value;this.effects new Set(); //收集依赖的容器,使用set数据结构}…...