Використовуємо 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: %s\n", 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: %s\n", 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: %s\n", 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: %s\n", 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: %s\n", 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 інструмент… Навіть якщо Ви розробляєте на Сі.

Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.