3.3 发送消息
如果要发送一个消息,可以使用Chanel类的BasicPulish方法,如下所示:
string input;
do
{
input = Console.ReadLine();
var sendBytes = Encoding.UTF8.GetBytes(input);
//发布消息
channel.BasicPublish(exchangeName, routeKey, null, sendBytes);
} while (input.Trim().ToLower() != "exit");
channel.Close();
connection.Close();
下面这行代码发送了一条消息,投递模式(delivery mode)设置为2,即消息会被持久化(即存入磁盘)在服务器中。同时这条消息的优先级(priority)设置为1,如下所示 可以设置消息属性:
var basicConsumer= channel.CreateBasicProperties();
basicConsumer.Priority = 1;
basicConsumer.UserId = "hidden";
basicConsumer.DeliveryMode = 2;
channel.BasicPublish(exchangeName, routeKey, basicConsumer,sendBytes);
还可以发送一条带有过期时间(expiration)的消息,如下:
var basicConsumer= channel.CreateBasicProperties();
basicConsumer.Expiration = "60000";
对应的具体参数解释如下所述:
exchange:交换器的名称,指明消息需要发送到哪个交换器中,如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。
routingkey:路由键,交换器根据路由键将消息存储到相应的队列之中。
byte[] body:消息体(payload),真正需要发送的消息。
mandatory:下篇讲解
Props消息的基本属性集,其中包含14个属性成员。如下所示:
3.4 消费消息
RabiitMQ的消费模式有两种:推(push) 和拉(pull)模式,推模式采用Basic.Consume进行消费, 而拉模式则是调用Basic.Get进行消费。要获得高性能和并发高需选择推模式。
3.4.1推模式
//创建连接
var connection = factory.CreateConnection();
//创建通道
var channel = connection.CreateModel();
//事件基本消费者
var consumer = new EventingBasicConsumer(channel);
//接收到消息事件
consumer.Received += (ch, ea) =>
{
var boby = ea.Body;
var message = Encoding.UTF8.GetString(boby.ToArray());
Console.WriteLine($"收到消息: {message}");
//确认消费
channel.BasicAck(ea.DeliveryTag, true);
};
//启动消费者 设置为手动应答消息,这里设置autoAck为false是非常必要的,防止不必要的丢失。
channel.BasicConsume(queueName, false, consumer);
Console.WriteLine("消费者已启动");
BasicConsume方法有四个重载,如下所示:
public static string BasicConsume(this IModel model, IBasicConsumer consumer, string queue, bool autoAck = false, string consumerTag = "", bool noLocal = false, bool exclusive = false, IDictionary<string, object> arguments = null);
public static string BasicConsume(this IModel model, string queue, bool autoAck, IBasicConsumer consumer);
public static string BasicConsume(this IModel model, string queue, bool autoAck, string consumerTag, IBasicConsumer consumer);
public static string BasicConsume(this IModel model, string queue, bool autoAck, string consumerTag, IDictionary<string, object> arguments, IBasicConsumer consumer);
参数说明:
queue:队列名称
autoACK:设置是否自动确认,建设设成false,即不自动确认.
consumerTag:消费者标签,用来区分多个消费者。
noLocal :设置为true则表示不能将同一个Connection中生产者发送的消息传送给这个Connection中的消费者
exclusive:设置是否排他;
arguments:设置消费者的其他参数。
对于消费者客户端来说,重写事件也是十分方便 的,更复杂的客户端会重写更多的方法,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/23a965325d34cab401c403e440fad791.png)
比如HandModelShutDown应该是在Model(Channel) 关闭时调用。每个Channel都拥有自己独立的线程,最常见的做法是一个Channel对应一个消费者,这也意味着消费者彼此之间没有任何关联。当然也可以在一个Channel中维持多个消费者,但要注意的一个问题,如果Channel中的一个消费者一直在运行,那么其它消费者要等待。
3.4.2 拉模式
这里讲一下拉模式的消费方式,通过channel.BasicGet方法可以单条地获取消息,返回一个BasicGetResult,没有重载就一个方法
//
// 摘要:
// Retrieve an individual message, if one is available; returns null if the server
// answers that no messages are currently available. See also RabbitMQ.Client.IModel.BasicAck(System.UInt64,System.Boolean).
[AmqpMethodDoNotImplementAttribute(null)]
BasicGetResult BasicGet(string queue, bool autoAck);
其中queue代表队列的名称,如果设置autoAck为False,那么同样需要调用Channel.basicAck来确认消息已被成功接收。
注意要点:
channel.BasicConsume将信道(Channel)置为接收模式,直到取消队列的订阅为止,在接收模式期间,RabiitMQ会不断地推送消息给消费者,当然推送消息的个数还是会受到Basic.Qos的限制。 如果只想从队列获利单条消息而不是持续订阅,建议还是使用channel.BasicGet进行消费。但是不能将channel.BasicGet放在一个循环里来代替channel.BasicConsume,这样严重影响Rabbitmq的性能。如果要实现高吞吐量,消费者理应使用channel.BasicConsume.
3.5 消费端的确认与拒绝
为了保证消息从队列可靠地达到消费者,RabbitMQ提供了消息确认机制(message acknowledgement)。消费者在订阅队列时,可以指定autoAck参数,当autoAck等于false时,RabbitMQ会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除),当autoAck等于true时,Rabbitmq会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费了这些消息。
当autoAck参数置为False,如果rabbitmq一直没有收到消费者确认信号,并且消费此消息的消费者已经断开连接,则rabbitmq会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。
Rabbitmq不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开,这么设计的原因是rabbitmq允许消费者消费一条消息的时间可以很久很久。
rabbitmq的web管理平台上可以看到当前队列中"ready"状态和"Unacked"状态的消息数,分别对应上文中的等待投递给消费者的消息数和已经投递给消费者但是未收到确认信号的消息数,如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/fad4738b50e449fe5d9ae7fa659cdc35.png)
在消费者接收到消息后,如果想明确拒绝当前的消息而不是确认,可以调用BasicReject这个命令,如下所示:
void BasicReject(ulong deliveryTag, bool requeue);
其中deliveryTag可以看作消息的编号,它是一个64位的长整型值,如果requeue参数设置为true,则rabbitmq会重新将这条消息存入队列,以便可以发送给下一个订阅的消费者,如果requeue参数设置为false,则RabbitMQ立即会把消息从队列中移除,而不会把它发送给新的消费者。
channel.BasicReject命令一次只能拒绝一条消息,如果想要批量拒绝消息,则可以使用BasicNack,方法如下所示:
void BasicNack(ulong deliveryTag, bool multiple, bool requeue);
方法multiple参数设置为false,则表示拒绝编号为 deliveryTag的这一条消息,这个时候basicNack和basicReject方法一样;如果设置为true则表示拒绝deliverTag编号之前所有未被当前消费者确认的消息。
使用channel.BasicRecover方法具备可重入队列的特性,方法如下所示 :
void BasicRecover(bool requeue);
该方法用来请求Rabbitmq重新发送还未确认的消息,如果requeue参数设置为true,则未被确认的消息会被重新加入到队列中,这样对于同一条消息来说,可能会被分配给与之前不同的消费者,如果requeue参数设置为false,那么同一条消息会被分配给与之前相同的消费者。
3.6关闭连接
在应用程序使用完之后,需要关闭连接,释放资源:
channel.close();
conn.close();
显示地关闭channel是个好习惯,但这不是必须的,在connection关闭的时候,channel也会自动关闭。