RabbitMQ C Master 使用記錄之C/C++ RPC遠程調用

RabbitMQ C Master 使用記錄之RPC遠程調用

經過查詢,目前沒有在主流的博客或者論壇中找到C/C++中使用RPC的內容,並且官方的文檔並不是很詳細,示例代碼中也僅僅發現了amqp_rpc_sendstring_client.c的文檔,並沒有server端的代碼,再寫server端程序的時候遇到各種問題,解決完後以此文記錄下來,解決問題的過程中參考了RPC-Java和Python的官方示例。

1. 目的

目的:使用RabbitMQ C Master官方提供的C API開發出一套能夠在C/C++平臺中使用的RPC遠程調用程序。

2. 重點關注服務端程序

只需要關注關鍵部分的修改即可,完整程序請查看下面詳細內容。
此處爲server端的RPC回覆部分,之前遇到的問題是無法發送數據到client端,找了很多方法,最終從python和Java版本的代碼中獲得靈感,如果有官方的出處,也請評論中註明

/*
	4. 此處爲作者個人修改部分
	因爲在發送的時候初始化props爲0, 則如果不是一個RPC的消息類型,則此處的reply_to的長度應該是0,否則,不爲0
*/
if (0 != envelope.message.properties.reply_to.len)
{
	printf("there is a RPC reply\n");

	amqp_basic_properties_t reply_props = { 0 };
	reply_props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG;
	reply_props.content_type = amqp_cstring_bytes("text/plain");
	reply_props.delivery_mode = 2; /* persistent delivery mode */
	reply_props.correlation_id = envelope.message.properties.correlation_id;
	/*
		5. 重點需要照顧的部分
		此處EXCHANGE_NAME也就是交換機的名稱爲空,如果填寫了上面接收使用的名稱,rpc client端將不會收到,或者使用其他的名稱也不行
		在原本需要寫入ROUTE_KEY的部分則需要填入接收到的reply_to信息,只有當這兩部分都填寫正確rpc client纔會收到回覆的消息
	*/
	die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes(""),
		amqp_cstring_bytes(envelope.message.properties.reply_to.bytes), 0, 0,
		&reply_props, amqp_cstring_bytes(messagebody)),
		"Publishing");
}

2. 客戶端程序

客戶端程序:參考examples中的amqp_rpc_sendstring_client.c示例:
需要將IP、PORT、EXCHANGE_NAME、ROUTE_KEY替換成自己使用的,此處程序並未做過多研究和改動,僅僅作爲參考。

char const *HostName = "IP";
int port = PORT;
int status = 0;
char const *exchange = "EXCHANGE_NAME";
char const *routingkey = "ROUTE_KEY";
char const *messagebody = "hello rpc msg";
char const *username = "test";
char const *passwd = "test";

amqp_socket_t *socket = NULL;
amqp_connection_state_t conn = NULL;
amqp_bytes_t reply_to_queue = { 0 };


conn = amqp_new_connection();

socket = amqp_tcp_socket_new(conn);
if (!socket)
{
	die("creating TCP socket");
}

status = amqp_socket_open(socket, HostName, port);
if (status) {
	die("opening TCP socket");
}

/*登錄*/
die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, username, passwd), "Logging in");
/*打開通道*/
amqp_channel_open(conn, 1);
/*檢測通道是否成功打開,等待rpc反饋,不等同rpc通信*/
die_on_amqp_error(amqp_get_rpc_reply(conn),"Opening channel");

/*
	create private reply_to queue
	創建回調的接收隊列
*/

{
	amqp_queue_declare_ok_t *r = amqp_queue_declare(conn, 1, amqp_empty_bytes, 0, 0, 0, 1, amqp_empty_table);
	die_on_amqp_error(amqp_get_rpc_reply(conn), "Declaring queue");
	reply_to_queue = amqp_bytes_malloc_dup(r->queue);
	if (NULL == reply_to_queue.bytes)
	{
		fprintf(stderr, "Out of memory while copying queue name");
		return 1;
	}/*end of if*/
}/*end of privagte reply_to queue*/

 /*
	send the message
	發送msg
*/
{
	/*
	  set properties
	  設置消息屬性:
	  此處必須注意的是與常規send msg不同的是添加了reply_to屬性的賦值,此處賦值爲上面申請的回調隊列,並且必須設置correlation_id的值
	*/
	amqp_basic_properties_t props = { 0 };
	props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG 
		| AMQP_BASIC_REPLY_TO_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG;
	props.content_type = amqp_cstring_bytes("text/plain");
	props.delivery_mode = 2; /* persistent delivery mode */
	props.reply_to = amqp_bytes_malloc_dup(reply_to_queue);
	if (NULL == props.reply_to.bytes)
	{
		fprintf(stderr, "Out of memory while copying queue name");
		return 1;
	}

	props.correlation_id = amqp_cstring_bytes("1");



/*
	publish
	發送消息
*/

die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes(exchange), amqp_cstring_bytes(routingkey),
	0, 0, &props, amqp_cstring_bytes(messagebody)), "Publishing");
amqp_bytes_free(props.reply_to);

}

/*
	wait an answer
	等待RPC回覆
*/

{
	amqp_basic_consume(conn, 1, reply_to_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table);
	die_on_amqp_error(amqp_get_rpc_reply(conn), "Consuming");
	amqp_bytes_free(reply_to_queue);

	{
		amqp_frame_t frame = { 0 };
		int result = 0;

		amqp_basic_deliver_t *d = NULL;
		amqp_basic_properties_t *p = NULL;
		size_t body_target = 0;
		size_t body_received = 0;

		for (;;)
		{
			amqp_maybe_release_buffers(conn);
			result = amqp_simple_wait_frame(conn, &frame);
			printf("Result: %d\n", result);
			if (result < 0)
			{
				break;
			}

			if (frame.frame_type != AMQP_FRAME_METHOD) {
				continue;
			}

			printf("Method: %s\n", amqp_method_name(frame.payload.method.id));
			if (frame.payload.method.id != AMQP_BASIC_DELIVER_METHOD) {
				continue;
			}

			d = (amqp_basic_deliver_t *)frame.payload.method.decoded;
			printf("Delivery: %u exchange: %.*s routingkey: %.*s\n",
				(unsigned)d->delivery_tag, (int)d->exchange.len,
				(char *)d->exchange.bytes, (int)d->routing_key.len,
				(char *)d->routing_key.bytes);

			result = amqp_simple_wait_frame(conn, &frame);
			if (result < 0) {
				break;
			}

			if (frame.frame_type != AMQP_FRAME_HEADER) {
				fprintf(stderr, "Expected header!");
				abort();
			}

			p = (amqp_basic_properties_t *)frame.payload.properties.decoded;
			if (p->_flags & AMQP_BASIC_CONTENT_TYPE_FLAG) {
				printf("Content-type: %.*s\n", (int)p->content_type.len,
					(char *)p->content_type.bytes);
			}
			printf("----\n");

			body_target = (size_t)frame.payload.properties.body_size;
			body_received = 0;

			while (body_received < body_target) {
				result = amqp_simple_wait_frame(conn, &frame);
				if (result < 0) {
					break;
				}

				if (frame.frame_type != AMQP_FRAME_BODY) {
					fprintf(stderr, "Expected body!");
					abort();
				}

				body_received += frame.payload.body_fragment.len;
				assert(body_received <= body_target);

				amqp_dump(frame.payload.body_fragment.bytes,
					frame.payload.body_fragment.len);
			}

			if (body_received != body_target) {
				/* Can only happen when amqp_simple_wait_frame returns <= 0 */
				/* We break here to close the connection */
				break;
			}

			/* everything was fine, we can quit now because we received the reply */
			break;

		}/*end of for*/
	}/*end of part2*/

}/*end of part1*/

die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS),
	"Closing channel");
die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS),
	"Closing connection");
die_on_error(amqp_destroy_connection(conn), "Ending connection");


return 0;

3. 服務端程序

服務端程序目前沒有找到C/C++版本的,一下爲個人編寫的,如果有錯誤或者更好的方法,歡迎評論留言。

char const *HostName = "IP";
int port = PORT;
int status = 0;
char const *exchange = "EXCHANGE_NAME";
char const *routingkey = "ROUTE_KEY";
char const *messagebody = "hello rpc msg, I recv your msg";
char const *username = "test";
char const *passwd = "test";

amqp_socket_t *socket = NULL;
amqp_connection_state_t conn = NULL;
amqp_bytes_t recv_queue_name = { 0 };


conn = amqp_new_connection();

socket = amqp_tcp_socket_new(conn);
if (!socket)
{
	die("creating TCP socket");
}

status = amqp_socket_open(socket, HostName, port);
if (status) {
	die("opening TCP socket");
}

/*登錄*/
die_on_amqp_error(amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, username, passwd), "Logging in");
/*打開通道*/
amqp_channel_open(conn, 1);
/*檢測通道是否成功打開,等待rpc反饋,不等同rpc通信*/
die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel");


/*
create recv queue
1. 創建接收隊列,此處用來接收消息
*/

{
	amqp_queue_declare_ok_t *r = amqp_queue_declare(conn, 1, amqp_empty_bytes, 0, 0, 0, 1, amqp_empty_table);
	die_on_amqp_error(amqp_get_rpc_reply(conn), "Declaring queue");
	recv_queue_name = amqp_bytes_malloc_dup(r->queue);
	if (NULL == recv_queue_name.bytes)
	{
		fprintf(stderr, "Out of memory while copying queue name");
		return 1;
	}/*end of if*/
}/*end of privagte reply_to queue*/


/*
	consume
	2. 消費消息
*/
amqp_queue_bind(conn, 1, recv_queue_name, amqp_cstring_bytes(exchange),
	amqp_cstring_bytes(routingkey), amqp_empty_table);
die_on_amqp_error(amqp_get_rpc_reply(conn), "Binding queue");

amqp_basic_consume(conn, 1, recv_queue_name, amqp_empty_bytes, 0, 1, 0,
	amqp_empty_table);
die_on_amqp_error(amqp_get_rpc_reply(conn), "Consuming");

/*
	Get MSG
	3. 獲取消息的具體內容
*/
{
	for (;;) {
		amqp_rpc_reply_t res;
		amqp_envelope_t envelope;

		amqp_maybe_release_buffers(conn);

		res = amqp_consume_message(conn, &envelope, NULL, 0);

		if (AMQP_RESPONSE_NORMAL != res.reply_type) {
			break;
		}
		printf("Delivery %u, exchange %.*s routingkey %.*s\n",
			(unsigned)envelope.delivery_tag, (int)envelope.exchange.len,
			(char *)envelope.exchange.bytes, (int)envelope.routing_key.len,
			(char *)envelope.routing_key.bytes);

		printf("----\n");

		amqp_dump(envelope.message.body.bytes, envelope.message.body.len);

		/*
			4. 此處爲作者個人修改部分
			因爲在發送的時候初始化props爲0, 則如果不是一個RPC的消息類型,則此處的reply_to的長度應該是0,否則,不爲0
		*/
		if (0 != envelope.message.properties.reply_to.len)
		{
			printf("there is a RPC reply\n");

			amqp_basic_properties_t reply_props = { 0 };
			reply_props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG;
			reply_props.content_type = amqp_cstring_bytes("text/plain");
			reply_props.delivery_mode = 2; /* persistent delivery mode */
			reply_props.correlation_id = envelope.message.properties.correlation_id;
			/*
				5. 重點需要照顧的部分
				此處EXCHANGE_NAME也就是交換機的名稱爲空,如果填寫了上面接收使用的名稱,rpc client端將不會收到,或者使用其他的名稱也不行
				在原本需要寫入ROUTE_KEY的部分則需要填入接收到的reply_to信息,只有當這兩部分都填寫正確rpc client纔會收到回覆的消息
			*/
			die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes(""),
				amqp_cstring_bytes(envelope.message.properties.reply_to.bytes), 0, 0,
				&reply_props, amqp_cstring_bytes(messagebody)),
				"Publishing");
		}



		amqp_destroy_envelope(&envelope);

	}
}

die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS),
	"Closing channel");
die_on_amqp_error(amqp_connection_close(conn, AMQP_REPLY_SUCCESS),
	"Closing connection");
die_on_error(amqp_destroy_connection(conn), "Ending connection");


以上的內容爲作者自行研究的內容,如果存在不正確和更優化的方式,歡迎各位留言評論!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章