基于rabbitmq-c的Rabbitmq C++客户端的实现
序言
最近在一个项目中需要通过rabbitmq进行数据交换,网上查了下最终选择使用rabbitmq-c,目前项目已经告一段落,遂将rabbitmq-c的使用流程和注意事项记录于此,便于大家查阅指正。
rabbitmq-c编译
下载代码:https://github.com/alanxz/rabbitmq-c
使用CMake构建visual studio工程
编译生成rabbitmq.4.lib、rabbitmq.4.dll、librabbitmq.4.lib(静态库)
rabbitmq-c中有examples说明了基本的调用流程,可以查阅
rabbitmq-c封装
由于实现使用中交换机、队列和路由规则都是提前定义好的所以就没有对其进行封装,如有需要可以查阅rabbitmq-c的examples.
RabbitmqClient.h
#ifndef __RABBITMQ_CLIENT_H__
#define __RABBITMQ_CLIENT_H__
#include <iostream>
#include "amqp_tcp_socket.h"
class RabbitmqClient
{
public:
typedef enum ClientType{PRODUCER, CONSUMER};
ClientType get_client_type() { return _client_type; }
public:
RabbitmqClient(ClientType type, int channel, int heart);
virtual ~RabbitmqClient();
int start(const std::string& str_host_name, int port, const std::string& str_user, const std::string& str_password);
void restart(const std::string& str_host_name, int port, const std::string& str_user, const std::string& str_password);
void stop();
public:
int publish(const std::string& str_message, const std::string& str_exchange, const std::string& str_route_key);
int consumer(const std::string &str_queue_name, struct timeval* timeout);
class Listener {
public:
virtual void rabbitmq_notify(const std::string& message) = 0;
virtual void rabbit_reconnect_request(ClientType client_type) = 0;
virtual void rabbit_reconnect_success_notify(ClientType client_type) = 0;
};
void set_rabbit_listener(Listener* listener) { _listener = listener; }
void set_stop_consumer(bool stop_consumer) { _stop_consumer = stop_consumer; }
private:
RabbitmqClient(const RabbitmqClient & rh);
void operator=(const RabbitmqClient & rh);
int connect(const std::string &str_host_name, int port, const std::string &str_user, const std::string &str_password);
int disconnect();
int error_msg(amqp_rpc_reply_t x, char const *context);
std::string _host_name;
int _port;
std::string _username;
std::string _password;
int _channel;
int _heart;
amqp_socket_t *_socket;
amqp_connection_state_t _connect_state;
bool _stop_consumer;
ClientType _client_type;
Listener *_listener;
};
#endif
RabbitmqClient.cpp
#include "RabbitmqClient.h"
#include <windows.h>
RabbitmqClient::RabbitmqClient(ClientType type, int channel, int heart)
: _host_name("")
, _port(0)
, _username("")
, _password("")
, _client_type(type)
, _channel(channel) //默认用1号通道,通道无所谓
, _heart(heart)
, _socket(NULL)
, _listener(NULL)
, _stop_consumer(false)
, _connect_state(NULL)
{
}
RabbitmqClient::~RabbitmqClient()
{
if (NULL != _connect_state) {
_stop_consumer = true;
disconnect();
_connect_state = NULL;
}
}
int RabbitmqClient::start(const std::string& str_host_name, int port, const std::string& str_user, const std::string& str_password)
{
int iRet = connect(str_host_name, port, str_user, str_password);
if (0 != iRet) {
return -1;
}
return 0;
}
void RabbitmqClient::restart(const std::string& str_host_name, int port, const std::string& str_user, const std::string& str_password)
{
int iRet = connect(str_host_name, port, str_user, str_password);
while (0 != iRet) {
iRet = connect(str_host_name, port, str_user, str_password);
Sleep(10000);
}
// reconnect success
if (_listener) {
_listener->rabbit_reconnect_success_notify(_client_type);
}
}
void RabbitmqClient::stop()
{
_stop_consumer = true;
disconnect();
}
int RabbitmqClient::connect(const std::string &str_host_name, int port, const std::string &str_user, const std::string &str_password) {
_host_name = str_host_name;
_port = port;
_username = str_user;
_password = str_password;
_connect_state = amqp_new_connection();
if (NULL == _connect_state) {
std::cerr << "amqp new connection failed\n" << std::endl;
return -1;
}
_socket = amqp_tcp_socket_new(_connect_state);
if (NULL == _socket) {
std::cerr << "amqp tcp new socket failed." << std::endl;
return -2;
}
int status = amqp_socket_open(_socket, _host_name.c_str(), _port);
if (status<0) {
std::cerr << "amqp socket open failed." << std::endl;
return -3;
}
// amqp_login(amqp_connection_state_t state,char const *vhost, int channel_max, int frame_max, int heartbeat, amqp_sasl_method_enum sasl_method, ..)
if (0 != error_msg(amqp_login(_connect_state, "/", 0, 131072, _heart,
AMQP_SASL_METHOD_PLAIN, _username.c_str(), _password.c_str()), "Logging in")) {
return -4;
}
amqp_channel_open(_connect_state, _channel);
if (0 != error_msg(amqp_get_rpc_reply(_connect_state), "open channel for consumer.")) {
return -2;
}
return 0;
}
int RabbitmqClient::disconnect() {
if (NULL != _connect_state) {
amqp_channel_close(_connect_state, _channel, AMQP_REPLY_SUCCESS);
amqp_connection_close(_connect_state, AMQP_REPLY_SUCCESS);
amqp_destroy_connection(_connect_state);
_connect_state = NULL;
}
return 0;
}
int RabbitmqClient::publish(const std::string &str_message, const std::string &str_exchange, const std::string &str_route_key) {
if (NULL == _connect_state) {
std::cerr << "publish _connect_state is null, publish failed." << std::endl;
return -1;
}
amqp_bytes_t message_bytes;
message_bytes.len = str_message.length();
message_bytes.bytes = (void *)(str_message.c_str());
//fprintf(stderr, "publish message(%d): %.*s\n", (int)message_bytes.len, (int)message_bytes.len, (char *)message_bytes.bytes);
amqp_basic_properties_t props;
props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_CONTENT_ENCODING_FLAG;
props.content_type = amqp_cstring_bytes("application/json");
props.content_encoding = amqp_cstring_bytes("utf-8");
amqp_bytes_t exchange = amqp_cstring_bytes(str_exchange.c_str());
amqp_bytes_t routekey = amqp_cstring_bytes(str_route_key.c_str());
if (0 != amqp_basic_publish(_connect_state, _channel, exchange, routekey, 0, 0, &props, message_bytes)) {
fprintf(stderr, "publish amqp_basic_publish failed\n");
if (0 != error_msg(amqp_get_rpc_reply(_connect_state), "amqp_basic_publish")) {
return -3;
}
}
return 0;
}
int RabbitmqClient::consumer(const std::string &str_queue_name, struct timeval* timeout)
{
if (NULL == _connect_state) {
std::cout << "Consumer _connect_state is null, Consumer failed." << std::endl;
return -1;
}
amqp_basic_qos(_connect_state, _channel, 0, 1, true);
if (0 != error_msg(amqp_get_rpc_reply(_connect_state), "amqp basic qos.")) {
return -2;
}
int ack = 1;
amqp_bytes_t queuename = amqp_cstring_bytes(str_queue_name.c_str());
amqp_basic_consume(_connect_state, _channel, queuename, amqp_empty_bytes, 0, ack, 0, amqp_empty_table);
if (0 != error_msg(amqp_get_rpc_reply(_connect_state), "Consuming")) {
return -3;
}
_stop_consumer = false;
amqp_rpc_reply_t res;
amqp_envelope_t envelope;
while (!_stop_consumer) {
amqp_maybe_release_buffers(_connect_state);
res = amqp_consume_message(_connect_state, &envelope, timeout, 0);
if (AMQP_RESPONSE_NORMAL != res.reply_type) {
int iRet = error_msg(res, "amqp_consume_message");
if (-2 == iRet){
std::cerr << "RabbitmqClient::consumer quit." << std::endl;
amqp_destroy_envelope(&envelope);
break;
}
else {
amqp_destroy_envelope(&envelope);
continue;
}
}
std::string str((char*)envelope.message.body.bytes,
(char*)envelope.message.body.bytes + envelope.message.body.len);
if (_listener && !_stop_consumer) {
std::cout << "recv rabbitmq data!" << std::endl;
_listener->rabbitmq_notify(str);
}
amqp_destroy_envelope(&envelope);
}
_stop_consumer = true;
return 0;
}
int RabbitmqClient::error_msg(amqp_rpc_reply_t x, char const *context) {
int iRet = 0;
switch (x.reply_type) {
case AMQP_RESPONSE_NORMAL:
iRet = 0;
break;
case AMQP_RESPONSE_NONE:
std::cerr << context << ": missing RPC reply type!" << std::endl;
break;
case AMQP_RESPONSE_LIBRARY_EXCEPTION:
if (x.library_error == 0xfffffff3) {
iRet = 1;
}
else
{
iRet = -2; // reconnect
std::cerr << context << ": " << amqp_error_string2(x.library_error) << std::endl;
}
break;
case AMQP_RESPONSE_SERVER_EXCEPTION:
switch (x.reply.id) {
case AMQP_CONNECTION_CLOSE_METHOD: {
amqp_connection_close_t *m = (amqp_connection_close_t *)x.reply.decoded;
std::cerr << context << ": server connection error " << m->reply_code
<< ", message: %s" << (char*)m->reply_text.bytes << std::endl;
iRet = -2; // reconnect
break;
}
case AMQP_CHANNEL_CLOSE_METHOD: {
amqp_channel_close_t *m = (amqp_channel_close_t *)x.reply.decoded;
std::cerr << context << ": server channel error " << m->reply_code
<< ", message: %s" << (char*)m->reply_text.bytes << std::endl;
iRet = -2; // reconnect
break;
}
default:
std::cerr << context << ": unknown server error, method id " << x.reply.id << std::endl;
break;
}
break;
}
if (iRet == -2) {
// reconnect request
if (_listener) {
_listener->rabbit_reconnect_request(_client_type);
}
}
return iRet;
}
代码说明
RabbitmqClient::ClientType:该枚举定义了rabbitmq client的类型,即生产者或者消费者;
RabbitmqClient::RabbitmqClient:构造rabbitmq client对象时需要传入,类型、通道号、心跳时间;
RabbitmqClient::start:连接到rabbitmq server;
RabbitmqClient::restart:该接口在重连线程中调用,每隔10秒中重连rabbitmq server,连接成功退出;
RabbitmqClient::stop:断开rabbitmq server连接;
RabbitmqClient::publish:用于生产消息,需要注意的是,调用该接口后不要马上调用stop,否则消息发送会失败,可能rabbitmq-c中的是使用异步方式发送,但接口中没有说明,这点有点疑问,如果有知道的请赐教。
RabbitmqClient::consumer:用于获取消息,该接口在消费线程中调用,可以通过第二个参数设置获取消息的超时时间,但是我在代码实现中没有使用第二个参数,而是直接使用NULL代表阻塞式消费;
RabbitmqClient::Listener:RabbitmqClient状态的监听类,该抽象类需要被实现,用于返回消费到的消息(rabbitmq_notify)、断线重连通知(rabbit_reconnect_request)、断线重连成功通知(rabbit_reconnect_success_notify);
RabbitmqClient::set_rabbit_listener:设置监听对象;
RabbitmqClient::set_stop_consumer: 设置为true表示停止消费;
使用说明
1、对于消费者和生产者需要建立不同的RabbitmqClient对象。这是由于amqp_new_connection()的返回值是非线程安全的,由于需要在一个消费线程中阻塞消费,所以不能使用同一个RabbitmqClient进行生产;如果在多线程中共用了amqp_new_connection()的返回值则会出现消费超时异常,从而引发abort;
2、RabbitmqClient作为生产者,调用pulish后不要马上调用stop;
3、RabbitmqClient作为生产者,如果使用长连接的方式则设置心跳为0。如果非0,则rabbitmq接收不到心跳回复就会主动断开;
4、RabbitmqClient作为消费者,建议设置心跳为非0(0-60);
备注
该代码还存在很大优化空间,后续使用中再逐步优化;如果大家有更好的封装、优化建议或者C++调用rabbitmq的方法请评论留言。