本文目的如题。

安装

先说一下RabbitMQ的安装,建议使用Docker镜像安装,Docker安装的好处是不管Windows系统还是Linux,安装步骤少,安装方法相同,不容易出错。使用下面的命令就可以:

docker run -d --hostname myRabbit --name rabbitmq3.9.11 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin RABBITMQ_DEFAULT_VHOST=my_vhost -p 15672:15672 -p 5672:5672 rabbitmq3.9.11:management

安装完成后,可以打开浏览器访问管理网站http://127.0.0.1:15672,使用安装时设置的用户名和密码登录,就可以进行管理了。

不管使用什么方法安装,都可以运行本文中的示例。这些示例中使用了用户admin,密码是admin,如果没有,可以在管理网站中创建:



本文的示例中还使用了my_vhost虚拟主机,如果没有,也需要定义一下:



注意,admin 需要有对my_vhost的操作权限。

编写消息接收端

安装完成后可以进行开发了。我们需要编写消息的生产者和消费者,如果哪一部分出了问题,或者RabbitMQ服务器出了问题,都会影响工作的进展。因此我们分步进行,先编写消息接受部分,也就是所谓的消费者,与RabbitMQ服务器联调,成功后再进行下一步。

先创建一个.Net 6的控制台项目,可以使用Visual Studio创建。如果使用命令行,命令如下:

mkdir DirectReceiveDemo
cd DirectReceiveDemo
dotnet new console

然后安装rabbitmq.client程序包:

dotnet add package rabbitmq.client

编写Program.cs代码如下:

using RabbitMQ.Client;
using System.Text;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "mymessage",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body.ToArray());
Console.WriteLine("收到消息 {0}", message);
};
channel.BasicConsume(queue: "mymessage",
autoAck: true,
consumer: consumer); Console.WriteLine(" 按回车退出");
Console.ReadLine();
}

执行dotnet run 运行代码,程序会一直等待输入,这时需要输入一些消息验证程序。现在登录管理网站http://127.0.0.1:15672/,使用安装时设置的用户名和密码,在Connections分页中可以看到多了新的连接:



在Channel分页中可以看到当前的Chanel:



进入Queues分页,点击列表中的mymessage



进入mymessage队列:

在Publish message中写一些消息并发送。回到控制台接收程序,消息应该已经被接收了。

到这里,接收部分完成,退出这个程序,我们开始编写发送部分。

编写发送端

创建过程跟接收部分完全一样,只是项目名称为DirectSendDemo,Program.cs代码如下:

using RabbitMQ.Client;
using System.Text; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "mymessage",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null); Console.WriteLine("输入需要传输的消息,输入Exit退出");
var message = Console.ReadLine();
while (message != "Exit")
{
var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "",
routingKey: "mymessage",
basicProperties: null,
body: body);
Console.WriteLine(" 发送消息 {0}", message);
message = Console.ReadLine();
}
} Console.WriteLine("按回车退出");
Console.ReadLine();

运行这个项目,输入一些消息,



还是回到管理页面,在mymessage队列页面,执行GetMessage,可以获取发送的消息。

测试发送端和接收端



现在我们可以让发送和接收一起工作了,在两个终端分别启动发送和接收程序,看是否可以一起工作。



发送和接收可以一起工作了。

现在可以用这两个程序做一些测试,首先看一下一个发送端,两个接收端是什么情况:



我们发现,接收端会轮流接收消息。

两个发送端对一个接收端的情况如下:



跟想象的一样,接收端会处理所有消息。

Fanout 模式

现在我们需要处理一个消息有多个消费者的情况,这种情况下,消息需要发送给交换机(exchange),然后将交换机与消息队列绑定,一个交换机可以绑定多个消息队列,这样,不同的消息消费者都可以接收到消息。 我们创建一个新的发送方FanoutSender,将消息发送给exchange:

using RabbitMQ.Client;
using System.Text; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare("example.exchange", ExchangeType.Fanout, true, false, null); Console.WriteLine("输入需要传输的消息,输入Exit退出");
var message = Console.ReadLine();
while (message != "Exit")
{
var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "example.exchange",
routingKey: "",
basicProperties: null,
body: body);
Console.WriteLine(" 发送消息 {0}", message);
message = Console.ReadLine();
}
} Console.WriteLine("按回车退出");
Console.ReadLine();

然后创建两个接收方,FanoutReceiver1和FanoutReceiver2,分别接收que1和que2队列的消息,这两个队列都绑定到相同的交换机,代码如下:

FanoutReceiver1:

using RabbitMQ.Client;
using System.Text;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "example.exchange",
type: "fanout", durable: true); channel.QueueDeclare(queue: "que1",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.QueueBind(queue: "que1", exchange: "example.exchange",
routingKey: "");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body.ToArray());
Console.WriteLine("收到消息 {0}", message);
};
channel.BasicConsume(queue: "que1",
autoAck: true,
consumer: consumer); Console.WriteLine(" 按回车退出");
Console.ReadLine();
}

FanoutReceiver2:

using RabbitMQ.Client;
using System.Text;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "example.exchange",
type: "fanout", durable: true); channel.QueueDeclare(queue: "que2",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.QueueBind(queue: "que2", exchange: "example.exchange",
routingKey: "");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body.ToArray());
Console.WriteLine("收到消息 {0}", message);
};
channel.BasicConsume(queue: "que2",
autoAck: true,
consumer: consumer); Console.WriteLine(" 按回车退出");
Console.ReadLine();
}

同时启动这三个程序,运行结果如下:



发送的消息被同时接收。

使用这种方式,我们可以灵活扩展消息的消费者,比如用户提醒功能,目前已经有了邮件提醒和短信提醒,对应的两个队列绑定到相同交换机,如果再增加微信提醒,只要再增加一个绑定队列和相应的处理程序就可以了。

Direct模式和RouteKey



在Fanout模式下,我们将消息发送到订阅消息的所有队列中,如果我们希望选择性地向队列发送消息,可以使用Direct模式,根据不同的RouteKey向不同的队列发送消息。

我们建立三个控制台程序程序模拟一个发送方和两个接收方,项目的创建方法同上,代码如下:

发送:

using RabbitMQ.Client;
using System.Text; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare("directdemo.exchange", ExchangeType.Direct, true, false, null); Console.WriteLine("输入需要传输的消息,输入Exit退出");
var message = Console.ReadLine();
while (message != "Exit")
{
Console.WriteLine("输入RouteKey");
var routekey = Console.ReadLine();
var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "directdemo.exchange",
routingKey: routekey,
basicProperties: null,
body: body);
Console.WriteLine(" 发送消息 {0} Routekey {1}", message,routekey);
message = Console.ReadLine();
}
} Console.WriteLine("按回车退出");
Console.ReadLine();

接收1:

using RabbitMQ.Client;
using System.Text;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{ channel.ExchangeDeclare(exchange: "directdemo.exchange",
type: ExchangeType.Direct, durable: true); channel.QueueDeclare(queue: "log_que",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.QueueBind(queue: "log_que", exchange: "directdemo.exchange",
routingKey: "log");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body.ToArray());
Console.WriteLine("收到消息 {0}", message);
};
channel.BasicConsume(queue: "log_que",
autoAck: true,
consumer: consumer); Console.WriteLine(" 按回车退出");
Console.ReadLine();
}

接收2:

using RabbitMQ.Client;
using System.Text;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{ channel.ExchangeDeclare(exchange: "directdemo.exchange",
type: ExchangeType.Direct, durable: true); channel.QueueDeclare(queue: "email_que",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.QueueBind(queue: "email_que", exchange: "directdemo.exchange",
routingKey: "email");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body.ToArray());
Console.WriteLine("收到消息 {0}", message);
};
channel.BasicConsume(queue: "email_que",
autoAck: true,
consumer: consumer); Console.WriteLine(" 按回车退出");
Console.ReadLine();
}

上面的代码中,关键是队列绑定:

   channel.QueueBind(queue: "email_que", exchange: "directdemo.exchange",
routingKey: "email");

这句话将queue、exchange和routingKey绑定在一起。运行效果如下:

Topic 模式

前面的Direct模式中,RouteKey是固定的,Topic模式引入了通配符,RouteKey可以是符合表达式的任何字符串。

  • 通配符“*”,代表一个字符
  • 通配符“#”,代表0或多个字符

仔细研究上面的规则,会发现Topic模式可以代替Direct和Fanout,如果RouteKey被设置为“#”,就是队列可以接收任何消息,这与Fanout模式相同,如果RouteKey中没有通配符,则和使用Direct模式的效果相同。

现在我们编写Topic模式的发送和接收,代码如下:

Topic模式发送:

using RabbitMQ.Client;
using System.Text; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.ExchangeDeclare("topicdemo.exchange", ExchangeType.Topic, true, false, null); Console.WriteLine("输入需要传输的消息,输入Exit退出");
var message = Console.ReadLine();
while (message != "Exit")
{
Console.WriteLine("输入RouteKey");
var routekey = Console.ReadLine();
var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "topicdemo.exchange",
routingKey: routekey,
basicProperties: null,
body: body);
Console.WriteLine(" 发送消息 {0} Routekey {1}", message, routekey);
message = Console.ReadLine();
}
} Console.WriteLine("按回车退出");
Console.ReadLine();

Topic模式接收:

using RabbitMQ.Client;
using System.Text;
using RabbitMQ.Client.Events; var factory = new ConnectionFactory()
{
HostName = "127.0.0.1",
UserName = "admin",
Password = "admin",
VirtualHost = "my_vhost"
};
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{ channel.ExchangeDeclare(exchange: "topicdemo.exchange",
type: ExchangeType.Topic, durable: true); channel.QueueDeclare(queue: "topic_que",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.QueueBind(queue: "topic_que", exchange: "topicdemo.exchange",
routingKey: "#.log");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body.ToArray());
Console.WriteLine("收到消息 {0}", message);
};
channel.BasicConsume(queue: "topic_que",
autoAck: true,
consumer: consumer); Console.WriteLine(" 按回车退出");
Console.ReadLine();
}

我们设置的RouteKey是"#.log",也就是匹配这个表达式的RouteKey的消息会被接收到:

到这里RabbitMQ常用的几种模式都介绍了,最后说一点代码中的细节,在发送方和接收方代码中,有重复的queue或者exchange声明,比如:

    channel.QueueDeclare(queue: "mymessage",
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);

这些代码让人感到有些困惑,似乎每次都需要声明,而实际上是只要存在相关的queue或者exchange,这些代码就不再起作用。之所以在发送方和接收方都包含这些代码,是因为不知道是否存在相关的queue或exchange,也不知道谁先启动,避免出错。如果在RabbitMQ的Web管理页面预先手工创建了相应的queue或者exchange,这些代码是可以去掉的。

本文代码可以从github下载:https://github.com/zhenl/ZL.RabbitMQ.Demo

C# RabbitMQ的使用的更多相关文章

  1. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  2. RabbitMq应用二

    在应用一中,基本的消息队列使用已经完成了,在实际项目中,一定会出现各种各样的需求和问题,rabbitmq内置的很多强大机制和功能会帮助我们解决很多的问题,下面就一个一个的一起学习一下. 消息响应机制 ...

  3. 如何优雅的使用RabbitMQ

    RabbitMQ无疑是目前最流行的消息队列之一,对各种语言环境的支持也很丰富,作为一个.NET developer有必要学习和了解这一工具.消息队列的使用场景大概有3种: 1.系统集成,分布式系统的设 ...

  4. RabbitMq应用一的补充(RabbitMQ的应用场景)

    直接进入正题. 一.异步处理 场景:发送手机验证码,邮件 传统古老处理方式如下图 这个流程,全部在主线程完成,注册->入库->发送邮件->发送短信,由于都在主线程,所以要等待每一步完 ...

  5. RabbitMq应用一

    RabbitMq应用一 RabbitMQ的具体概念,百度百科一下,我这里说一下我的理解,如果有少或者不对的地方,欢迎纠正和补充. 一个项目架构,小的时候,一般都是传统的单一网站系统,或者项目,三层架构 ...

  6. 缓存、队列(Memcached、redis、RabbitMQ)

    本章内容: Memcached 简介.安装.使用 Python 操作 Memcached 天生支持集群 redis 简介.安装.使用.实例 Python 操作 Redis String.Hash.Li ...

  7. 消息队列性能对比——ActiveMQ、RabbitMQ与ZeroMQ(译文)

    Dissecting Message Queues 概述: 我花了一些时间解剖各种库执行分布式消息.在这个分析中,我看了几个不同的方面,包括API特性,易于部署和维护,以及性能质量..消息队列已经被分 ...

  8. windows下 安装 rabbitMQ 及操作常用命令

    rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.它遵循Mozilla Public License开源协议,采用 Erlang 实现的工业级的消息队列(MQ)服务器,Rab ...

  9. RabbitMQ + PHP (三)案例演示

    今天用一个简单的案例来实现 RabbitMQ + PHP 这个消息队列的运行机制. 主要分为两个部分: 第一:发送者(publisher) 第二:消费者(consumer) (一)生产者 (创建一个r ...

  10. RabbitMQ + PHP (二)AMQP拓展安装

    上篇说到了 RabbitMQ 的安装. 这次要在讲案例之前,需要安装PHP的AMQP扩展.不然可能会报以下两个错误. 1.Fatal error: Class 'AMQPConnection' not ...

随机推荐

  1. 【leetocde】922. Sort Array By Parity II

    Given an array of integers nums, half of the integers in nums are odd, and the other half are even.  ...

  2. git pull、git fetch、git merge、git rebase的区别

    一.git pull与git fetch区别 1.两者的区别       两者都是更新远程仓库代码到本地. git fetch相当于是从远程获取最新版本到本地,不会自动merge. 只是将远程仓库最新 ...

  3. Git的使用-一个分支完全替换另一个分支

    之前公司git分支混乱,今天花时间整理了一下,在合并分支的时候遇到一个问题: 一个很久没有拉取远程代码的分支与master分支合并时,出现冲突之外,还会丢失文件,很头疼,然后找到了下面的方法,可以直接 ...

  4. lvm 创建扩展

    fdisk /dev/sdgnpt8ewpartprobepvcreate /dev/sdg1vgcreate multibank /dev/sdg1lvcreate -l +100%FREE -n ...

  5. Oracle中IS TABLE OF的使用

    IS TABLE OF :指定是一个集合的表的数组类型,简单的来说就是一个可以存储一列多行的数据类型. INDEX BY BINARY_INTEGER:指索引组织类型 BULK COLLECT :指是 ...

  6. HUD总结

    HUD 指示器/HUD/遮盖/蒙板 半透明的指示器如何实现 指示器的alpha = 1.0; 指示器的背景色是半透明的 1. 创建颜色 直接创建对应的颜色 + (UIColor *)blackColo ...

  7. 【Linux】【Services】【SaaS】Docker+kubernetes(2. 配置NTP服务chrony)

    1. 简介 1.1. 这次使用另外一个轻量级的NTP服务,chrony.这是openstack推荐使用的ntp服务. 1.2. 官方网站:https://chrony.tuxfamily.org/ 2 ...

  8. java实现文件压缩

    java实现文件压缩:主要是流与流之间的传递 代码如下: package com.cst.klocwork.service.zip; import java.io.File; import java. ...

  9. XML解析器

    1.非验证解析器 检查文档格式是否良好,如用浏览器打开XML文档时,浏览器会进行检查,即格式是否符合XML(可拓展标记语言)基本概念. 2.验证解析器 使用DTD(Document Type Defi ...

  10. 程序员Meme 第02期