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");
以上的內容爲作者自行研究的內容,如果存在不正確和更優化的方式,歡迎各位留言評論!!!!