基于rabbitmq-c的Rabbitmq C++客户端的实现

基于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的方法请评论留言。

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值