RabbitMQ是做什么的?#
RabbitMQ可以类比现实生活中的邮政服务。
现实中邮件服务处理的是邮件,发件人写好信件投入邮箱,邮递员收取信件存入邮局,邮局根据信件地址,分配邮递员投递信件到指定地点。
RabbitMQ与邮政服务的主要区别是RabbitMQ处理的是消息(二进制数据块), 即消息的接收、存储、分发。
RabbitMQ中的主要概念#
消息生产者(producer)#
发送消息的程序
消息消费者(Consumer)#
等待接收消息的程序
消息队列#
RabbitMQ中的消息队列,就相当于邮政服务中的邮箱,所有通过RabbitMQ和你的应用程序发送接收的消息都存储在消息队列中,它是一个巨大的消息缓存,它的大小仅受服务器内存和硬盘空间的限制。
多个消息生产者可以通过同一个消息队列发送消息,多个消息消费者也可以通过同一个消息队列接收消息。
消息的生产者、消费者、消息队列不需要一定放置在同一个服务器中,现实中的大部分应用场景也不会允许他们放置在同一服务器中
第一个Hello World程序#
这里我们创建2个控制台程序,一个负责发送简单的 Hello World消息,一个负责输出接收到的消息,并在控制台打印。
流程图如下,P为生产者,C为消费者,中间的红色块是消息队列
创建程序#
使用.NET Core的命令行工具,创建2个控制台程序,一个命名为Send, 另外一个命名为Receive。
1
2
3
|
dotnet new console –name Send
dotnet new console –name Receive
|
安装RabbitMQ客户端程序集#
使用.NET Core的命令行工具,分别为2个控制台程序添加RabbitMQ客户端程序集
dotnet add package RabbitMQ.Client dotnet restore
编写消息发送程序#
首先引入一些命名空间
1
2
3
4
5
|
using
System;
using
RabbitMQ.Client;
using
System.Text;
|
然后在修改Main方法添加如下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var
factory =
new
ConnectionFactory() { HostName =
"localhost"
};
using
(
var
connection = factory.CreateConnection())
{
using
(
var
channel = connection.CreateModel())
{
}
}
|
connection对象抽象出了一个Socket连接,并负责RabbitMQ所使用的协议版本的验证和协商。
这里连接的是本地的RabbitMQ实例,所以使用的localhost作为主机名,如果需要连接其他服务器的RabbitMQ实例,只需要将主机名变更为对应服务器的ip地址即可。
然后我们需要创建一个channel对象,大部分的消息处理有关的api都是在channel对象中。
接下来,为了发送消息,我们需要创建一个消息队列,然后向这个消息队列中发布消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
using
System;
using
RabbitMQ.Client;
using
System.Text;
class
Send
{
public
static
void
Main()
{
var
factory =
new
ConnectionFactory() { HostName =
"localhost"
};
using
(
var
connection = factory.CreateConnection())
using
(
var
channel = connection.CreateModel())
{
channel.QueueDeclare(queue:
"hello"
,
durable:
false
,
exclusive:
false
,
autoDelete:
false
,
arguments:
null
);
string
message =
"Hello World!"
;
var
body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange:
""
,
routingKey:
"hello"
,
basicProperties:
null
,
body: body);
Console.WriteLine(
" [x] Sent {0}"
, message);
}
Console.WriteLine(
" Press [enter] to exit."
);
Console.ReadLine();
}
}
|
Channel对象的QueueDeclare方法是用来声明一个消息队列的,这个方法是等幂的,即只要当该消息队列不存在的时候才创建他,如果已经存在,就直接返回之前创建的对象。
RabbitMQ中传递的消息是二进制数据,所以需要将传递的文本转换成二进制数据。
这样消息发送程序就完成了。
编写接收消息程序#
前面我们做的发送程序运行一次只发送一条消息,与发送程序不同,接收消息程序需要监听接收到的所有消息,并打印。
首先我们引入需要使用的命名空间
1
2
3
4
5
6
7
|
using
RabbitMQ.Client;
using
RabbitMQ.Client.Events;
using
System;
using
System.Text;
|
初始的设置代码和发送程序一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public
static
void
Main()
{
var
factory =
new
ConnectionFactory() { HostName =
"localhost"
};
using
(
var
connection = factory.CreateConnection())
{
using
(
var
channel = connection.CreateModel())
{
channel.QueueDeclare(queue:
"hello"
,
durable:
false
,
exclusive:
false
,
autoDelete:
false
,
arguments:
null
);
}
}
}
|
这里还要重复声明一次消息队列的原因是,你不能保证在消息接收程序启动时,消息队列一定存在(即消息接收程序的启动早于发送消息程序)。
由于消息的推送是异步的,所以这里我们需要使用事件来捕捉消息,并打印在控制台
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
var
factory =
new
ConnectionFactory() { HostName =
"localhost"
};
using
(
var
connection = factory.CreateConnection())
using
(
var
channel = connection.CreateModel())
{
channel.QueueDeclare(queue:
"hello"
,
durable:
false
,
exclusive:
false
,
autoDelete:
false
,
arguments:
null
);
var
consumer =
new
EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var
body = ea.Body;
var
message = Encoding.UTF8.GetString(body);
Console.WriteLine(
" [x] Received {0}"
, message);
};
channel.BasicConsume(queue:
"hello"
,
autoAck:
true
,
consumer: consumer);
Console.WriteLine(
" Press [enter] to exit."
);
Console.ReadLine();
}
|
运行程序#
在消息发送程序和消息接收程序目录下,使用.NET Core命令行工具启动2个项目
dotnet run