生產者通過指定一個 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();
}
}
}
}
}