Используем Thrift в Си

в 10:59, , рубрики: linux, thrift, метки: , ,

Приветствую, уважаемый читатель.

Начну с небольшой предыстории. В данный момент я работаю над довольно комплексным продуктом, который состоит из серверной части (включает в себя несколько сервисов) и клиентского SDK, портированного на определенные платформы. Весь этот зоопарк нам надо каким-то образом тестировать совместно.

Были сделаны клиентские приложения (своего рода драйверы), которые используют соответствующие SDK и умеют получать команды от тестирующего сервиса вида «сходи на сервер, сделай то», или «дай мне такой-то результат для проверки». Команды клиент получает, используя Thrift(wiki). И все было хорошо, пока мы не добрались до портирования SDK на Си и не обнаружили, что толкового мануала для Си у Apache'а нету. Нашли тикет на создание оного мануала и скудный пример там же. После успешного применения Thrift'а в Си, было решено написать небольшой ликбез на эту тему.

Цели, которые мы поставили:
— Клиент должен получать команды от тестирующего сервиса, используя Thrift;
— Команды это отлично, но нужно еще и с сервером общаться;
— Клиент должен работать в одном потоке.

Буду описывать особенности работы, используя пример “RPC калькулятора” с сайта Apache. С-шная реализация Thrift'а использует glib, а значит и нам придется.
В первую очередь, сгенерируем исходники по схеме из примера:

thrift -r --gen c_glib ./tutorial.thrift

Получили несколько *.c и *.h файлов. Они нам понадобятся в дальнейшем.
Теперь напишем функцию для инициализации Thrift соединения:

Функция thrift_init()

#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;
}

И напоследок — функция для освобождения ресурсов:

Функция thrift_destroy()

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

Источник

* - обязательные к заполнению поля