Приветствую, уважаемый читатель.
Начну с небольшой предыстории. В данный момент я работаю над довольно комплексным продуктом, который состоит из серверной части (включает в себя несколько сервисов) и клиентского SDK, портированного на определенные платформы. Весь этот зоопарк нам надо каким-то образом тестировать совместно.
Были сделаны клиентские приложения (своего рода драйверы), которые используют соответствующие SDK и умеют получать команды от тестирующего сервиса вида «сходи на сервер, сделай то», или «дай мне такой-то результат для проверки». Команды клиент получает, используя Thrift(wiki). И все было хорошо, пока мы не добрались до портирования SDK на Си и не обнаружили, что толкового мануала для Си у Apache'а нету. Нашли тикет на создание оного мануала и скудный пример там же. После успешного применения Thrift'а в Си, было решено написать небольшой ликбез на эту тему.
Цели, которые мы поставили:
— Клиент должен получать команды от тестирующего сервиса, используя Thrift;
— Команды это отлично, но нужно еще и с сервером общаться;
— Клиент должен работать в одном потоке.
Буду описывать особенности работы, используя пример “RPC калькулятора” с сайта Apache. С-шная реализация Thrift'а использует glib, а значит и нам придется.
В первую очередь, сгенерируем исходники по схеме из примера:
thrift -r --gen c_glib ./tutorial.thrift
Получили несколько *.c и *.h файлов. Они нам понадобятся в дальнейшем.
Теперь напишем функцию для инициализации Thrift соединения:
#include <glib.h>
#include <glib-object.h>
#include <thrift/c_glib/protocol/thrift_protocol.h>
#include <thrift/c_glib/protocol/thrift_binary_protocol.h>
#include <thrift/c_glib/transport/thrift_transport.h>
#include <thrift/c_glib/transport/thrift_buffered_transport.h>
#include <thrift/c_glib/transport/thrift_socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <calculator.h> // Сгенеренный хидер
static ThriftSocket *socket = NULL;
static ThriftTransport *transport = NULL;
static ThriftProtocol *protocol = NULL;
static CalculatorClient *client = NULL;
static CalculatorIf *iface = NULL;
int thrift_init(const char *hostname, int port)
{
printf("Initializing Thrift client (server=%s:%d)...n", hostname, port);
g_type_init();
GError *error = NULL;
socket = THRIFT_SOCKET(g_object_new(THRIFT_TYPE_SOCKET, "hostname", hostname, "port", port, &error));
if (error) {
printf("Failed to create thrift socket: %sn", error->message);
g_error_free(error);
return -1;
}
transport = THRIFT_TRANSPORT(g_object_new(THRIFT_TYPE_BUFFERED_TRANSPORT, "transport", socket, &error));
if (error) {
printf("Failed to create thrift transport: %sn", error->message);
g_error_free(error);
return -1;
}
protocol = THRIFT_PROTOCOL(g_object_new(THRIFT_TYPE_BINARY_PROTOCOL, "transport", transport, &error));
if (error) {
printf("Failed to create thrift binary protocol: %sn", error->message);
g_error_free(error);
return -1;
}
client = CALCULATOR_CLIENT(g_object_new(TYPE_CALCULATOR_CLIENT, "input_protocol", protocol, "output_protocol", protocol, &error));
if (error) {
printf("Failed to create thrift client: %sn", error->message);
g_error_free(error);
return -1;
}
iface = CALCULATOR_IF(client);
thrift_transport_open(transport, &error); // Открываем соединение
if (error) {
printf("Failed to open thrift connection: %sn", error->message);
g_error_free(error);
return -1;
}
return socket->sd; // Возвращаем дескриптор сокета. Понадобится дальше
}
Тут мы создаем и инициализируем такие важные вещи как: сокет для передачи данных, транспортный бинарный протокол, клиентский интерфейс и пр. Теперь попробуем вызвать метод калькулятора:
GError *error = NULL;
gint32 result = 0;
calculator_client_add(iface, &result, 4, 3, &error);
Отлично, в переменную result получили значение 7. Но нам этого недостаточно. Вызов получился блокирующим, а в нашем случае тестирующий сервис может далеко не сразу вернуть команду/результат, при этом клиенту надо все еще читать и обрабатывать ответы от нашего сервера (работаем в одном потоке, помните?). К счастью, реализация Thrift позволяет нам разбить вызов calculator_client_add на 2 вызова: calculator_client_send_add и calculator_client_recv_add. Первый из них отправляет запрос с аргументами функции. Второй, соответственно, читает ответ с возвращенным значением. Наша функция thrift_init как раз возвращает дескриптор Thrift сокета, его и будем использовать:
int main()
{
int socket = -1;
if ((socket = thrift_init("localhost", 9090)) >= 0) {
GError *error = NULL;
calculator_client_send_add(iface, 4, 3, &error);
if (error) {
// Обработка ошибки
g_error_free(error);
return -1;
}
struct pollfd fds;
fds.fd = socket;
fds.events = POLLIN;
fds.revents = 0;
if (poll(&fds, 1, 5000 /*5 секунд */) > 0) {
if (fds.revents & POLLIN) {
gint32 result = 0;
calculator_client_recv_add(iface, &result, &error); // Получили 7 в result
if (error) {
// Обработка ошибки
g_error_free(error);
return -1;
}
}
}
return 0;
}
return -1;
}
И напоследок — функция для освобождения ресурсов:
void thrift_destroy()
{
if (transport) {
thrift_transport_close(transport, NULL);
g_object_unref(transport);
}
if (client) {
g_object_unref(client);
}
if (protocol) {
g_object_unref(protocol);
}
if (socket) {
g_object_unref(socket);
}
}
Таким образом, нам удалось убить сразу двух зайцев: и с сервером пообщаться, и thrift вызовы подергать. При этом никаких fork'ов и thread'ов. Хочу добавить, что Thrift – замечательный и простой в использовании RPC инструмент… Даже если Вы разрабатываете на Си.
Автор: Zyoma