生产者通过指定一个 exchange 和 routingkey 把消息送达到某个队列中去,然后消费者监听队列,进行消费处理。但是在某些情况下,如果我们在发送消息时,当前的 exchange 不存在或者指定的 routingkey 路由不到,这个时候如果要监听这种不可达的消息,就要使用 return
项目建议先启动消费端,再启动生产端(因为我的交换机和队列都是在消费端创建的,交换机与队列绑定指定的路由也是在消费者端指定的,生产端并没有创建交换机,生产者想发送一条消息到MQ服务器,就必须要有交换机,因为只有交换机才能接收到消息,然后交换机把消息保存到Queue队列中,消息保存到队列的前提是队列与交换机进行了绑定,这个绑定的过程可能指定了RoutingKey路由,也可能没有指定路由(比如Fanout模式下就不需要指定路由))
假设你事先已经知道MQ服务器中已经有vhost001这个虚拟地址, vhost001这个虚拟地址下 有一个ex.user的交换机,与他绑定的Queue队列中有一个名字为queue.user的队列,这个交换机与队列的路由是routing.user ,那么你先启动生产者也可以。因为这些指定名称的交换机,队列,路由都已经事先在RabbitMq服务器上存在了。
ProducterApp:生产者端
假设MQ服务器中已经有vhost001这个虚拟地址
假设vhost001这个虚拟地址下 有一个ex.user的交换机
假设ex.user这个交换机与名字为queue.user的这个队列进行绑定了,他们绑定并指定的路由为 routing.user
那么下面这个生产者向MQ服务器发送消息是不可达的(因为我发消息的时候已经指定了这条消息往ex.user交换机中发,这个交换机使用routing.abc这个RoutingKey去找与这个ex.user交换机绑定的队列,而MQ服务器中ex.user交换机发现与之绑定的队列中没有routing.abc这条路由,所以消息不可达),这条不可达的消息将会被 EventHandler<BasicReturnEventArgs>事件所监听到。(前提是在外面发送消息的时候,将mandatory:设为true了,否则这条不可达的消息将被删除)
假设ex.user这个交换机与名字为queue.abc的这个队列进行绑定了,他们绑定并指定的路由为 routing.abc
那么下面这个生产者向MQ服务器发送消息是可达的。
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace ProducterApp
{
class Program
{
/// 连接配置
/// </summary>
private static readonly ConnectionFactory rabbitMqFactory = new ConnectionFactory() //创建一个工厂连接对象
{
HostName = "192.168.31.30",
UserName = "admin",
Password = "admin",
VirtualHost = "/vhost001",
Port = 5672,
AutomaticRecoveryEnabled = true,//网络故障自动连接恢复
};
/// <summary>
/// 路由名称
/// </summary>
const string ExchangeName = "ex.user";
const string routingKey = "routing.abc";
//队列名称
//const string QueueName = "queue010";
static void Main(string[] args)
{
using (IConnection conn = rabbitMqFactory.CreateConnection()) //创建一个连接
{
using (IModel channel = conn.CreateModel()) //创建一个Channel
{
IBasicProperties props = channel.CreateBasicProperties();
props.DeliveryMode = 2; //1:非持久化 2:持续久化 (即:当值为2的时候,我们一个消息发送到服务器上之后,如果消息还没有被消费者消费,服务器重启了之后,这条消息依然存在)
props.Persistent = true;
props.ContentEncoding = "UTF-8"; //注意要大写
//我们也可以设定自定义属性,把自定义的属性放到IBasicProperties的Headers中进行发送
var dir = new Dictionary<string, object>();
dir.Add("Name", "lily");//特别要注意,字符串传递过去的时候是实际是以byte[]数组的形式传递过去的
dir.Add("Age", 25);
props.Headers = dir;
//这个事件就是用来监听我们一些不可达的消息的内容的:比如某些情况下,如果我们在发送消息时,当前的exchange不存在或者指定的routingkey路由不到,这个时候如果要监听这种不可达的消息,就要使用 return
EventHandler<BasicReturnEventArgs> evreturn = new EventHandler<BasicReturnEventArgs>((o, basic) => {
Console.WriteLine(basic.ReplyCode); //消息失败的code
Console.WriteLine(basic.ReplyText); 描述返回原因的文本
Console.WriteLine(Encoding.UTF8.GetString(basic.Body)); //失败消息的内容
//在这里我们可能要对这条不可达消息做处理,比如是否重发这条不可达的消息呀,或者这条消息发送到其他的路由中呀,等等
System.IO.File.AppendAllText("d:/return.txt", "调用了Return;ReplyCode:" + basic.ReplyCode + ";ReplyText:" + basic.ReplyText + ";Body:" + Encoding.UTF8.GetString(basic.Body));
});
channel.BasicReturn += evreturn;
for (int i = 0; i < 5; i++) //发5条消息到MQ服务器
{
props.MessageId = Guid.NewGuid().ToString("N"); //设定这条消息的MessageId(每条消息的MessageId都是唯一的)
string msg = "你好,这是我的第" + i + "条消息;时间:" + DateTime.Now.ToString("yyyy:MM:dd");
var msgBody = Encoding.UTF8.GetBytes(msg); //发送的消息必须是二进制的
//记住:如果需要EventHandler<BasicReturnEventArgs>事件监听不可达消息的时候,一定要将mandatory设为true
channel.BasicPublish(exchange: ExchangeName, routingKey: routingKey, mandatory:true, basicProperties: props, body: msgBody);
}
Console.ReadKey();
}
}
}
}
}
CustomerApp:消费者端
特别注意:生产者向MQ服务器发送消息的可达与不可达,与消费者没有任何关系。我之所以把消费者端的代码放上来,仅仅只是我在消费者端创建了交换机,队列,还有交换机与队列绑定所指定的路由,从而确保MQ服务器有这些东西
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
using System.Threading;
namespace CustomerApp
{
class Program
{
/// <summary>
/// 连接配置
/// </summary>
private static readonly ConnectionFactory rabbitMqFactory = new ConnectionFactory()
{
HostName = "192.168.31.30",
UserName = "admin",
Password = "admin",
Port = 5672,
VirtualHost = "/vhost001",
};
/// <summary>
/// 路由名称
/// </summary>
const string ExchangeName = "ex.user";
//队列名称
const string QueueName = "queue.user";
const string routingKey = "routing.user"; //这里routingkey与消费端的routingkey不保持一致的原因就是要测试生产端的消息不可达,测试不可达所产生的事件调用
static void Main(string[] args)
{
using (IConnection conn = rabbitMqFactory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
//创建交换机
channel.ExchangeDeclare(ExchangeName, ExchangeType.Direct, durable: true, autoDelete: false, arguments: null);
//创建Queue队列
channel.QueueDeclare(QueueName, durable: true, autoDelete: false, exclusive: false, arguments: null);
//交换机与队列进行绑定,并指定了他们的路由
channel.QueueBind(QueueName, ExchangeName, routingKey: routingKey);
//创建一个消费者
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (o, basic) =>//通过订阅检索消息:这个订阅是一个EventHandler<BasicDeliverEventArgs>类型事件
{
var msgBody = basic.Body; //获取消息内容
var a = basic.ConsumerTag;
var b = basic.DeliveryTag;
var c = basic.Redelivered;
var e = basic.RoutingKey;
var f = basic.BasicProperties.Headers;
Console.WriteLine(string.Format("接收时间:{0},消息内容:{1}", DateTime.Now.ToString("HH:mm:ss"), Encoding.UTF8.GetString(msgBody)));
};
channel.BasicConsume(QueueName, autoAck: true, consumer: consumer);//第二个参数autoAck设为true为自动应答,false为手动ack
Console.ReadKey();
}
}
}
}
}