1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/sogou-workflow

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
about-tlv-message.md 6.2 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 01.03.2025 10:20 552780a

Описание сообщений в формате TLV (Type-Length-Value)

Сообщение TLV состоит из трёх частей: типа, длины и значения. Благодаря простой и универсальной структуре, а также удобству для вложения и расширения, этот формат особенно полезен для определения сообщений связи.

Для удобства реализации пользователей мы предоставляем встроенную поддержку TLV-сообщений.

Структура сообщений TLV

Формат TLV не конкретизирует количество байтов, занимаемых полями Type и Length. В нашем протоколе они занимают по 4 байта (в сетевой последовательности). Это значит, что сообщение имеет заголовок размером Yöntem 8 байт и содержание Value до 32 ГБ. Мы не регламентируем значения полей Type и Value.

Класс TLVMessage

Поскольку определение TLV очень мало, то и необходимый набор методов для класса TLVMessage минимальен.

namespace protocol
{
class TLVMessage : public ProtocolMessage
{
public:
    int get_type() const { return this->type; }
    void set_type(int type) { this->type = type; }

    std::string *get_value() { return &this->value; }
    void set_value(std::string value) { this->value = std::move(value); }

protected:
    int type;
    std::string value;

    ...
};
using TLVRequest = TLVMessage;
using TLVResposne = TLVMessage;
}

Прямое использование TLV для передачи данных требует использования лишь нескольких методов: установка и получение значений Type и Value. Поле Value возвращается как std::string, что позволяет пользователям легко перемещать данные при необходимости с помощью std::move.

Сервер и клиент echo на основе TLV

Ниже приведён пример запуска сервера на основе TLV и создания задач клиента через командную строку. Рекомендуется запустить его.

#include <stdio.h>
#include <string>
#include <iostream>
#include "workflow/WFGlobal.h"
#include "workflow/WFFacilities.h"
#include "workflow/TLVMessage.h"
#include "workflow/WFTaskFactory.h"
#include "workflow/WFServer.h"

using namespace protocol;

using WFTLVServer = WFServer<TLVRequest, TLVResponse>;
using WFTLVTask = WFNetworkTask<TLVRequest, TLVResponse>;
using tlv_callback_t = std::function<void (WFTLVTask *)>;

WFTLVTask *create_tlv_task(const char *host, unsigned short port, tlv_callback_t callback)
{
    auto *task = WFNetworkTaskFactory<TLVRequest, TLVResponse>::create_client_task(
                                       TT_TCP, host, port, 0, std::move(callback));
    task->set_keep_alive(60 * 1000);
    return task;
}

int main()
{
    WFTLVServer server([](WFTLVTask *task) {
        *task->get_resp() = std::move(*task->get_req());
    });

    if (server.start(8888) != 0) {
        perror("server.start");
        exit(1);
    }

    auto&& create = [](WFRepeaterTask *)->SubTask * {
        std::string string;
        printf("Введите строку (Ctrl-D для завершения): ");
        std::cin >> string;
        if (string.empty())
            return NULL;

        auto *task = create_tlv_task("127.0.0.1", 8888, [](WFTLVTask *task) {
            if (task->get_state() == WFT_STATE_SUCCESS)
                printf("Ответ сервера: %s\n", task->get_resp()->get_value()->c_str());
            else {
                const char *str = WFGlobal::get_error_string(task->get_state(), task->get_error());
                fprintf(stderr, "Ошибка: %s\n", str);
            }
        });

        task->get_req()->set_value(std::move(string));
        return task;
    };

    WFFacilities::WaitGroup wait_group(1);
    WFRepeaterTask *repeater = WFTaskFactory::create_repeater_task(std::move(create), nullptr);
    Workflow::start_series_work(repeater, [&wait_group](const SeriesWork *) {
        wait_group.done();
    });

    wait_group.wait();
    server.stop();
    return 0;
}

Наследование от TLVMessage

В примере выше используется базовый класс TLVMessage. Однако рекомендуется создавать производные классы для конкретных применений.

В этих производных классах можно предоставлять более богатые интерфейсы для работы со значениями сообщений, избегая непосредственного обращения к полю Value и создавая свои вторичные протоколы. Например, можно реализовать протокол JSON следующим образом:

#include "workflow/json-parser.h"    // Встроенный парсер JSON

class JsonMessage : public TLVMessage
{
public:
    void set_json_value(const json_value_t *val)
    {
        this->type = JSON_TYPE;
        this->json_to_string(val, &this->value);  // Необходимо реализовать
    }

    json_value_t *get_json_value() const
    {
        if (this->type == JSON_TYPE)
            return json_parser_parse(this->value.c_str());  // Функция из json-parser
        else
            return NULL;
    }
};

using JsonRequest = JsonMessage;
using JsonResponse = JsonMessage;

using JsonServer = WFServer<JsonRequest, JsonResponse>;

Этот пример предназначен только для демонстрации важности наследования. В реальных применениях производные классы могут быть значительно сложнее.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/sogou-workflow.git
git@api.gitlife.ru:oschina-mirror/sogou-workflow.git
oschina-mirror
sogou-workflow
sogou-workflow
master