使用RabbitMQ创建AMQP示例应用程序

使用RabbitMQ创建AMQP示例应用程序

RabbitMQ提供了一个名为RabbitMQ.Client的.NET客户机库。在这里可以找到关于客户端的文档。
与所有新技术一样,文档是空白的,API是原始的和无组织的,几乎所有的东西都可以从一个对象——IModel中访问。
首先要做的事情之一是:

  1. 通过围绕最常见的用例组织功能,使其更容易使用。

    • 发布消息
    • 接受消息
  2. 将声明与功能分开

    • 创建exchange(交换机),queue(队列)和绑定过程是独立的。
    • 发布和接受消息的过程。
  3. 使用app.config文件进行配置。

在我们设计上面描述的简化api之前,让我们简要地介绍一下RabbitMQ的功能,
客户提供下面是一些示例代码,以突出RabbitMQ的方式。
客户端API允许我们与RabbitMQ服务器进行交互。

//Creating Connections
RabbitMQ.Client.ConnectionFactory factory = new RabbitMQ.Client.ConnectionFactory();
factory.Endpoint = new AmqpTcpEndpoint(server);
factory.UserName = userName;
factory.Password = password;

IConnection Connection = factory.CreateConnection();

要连接到RabbitMQ服务器,必须在客户机和服务器之间建立连接,
如上所示。第7行创造了我们将在下面的例子中使用的连接。

连接并不是我们要用来与服务器通信的东西,
相反,我们使用的是连接对象为我们创建的专用通信通道IModel对象。

IModel是客户端和代理之间的通信通道,可以建立多个通道。
RabbitMQ的一个奇怪之处是IModel无法在线程之间共享对象,
我们需要确保在设计应用程序时考虑到这一点。
下一节将介绍通道对象提供的一些最常用的功能。

using (IModel channel = Connection.CreateModel())
{
    ... // Object declaration //
   channel.ExchangeDeclare( /* parameters */);
   channel.QueueDeclare( /* parameters */);
   channel.QueueBind( /* parameters */);

// Publishing 
   channel.BasicPublish( /* object parameters */);

// Synchronous receiving
   channel.BasicGet( /* parameters */);

// Acknowledging a message
   channel.BasicAck( /* parameters */);

// Reject a message
   channel.BasicReject( /* parameters */);

// Requeue a message
   channel.BasicReject( /* parameters - make sure to set requeue = true */);
}

奇怪的是,Requeueing消息使用了该BasicReject(…)方法。
BasicReject有一个requeue参数,通过设置为true,消息被重新排队。

创建专用AMQP App配置部分

当我们使用RabbitMQ时,我们必须配置服务器以适当地接收和分发消息给客户端,
以及配置我们的应用程序以从适当的交换或队列发送或接收消息。
最简单的方法是使用配置文件来执行此操作,
这也允许我们在应用程序部署到各种环境时轻松更改这些值。
我选择为RabbitMQ创建一个专用配置部分,因为需要配置很多,
我想将所有RabbitMQ配置元素保存在一起。结果如下所示
(代码可以在CodePlex项目中找到:codeplex_link)

<configuration>
  <configsections>
    <sectiongroup name="AMQPConnection">
      <section name="ConnectionSettings" 
      type="Sample.Configuration.AMQP.Config.ConnectionSection, 
      Sample.Configuration.AMQP" />
    </sectiongroup>
    <sectiongroup name="AMQPAdmin">
      <section name="AMQPObjectsDeclaration" 
      type="Sample.Configuration.AMQP.Config.AMQPObjectsDeclarationSection, 
      Sample.Configuration.AMQP" allowlocation="true" 
      allowdefinition="Everywhere" />
    </sectiongroup>
  </configsections>
  <amqpadmin>
    <amqpobjectsdeclaration>
      <exchangelist>
        <add name="orders" type="topic" 
        durable="true" autodelete="false" />
      </exchangelist>
      <queuelist>
        <add name="uk_orders" durable="true" 
        autodelete="false" />
      </queuelist>
      <bindinglist>
        <add subscriptionkey="order.uk.#" 
        queue="uk_orders" exchange="orders" />
      </bindinglist>
    </amqpobjectsdeclaration>
  </amqpadmin>
  <amqpconnection>
    <connectionsettings>
      <connection name="connection" 
      username="guest" server="devserver-rr1" 
      password="guest" />
      <publisher exchange="orders" />
      <asyncreceiver queue="uk_orders" maxthreads="4" />
    </connectionsettings>
  </amqpconnection>
  </configuration>

按行描述配置文件

  • 4,7:将配置节解释器映射到项目中的相应类。每个XML元素代表一个对象,必须进行解释
  • 13.创建一个称为持久的订单的主题交换(意味着它将持续服务器重新启动)并且不是自动删除
  • (如果autodelete标志设置为true,则在所有客户端完成发布后将删除交换)。
  • 16.创建一个名为uk_orders 的队列来表示英国客户的所有订单。队列设置为持久而不是自动删除
    1. 使用uk_orders订阅密钥将uk_orders队列绑定到orders交换order.uk.#
    2. 这样,所有以order.uk 开头的订单都将以该队列结束
  • 25:配置与服务器的连接字符串。
  • 26 ,. 配置发布者将消息发布到订单交换
  • 27.配置异步接收器使用4个线程在uk_orders它们到达时从uk_orders队列中获取订单。

配置部分可以接受每种类型的AMQP对象(交换,队列和绑定)的列表。

RabbitAdmin

那么在用于创建交换,队列及其绑定时,它会是什么样子?

namespace Sample.Configuration.AMQP.Admin
{
    public class RabbitAdmin
    {
        internal static void InitializeObjects(IConnection Connection)
        {
            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            var objects = config.GetSection
            ("AMQPAdmin/AMQPObjectsDeclaration") as AMQPObjectsDeclarationSection;
            if (objects != null)
            {
                Parallel.For(0, objects.ExchangeList.Count, i =>
                {
                    using (IModel channel = Connection.CreateModel())
                    {
                        var exchange = objects.ExchangeList[i];
                        channel.ExchangeDeclare(exchange.Name, 
                        exchange.Type.ToString(), exchange.Durable, exchange.AutoDelete, null);
                    }
                });
                Parallel.For(0, objects.QueueList.Count, i =>
                {
                    using (IModel channel = Connection.CreateModel())
                    {
                        var queue = objects.QueueList[i];
                        channel.QueueDeclare(queue.Name, queue.Durable, 
                        queue.Exclusive, queue.AutoDelete, null);
                    }
                });
                Parallel.For(0, objects.BindingList.Count, i =>
                {
                    using (IModel channel = Connection.CreateModel())
                    {
                        var binding = objects.BindingList[i];
                        channel.QueueBind(binding.Queue, 
                        binding.Exchange, binding.SuscriptionKey);
                    }
                });
            }
      }
}

在RabbitAdmin类创建的交流,队列及其绑定。
它接受一个连接对象并使用它与RabbitMQ服务器进行通信。
在InitializeObjects使用平行for循环创建每种类型的对象。
首先创建交换,然后将队列绑定声明为最后一个,因为它们需要队列和交换来绑定两者。

GatewayFactory

在GatewayFactory创建与服务器的连接,调用RabbitAdmin声明中的所有对象,
并提供方法来创建发布者和听众同步助手对象。消息传递给发布者并从队列接收,
消息对象是一个非常简单的对象,它包含一个标题和一个正文。


/// <summary>
/// The object encapsulating all the common message properties
/// transmitted to and received from the message bus.
/// </summary>
public class Message {
    public IBasicProperties Properties { get; set;}
    public byte[] Body { get; set; }
    public string RoutingKey { get; set; }
}

正文是一个简单的字节数组,您的内部数据结构被序列化。
为了能够一般地处理数据结构和消息之间的转换,包中包含以下委托和接口。


namespace Sample.Configuration.AMQP.Gateway
{
    public delegate Message ConvertToMessage(IModel channel, object packetToSend);

    public interface IConvertToMessage {
        Message ConvertObjectToMessage( IModel channel, object packetToSend);
    }
}

系统的客户端负责从其数据结构转换为消息结构。

Publishing


using (var gf = new GatewayFactory())
{
    var mc = new Sample.Configuration.AMQP.Gateway.Converter.StringToMessageConverter();
    var publisher = gf.GetPublisher(mc.ConvertObjectToMessage);
    publisher.Publish("Hello world");
}

随着配置的到位,出版是一个四线的事情。
只需创建一个GatewayFactory,在内部读取配置文件并设置所有内容。
然后请求发布者并传递objectToMessage转换处理程序。
该库附带了一个默认string的消息转换器,它在第5行中使用,对XML文档很有用。
否则,必须创建自定义转换器。
为了让您了解如何创建转换器,让我们来看看string转换器。


public class StringToMessageConverter : IConvertToMessage
{
    public static readonly string PLAIN_TEXT = "text/plain";
    public const string _defaultCharSet = "utf-8";
    public string CharSet { get; set; }
    public StringToMessageConverter()
    {
        CharSet = _defaultCharSet;
    }
    public virtual Message ConvertObjectToMessage
    (RabbitMQ.Client.IModel channel, object packetToSend)
    {
        var properties = channel.CreateBasicProperties();
        var bytes = Encoding.GetEncoding(CharSet).GetBytes((string)packetToSend);
        properties.ContentType = PLAIN_TEXT;
        properties.ContentEncoding = CharSet;
        return new Message()
        { Body = bytes, Properties = properties, RoutingKey = string.Empty };
    }
}

转换为消息主要是将需要发送的任何内容转换为位数组,让接收者知道内容是什么并设置RoutingKey。

The Asynchronous Receiver (异步接收)

异步接收消息就像发布一样简单。接收方必须将消息的状态与服务器通信 - 它可以被确认,拒绝或重新排队。

class Program
{
    static void Main(string[] args)
    {
        var mp = new MessageProcessor();
        using (var cf = new GatewayFactory())
        {
            cf.GetAsyncReceiver(mp.ConsumeMessage);
        }
    }
}

class MessageProcessor : IMessageConsumer
{
    public void ConsumeMessage
    (Message message, RabbitMQ.Client.IModel channel, DeliveryHeader header)
    {
        try
        {
            var str = ConvertFromMessageToString(message);
            channel.BasicAck(header.DeliveryTag, false);
        }
        catch (Exception ex)
        {
            channel.BasicReject(header.DeliveryTag, true);
        }
    }
    public string ConvertFromMessageToString(Message message)
    {
        var content = string.Empty;
        if (message.Properties.ContentType == StringToMessageConverter.PLAIN_TEXT)
        {
            var encoding = Encoding.GetEncoding
            (message.Properties.ContentEncoding ?? "utf-8");
            var ms = new MemoryStream(message.Body);
            var reader = new StreamReader(ms, encoding, false);
            content = reader.ReadToEnd();
        }
        return content;
    }
}

在第8行,我们将消息处理程序传递给异步接收器。
异步接收器是多线程的,并且处理程序应该是无状态的,以便它可以被多个线程同时使用。
响应服务器是通过IModel对象完成的。Delivery标头包含a deliverytag和a flag,表示这是否是第一次传递。
可以在第19行和第20行之间完成对消息有用的东西。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云策数据

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值