最近博主工作中用到了MQ,之前写了一篇关于MQ技术选型文章。今天把部署及demo代码也一并整理一下。网上关于操作RabbitMQ的代码更多的是java和python的。其实我也一直在用python,只是作为老本行代码,还是想用C++分享给大家,以解大家燃眉之急。
一、部署流程
这部分网上有很多rabbitmq的安装教程,大家一搜一大把。windows下部署我就不写了(也部署测试成功),这里只介绍下在linux下部署。我这里只把我这次部署时的步骤罗列一下:
首先,我是用虚拟机搭建了一个新的redhat7.6环境。因此首先要解决yum源的问题。网上有很多介绍更改yum的文章。很多介绍的是163或是阿里云的镜像。但是我发现这些yum源仓库中并没有rabbitmq相关的资源。后来同事给了一个好东西。问题一下就解决了。整个安装过程也简单了很多。
curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
yum -y install erlang
- 在终端中以root用户执行第一行的语句,否则报错。执行完会在/etc/yum.repos.d/下生成一个包含了erlang等资源的yum仓库。
并且sh脚本已经做了yum makecache等命令,真正地即插即用。 - 第二句安装erlang。因为rabbitmq是基于erlang开发的。yum帮我们安装的是最新的22.2版本的。
[很多人会问,没有这个yum源又能怎样?是的,没有的话你的机器也能正常使用,不过你安装所需的软件包,都要费劲扒拉地在官网上下载,耗时又费力。]
其次,安装完之后,接下来安装rabbitMQ
发现需要先安装socat,直接执行
yum -y install socat
rpm -ivh rabbitmq-server-3.8.2-1.el7.noarch.rpm
安装完成后,启动服务
systemctl start rabbitmq-server
启动界面管理,打开浏览器 127.0.0.1:15672 看是否能打开 guest guest登录
rabbitmq-plugins enable rabbitmq_management
二、代码示例
以上就是安装过程。下面要开发C++程序,我们还需要用到rabbitmq开发库。直接执行
yum -y install librabbitmq-devel.x86_64
下面直接上代码,是我整理过,调试的。这里提供一个消费者代码
rabbitmq_consumer.cpp
#include <iostream>
#include <cstdlib>
#include <string>
#include <amqp.h>
#include <amqp_framing.h>
#include <amqp_tcp_socket.h>
using namespace std;
int main()
{
string hostName = "127.0.0.1";
int port = 5672; //这里写成15672,会报错,因为这是管理界面的端口
amqp_socket_t *socket = nullptr;
amqp_connection_state_t conn;
amqp_rpc_reply_t reply ;
/*分配并初始化一个新的amqp_connection_state_t对象*/
conn = amqp_new_connection();
/*创建一个新的TCP socket*/
socket = amqp_tcp_socket_new(conn);
if(!socket)
{
cout << "create socket failed!";
exit(1);
}
/*打开socket连接
*
*成功返回AMQP_STATUS_OK,失败返回一个amqp_status_enum
*/
if(amqp_socket_open(socket, hostName.c_str(), port)
{
cout << "opening TCP socket failed" << endl;
exit(1);
}
/*登录到broker
*
*
* 使用amqp_open_socket和amqp_set_sockfd后,调用amqp_login完成到broker的连接
*
* \param [in] state 连接对象
* \param [in] vhost 虚拟主机连接到broker,大多数broker默认为“/”
* \param [in] channel_max 连接通道数量的限制,0代表无限制,较好的默认值是AMQP_DEFAULT_MAX_CHANNELS
* \param [in] frame_max 线路上的AMQP帧的最大大小以请求代理进行此连接,最小4096,
* 最大2^31-1,较好的默认值是131072 (128KB)或者AMQP_DEFAULT_FRAME_SIZE
* \param [in] heartbeat 心跳帧到broker的请求之间的秒数。设0表示禁用心跳
* \param [in] sasl_method SASL method用来验证broker,以下是SASL methods的实现:
* -AMQP_SASL_METHOD_PLAIN:该方法需要按如下顺序跟两个参数:
* const char* username, and const char* password.
* -AMQP_SASL_METHOD_EXTERNAL:该方法需要跟参数:
* const char* identity.
*
* 返回值:amqp_rpc_reply_t 标明成功或失败
*/
reply = amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest");
if(AMQP_RESPONSE_NORMAL != reply.reply_type)
{
cout << "login failed" << endl;
exit(1);
}
amqp_channel_open(conn, 1);
/**
* 获取最后一个全局amqp_rpc_reply
*
* 此API方法对应于大多数同步的AMQP方法返回一个指向解码方法结果的指针。
*
* \param [in] state 连接对象
* \return 最新的amqp_rpc_reply_t
* - reply.reply_type == AMQP_RESPONSE_NORMAL. RPC已成功完成
* - reply.reply_type == AMQP_RESPONSE_SERVER_EXCEPTION. broker返回异常
* - reply.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. 库内发生异常
*/
if(amqp_get_rpc_reply(conn).reply_type == AMQP_RESPONSE_SERVER_EXCEPTION)
{
printf("OpeningChannel::amqp get rpc_reply error\n");
return 0;
}
//自动回复ACK
//第三个参数是rabbitmq的node名,替换成你自己安装rabbitmq的node名
amqp_basic_consume(conn, 1, amqp_cstring_bytes("rabbit@localhost"), amqp_empty_bytes, 0, 1, 0, amqp_empty_table);
reply = amqp_get_rpc_reply(conn);
if(reply.reply_type != AMQP_RESPONSE_NORMAL)
{
printf("StartConsumer::amqp get rpc_reply error\n");
return false;
}
//循环消费,只要mq中有消息,就取来消费
while(1)
{
amqp_rpc_reply_t res;
/**
* 释放amqp_connection_state_t占用的内存
*
* 释放与任何通道相关的amqp_connection_state_t对象拥有的内存,允许库重用。
* 在调用该函数之前使用库返回的任何内存,会导致未定义的行为。
*/
amqp_maybe_release_buffers(conn);
amqp_envelope_t envelope;
//等待并消费一条消息
res = amqp_consume_message(conn, &envelope, NULL, 0);
if (AMQP_RESPONSE_NORMAL != res.reply_type)
{
break;
}
//输出从mq中拿到的消息内容
cout << "receive " << envelope.message.body.len << " bytes..." << endl
<< "The result is : " << (char *)envelope.message.body.bytes << endl;
//to do something with result...
/**
* 释放在amqp_consume_message()中分配的与amqp_envelope_t相关联的内存
*/
amqp_destroy_envelope(&envelope);
}
/**
* 关闭 channel
*
* \param [in] state 连接对象
* \param [in] channel channel标识符
* \param [in] 关闭channel的原因,最好默认为AMQP_REPLY_SUCCESS
* \return amqp_rpc_reply_t 表示成功或失败
*/
amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS);
/**
* 关闭整个连接
*
* 隐式关闭所有连接,通知broker连接正在关闭,收到来自broker的确认后,
* 关闭socket
*
* \param [in] state 连接对象
* \param [in] 关闭channel的原因,最好默认为AMQP_REPLY_SUCCESS
* \return amqp_rpc_reply_t 表示成功或失败
*/
amqp_connection_close(conn, AMQP_REPLY_SUCCESS);
/**
* 销毁amqp_connection_state_t对象
*
* 销毁使用amqp_new_connection()创建的amqp_connection_state_t对象
* 如果与broker的连接处于打开状态,则会以200(成功)的答复代码隐式关闭。
* 任何会被amqp_maybe_release_buffers()或amqp_maybe_release_buffers_on_channel()
* 释放的内存都将被释放,并且使用该内存将导致未定义的行为。
*
* \return 成功返回AMQP_STATUS_OK. 失败返回amqp_status_enum值
*/
amqp_destroy_connection(conn);
return 0;
}
最后编译代码,查看结果
g++ -std=c++11 -g -o rabbitmq_consumer rabbitmq_consumer.cpp -L/usr/local/lib -lrabbitmq
./rabbitmq_consumer
几点说明:
amqp_rpc_reply_t这个指针,反映了rpc交互结果。它有3种返回值,代码中已经介绍了。不建议在代码中写
if(amqp_get_rpc_reply(conn).reply_type == AMQP_RESPONSE_SERVER_EXCEPTION)
因为,如果之前网络没联通,或其他通信错误,是返回的AMQP_RESPONSE_LIBRARY_EXCEPTION。这个错误不是说库编译有问题,而多半是通信有误导致的。所以建议代码中写成
if (AMQP_RESPONSE_NORMAL != res.reply_type)
另外, amqp_basic_consume这个函数,貌似在while循环里面和外面都能正常执行,但我个人建议放在循环外面,指明一下获取哪个mq就好。while循环里还是等待读取消息就好。
三、总得看到点结果吧
这里额外提供一个python写的发布者代码,大家可以用它往mq里放一些消息。直接上代码。
durable_publish.py
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# 声明队列
channel.queue_declare(queue='rabbit@localhost',durable=True)
# 定义消息内容
message = 'hyazz1314'
# 发送消息
channel.basic_publish(exchange='',
routing_key='rabbit@localhost',
body=message,
properties=pika.BasicProperties(delivery_mode=2))
print(" [x] Sent '"+ message + "'")
connection.close()
durable=True指明这个队列是支持持久化消息的。这个代码就不多讲解了。网上有很多。
python使用rabbitmq,需要导入pika库,而且我用的python3,直接执行
yum install python3-pika
终端上执行
python3 durable_publish.py
这时,可以在管理界面中的overview标签下的图中看到有1个message展示了出来,这里就不放图了。
接着,在你编译好的C++程序目录下执行
./rabbitmq_consumer
这时,可以看到
大家可以忽略后面的乱码,因为我程序里强转成了char*,要打印到‘\0’的位置。实际工作中可以按收到的bytes长度来处理收到的消息。
这时候管理界面中的message个数也恢复成0,因为已经被消费走了。
好了,这就是整个过程。欢迎评论,讨论。
我的程序写在一个源文件中了,是个流程化的代码,为了梳理整个流程。最后推荐一个封装到类的C++代码。