Приветствую, уважаемый хабраюзер.
Начну с небольшой предыстории. В данный момент я работаю над довольно комплексным продуктом, который состоит из серверной части (включает в себя несколько сервисов) и клиентского SDK, портированного на определенные платформы. Весь этот зоопарк нам надо каким-то образом тестировать совместно.
Были сделаны клиентские приложения (своего рода драйверы), которые используют соответствующие SDK и умеют получать команды от тестирующего сервиса вида «сходи на сервер, сделай то», или «дай мне такой-то результат для проверки». Команды клиент получает, используя Thrift(wiki). И все было хорошо, пока мы не добрались до портирования SDK на Си и не обнаружили, что толкового мануала для Си у Apache'а нету. Нашли тикет на создание оного мануала и скудный пример там же. После успешного применения Thrift'а в Си, было решено написать небольшой ликбез на эту тему.
Цели, которые мы поставили:
— Клиент должен получать команды от тестирующего сервиса, используя Thrift;
— Команды это отлично, но нужно еще и с сервером общаться;
— Клиент должен работать в одном потоке.
Буду описывать особенности работы, используя пример “RPC калькулятора” с сайта Apache. С-шная реализация Thrift'а использует glib, а значит и нам придется.
В первую очередь, сгенерируем исходники по схеме из примера:
Получили несколько *.c и *.h файлов. Они нам понадобятся в дальнейшем.
Теперь напишем функцию для инициализации Thrift соединения:
Тут мы создаем и инициализируем такие важные вещи как: сокет для передачи данных, транспортный бинарный протокол, клиентский интерфейс и пр. Теперь попробуем вызвать метод калькулятора:
Отлично, в переменную result получили значение 7. Но нам этого недостаточно. Вызов получился блокирующим, а в нашем случае тестирующий сервис может далеко не сразу вернуть команду/результат, при этом клиенту надо все еще читать и обрабатывать ответы от нашего сервера (работаем в одном потоке, помните?). К счастью, реализация Thrift позволяет нам разбить вызов calculator_client_add на 2 вызова: calculator_client_send_add и calculator_client_recv_add. Первый из них отправляет запрос с аргументами функции. Второй, соответственно, читает ответ с возвращенным значением. Наша функция thrift_init как раз возвращает дескриптор Thrift сокета, его и будем использовать:
И напоследок — функция для освобождения ресурсов:
Таким образом, нам удалось убить сразу двух зайцев: и с сервером пообщаться, и thrift вызовы подергать. При этом никаких fork'ов и thread'ов. Хочу добавить, что Thrift – замечательный и простой в использовании RPC инструмент… Даже если Вы разрабатываете на Си.
Начну с небольшой предыстории. В данный момент я работаю над довольно комплексным продуктом, который состоит из серверной части (включает в себя несколько сервисов) и клиентского 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 инструмент… Даже если Вы разрабатываете на Си.