https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html
https://www.rabbitmq.com/dotnet-api-guide.html
The RabbitMQ .NET client is an implementation of an AMQP 0-9-1 client library for C# (and, implicitly, other .NET languages).
https://github.com/rabbitmq/rabbitmq-dotnet-client
RabbitMQ .NET client for .NET Standard 2.0+ and .NET 4.6.1+ https://www.rabbitmq.com/dotnet.html
This repository contains source code of the RabbitMQ .NET client. The client is maintained by the RabbitMQ team at VMware.
This guide covers RabbitMQ .NET/C# client and its public API. It assumes that the most recent major version of the client is used and the reader is familiar with the basics.
Key sections of the guide are:
- Important interfaces and classes in the public API
- Connecting to RabbitMQ
- Connection and Channel Lifespan
- Using Exchanges and Queues
- Publishing Messages
- Consuming Using a Subscription and Consumer Memory Safety
- Concurrency Considerations and Safety
- Automatic Recovery From Network Failures
Major namespaces, interfaces and classes
The client API is closely modelled on the AMQP 0-9-1 protocol model, with additional abstractions for ease of use.
An API reference is available separately.
The core API interfaces and classes are defined in the RabbitMQ.Client namespace:
using RabbitMQ.Client;
The core API interfaces and classes are
- IModel: represents an AMQP 0-9-1 channel, and provides most of the operations (protocol methods)
- IConnection: represents an AMQP 0-9-1 connection
- ConnectionFactory: constructs IConnection instances
- IBasicConsumer: represents a message consumer
Other useful interfaces and classes include:
- DefaultBasicConsumer: commonly used base class for consumers
Public namespaces other than RabbitMQ.Client include:
- RabbitMQ.Client.Events: various events and event handlers that are part of the client library, including EventingBasicConsumer, a consumer implementation built around C# event handlers.
- RabbitMQ.Client.Exceptions: exceptions visible to the user.
All other namespaces are reserved for private implementation detail of the library, although members of private namespaces are usually made available to applications using the library in order to permit developers to implement workarounds for faults and gaps they discover in the library implementation. Applications cannot rely on any classes, interfaces, member variables etc. that appear within private namespaces remaining stable across releases of the library.
Connecting to RabbitMQ
Before an application can use RabbitMQ, it has to open a connection to a RabbitMQ node. The connection then will be used to perform all subsequent operations. Connections are meant to be long-lived. Opening a connection for every operation (e.g. publishing a message) would be very inefficient and is highly discouraged.
To open a connection with the .NET client, first instantiate a ConnectionFactory and configure it to use desired hostname, virtual host, credentials, TLS settings, and any other parameters as needed.
Then call the ConnectionFactory.CreateConnection() method to open a connection. Successful and unsuccessful client connection events can be observed in server logs.
The following two code snippets connect to a RabbitMQ node on hostName:
ConnectionFactory factory = new ConnectionFactory(); // "guest"/"guest" by default, limited to localhost connections factory.UserName = user; factory.Password = pass; factory.VirtualHost = vhost; factory.HostName = hostName; IConnection conn = factory.CreateConnection();
ConnectionFactory factory = new ConnectionFactory(); factory.Uri = "amqp://user:pass@hostName:port/vhost"; IConnection conn = factory.CreateConnection();
Since the .NET client uses a stricter更严格的 interpretation of the AMQP 0-9-1 URI spec than the other clients, care must be taken when using URIs. In particular, the host part must not be omitted遗漏 and virtual hosts with empty names are not addressable.
All factory properties have default values. The default value for a property will be used if the property remains unassigned prior to creating a connection:
Property | Default Value |
Username | "guest" |
Password | "guest" |
Virtual host | "/" |
Hostname | "localhost" |
Port | 5672 for regular ("plain TCP") connections, 5671 for connections with TLS enabled |
Note that user guest can only connect from localhost by default. This is to limit well-known credential use in production systems.
The IConnection interface can then be used to open a channel:
IModel channel = conn.CreateModel();
The channel can now be used to send and receive messages, as described in subsequent sections.
Just like connections, channels are meant to be long-lived. Opening a new channel for every operation would be highly inefficient is highly discouraged. Channels, however, can have a shorter life span than connections. For example, certain protocol errors will automatically close channels. If applications can recover from them, they can open a new channel and retry the operation.
This is covered in more detail in the Channel guide as well as other guides such as Consumer Acknowledgements.
Disconnecting from RabbitMQ
To disconnect, simply close the channel and the connection:
channel.Close(); conn.Close();
Disposing channel and connection objects is not enough, they must be explicitly closed with the API methods from the example above.
Note that closing the channel may be considered good practice, but isn’t strictly necessary here - it will be done automatically anyway when the underlying connection is closed.
Client disconnection events can be observed in server node logs.
Connection and Channel Lifespan
Connections are meant to be long-lived. The underlying protocol is designed and optimized for long running connections. That means that opening a new connection per operation, e.g. a message published, is unnecessary and strongly discouraged as it will introduce a lot of network roundtrips and overhead.
Channels are also meant to be long-lived but since many recoverable protocol errors will result in channel closure, channel lifespan could be shorter than that of its connection. Closing and opening new channels per operation is usually unnecessary but can be appropriate. When in doubt, consider reusing channels first.
Channel-level exceptions such as attempts to consume from a queue that does not exist will result in channel closure. A closed channel can no longer be used and will not receive any more events from the server (such as message deliveries). Channel-level exceptions will be logged by RabbitMQ and will initiate a shutdown sequence for the channel (see below).
Using Exchanges and Queues
Client applications work with exchanges and queues, the high-level building blocks of the protocol. These must be "declared" before they can be used. Declaring either type of object simply ensures that one of that name exists, creating it if necessary.
Continuing the previous example, the following code declares an exchange and a queue, then binds them together.
channel.ExchangeDeclare(exchangeName, ExchangeType.Direct); channel.QueueDeclare(queueName, false, false, false, null); channel.QueueBind(queueName, exchangeName, routingKey, null);
This will actively declare the following objects:
- a non-durable, non-autodelete exchange of "direct" type
- a non-durable, non-autodelete, non-exclusive queue
The exchange can be customised by using additional parameters. The above code then binds the queue to the exchange with the given routing key.
Many channel API (IModel) methods are overloaded. The convenient short form of ExchangeDeclare uses sensible defaults. There are also longer forms with more parameters, to let you override these defaults as necessary, giving full control where needed.
This "short version, long version" pattern is used throughout the API.
Publishing Messages
To publish a message to an exchange, use IModel.BasicPublish as follows:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!"); channel.BasicPublish(exchangeName, routingKey, null, messageBodyBytes);
For fine control, you can use overloaded variants to specify the mandatory flag, or specify messages properties:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!"); IBasicProperties props = channel.CreateBasicProperties(); props.ContentType = "text/plain"; props.DeliveryMode = 2; channel.BasicPublish(exchangeName, routingKey, props, messageBodyBytes);
This sends a message with delivery mode 2 (persistent) and content-type "text/plain". See the definition of the IBasicProperties interface for more information about the available message properties.
In the following example, we publish a message with custom headers:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!"); IBasicProperties props = channel.CreateBasicProperties(); props.ContentType = "text/plain"; props.DeliveryMode = 2; props.Headers = new Dictionary<string, object>(); props.Headers.Add("latitude", 51.5252949); props.Headers.Add("longitude", -0.0905493); channel.BasicPublish(exchangeName, routingKey, props, messageBodyBytes);
Code sample below sets a message expiration:
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!"); IBasicProperties props = channel.CreateBasicProperties(); props.ContentType = "text/plain"; props.DeliveryMode = 2; props.Expiration = "36000000" channel.BasicPublish(exchangeName, routingKey, props, messageBodyBytes);
Retrieving Messages By Subscription ("push API")
consumer和producer通过host访问的是同一个RabbitMQ,打开channel之后producer进行BasicPublish操作,而consumer进行BasicConsume操作。
The recommended and most convenient way to receive messages is to set up a subscription using the IBasicConsumer interface. The messages will then be delivered automatically as they arrive, rather than having to be requested proactively主动地.
One way to implement a consumer is to use the convenience class EventingBasicConsumer, which dispatches deliveries and other consumer lifecycle events as C# events:
var consumer = new EventingBasicConsumer(channel); consumer.Received += (ch, ea) => { var body = ea.Body.ToArray(); // copy or deserialise the payload // and process the message // ... channel.BasicAck(ea.DeliveryTag, false); }; // this consumer tag identifies the subscription // when it has to be cancelled String consumerTag = channel.BasicConsume(queueName, false, consumer);
Another option is to subclass DefaultBasicConsumer, overriding methods as necessary, or implement IBasicConsumer directly. You will generally want to implement the core method IBasicConsumer.HandleBasicDeliver.
More sophisticated consumers will need to implement further methods. In particular, HandleModelShutdown traps channel/connection closure. Consumers can also implement HandleBasicCancelOk to be notified of cancellations.
The ConsumerTag property of DefaultBasicConsumer can be used to retrieve the server-generated consumer tag, in cases where none was supplied to the original IModel.BasicConsume call.
You can cancel an active consumer with IModel.BasicCancel:
channel.BasicCancel(consumerTag);
When calling the API methods, you always refer to consumers by their consumer tags, which can be either client- or server-generated as explained in the AMQP 0-9-1 specification document.