From 8284a3688362b9a203294f7b14e8107cd7448901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=9C=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Thu, 4 Dec 2025 13:07:55 +0700 Subject: [PATCH] disaggregate IpcMessage container and serdes --- README.md | 47 +++++++++-------- include/ipc/BaseIpcMessage.h | 80 +++++++++++++++++++++++++++++ include/ipc/IpcChannel.h | 2 +- include/ipc/IpcCodec.h | 2 +- include/ipc/IpcConfig.h | 21 ++++++++ include/ipc/IpcMessage.h | 117 ------------------------------------------- include/ipc/IpcPipeChannel.h | 32 ++++++++---- include/ipc/IpcSerializer.h | 102 +++++++++++++++++++++++++++++++++++++ 8 files changed, 254 insertions(+), 149 deletions(-) create mode 100644 include/ipc/BaseIpcMessage.h create mode 100644 include/ipc/IpcConfig.h delete mode 100644 include/ipc/IpcMessage.h create mode 100644 include/ipc/IpcSerializer.h diff --git a/README.md b/README.md index d3e69fa..afa31d4 100644 --- a/README.md +++ b/README.md @@ -19,39 +19,46 @@ project/ ├── CMakeLists.txt ├── README.md -├── src -│   ├── client.cpp -│   ├── common -│   │   ├── ipc -│   │   │   ├── IpcChannel.h -│   │   │   ├── IpcMessage.h -│   │   │   └── IpcPipeChannel.h -│   │   ├── proxy -│   │   │   └── ProxyMarshaller.h -│   │   └── rpc -│   │   ├── rpc_export.h -│   │   └── RpcInvoker.h -│   ├── MyService.cpp -│   ├── MyService.h -│   └── server.cpp -├── tools +├── include/ +│ ├── ipc/ +│ │ ├── BaseIpcMessage.h # шаблонный класс сообщения +│ │ ├── IpcChannel.h +│ │ ├── IpcCodec.h +│ │ ├── IpcConfig.h # type alias: using IpcMessage = BaseIpcMessage +│ │ ├── IpcDispatcher.h +│ │ ├── IpcPipeChannel.h +│ │ └── IpcSerializer.h # сериализаторы (TextIpcSerializer) +│ ├── proxy/ +│ │ └── ProxyMarshaller.h +│ └── rpc/ +│ ├── rpc_export.h +│ ├── RpcInvoker.h +│ └── RpcValue.h +├── src/ +│ ├── client.cpp +│ ├── MyService.cpp +│ ├── MyService.h +│ └── server.cpp +├── tools/ │ ├── generate_rpc.py -│ └── templates +│ └── templates/ │ ├── proxy.cpp.j2 │ ├── proxy.h.j2 │ ├── skeleton.cpp.j2 │ └── skeleton.h.j2 -└─ build/ # создаётся при сборке +└── build/ # создаётся при сборке ``` --- ## Архитектура (кратко) -* **Уровень IPC-сообщений**: `IpcMessage` +* **Уровень IPC-сообщений**: `IpcMessage` (type alias для `BaseIpcMessage`) * Построение: `msg.add("MyService.add"); msg.add(7); msg.add(8);` * Разбор: `auto name = msg.get(); auto a = msg.get(); auto b = msg.get();` - * Внутри хранится строка с простыми типовыми тегами (`i` для `int`, `s` для `std::string`), что позволяет в будущем перейти на бинарный формат без изменения API. + * Сериализация вынесена в отдельные сериализаторы (`TextIpcSerializer` и т.д.) + * Тип сырых данных параметризован через сериализатор (по умолчанию `std::string`, можно использовать `std::vector` для бинарных форматов) + * Выбор сериализатора делается один раз в `IpcConfig.h` через type alias * **Уровень канала**: `RpcChannel` + `IpcPipeChannel` * `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`. * `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`. diff --git a/include/ipc/BaseIpcMessage.h b/include/ipc/BaseIpcMessage.h new file mode 100644 index 0000000..723277d --- /dev/null +++ b/include/ipc/BaseIpcMessage.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +// Базовый шаблонный класс IPC-сообщения с API add() / get(). +// Сериализация вынесена в отдельные сериализаторы (см. IpcSerializer.h). +// Конкретный тип сообщения определяется через type alias в IpcConfig.h. + +template +class BaseIpcMessage { +public: + // Тип сырых данных определяется сериализатором + using RawData = typename Serializer::RawData; + + BaseIpcMessage() = default; + + // Конструирование из сырых данных (тип определяется сериализатором) + explicit BaseIpcMessage(const RawData& raw) { + data_ = Serializer::deserialize(raw); + } + + BaseIpcMessage(const BaseIpcMessage& other) + : data_(other.data_) + , pos_(0) {} + + BaseIpcMessage& operator=(const BaseIpcMessage& other) { + if (this != &other) { + data_ = other.data_; + pos_ = 0; + } + return *this; + } + + // Конструирование исходящего сообщения. + template + void add(const T& v) { + data_.emplace_back(v); + } + + // Чтение входящего сообщения по частям. + template + T get() { + if (pos_ >= data_.size()) { + return T{}; // или throw + } + + auto& v = data_[pos_++]; + if constexpr (std::is_same_v) { + if (auto* p = std::get_if(&v)) { + return *p; + } + } else if constexpr (std::is_same_v) { + if (auto* p = std::get_if(&v)) { + return *p; + } + } + return T{}; + } + + bool empty() const { + return pos_ >= data_.size(); + } + + // Сериализация в сырые данные (тип определяется сериализатором) + RawData serialize() const { + return Serializer::serialize(data_); + } + + // Получить сырые данные (для обратной совместимости или отладки) + RawData raw() const { + return serialize(); + } + +private: + std::vector> data_; + mutable std::size_t pos_{0}; +}; + diff --git a/include/ipc/IpcChannel.h b/include/ipc/IpcChannel.h index 544127f..7a18024 100644 --- a/include/ipc/IpcChannel.h +++ b/include/ipc/IpcChannel.h @@ -1,6 +1,6 @@ #pragma once -#include "IpcMessage.h" +#include "IpcConfig.h" // Абстракция IPC‑канала, работающего с IpcMessage. // Живёт отдельно от RPC‑кода, чтобы транспорт не зависел от конкретной RPC‑реализации. diff --git a/include/ipc/IpcCodec.h b/include/ipc/IpcCodec.h index d66ed59..d5a0f9e 100644 --- a/include/ipc/IpcCodec.h +++ b/include/ipc/IpcCodec.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/include/ipc/IpcConfig.h b/include/ipc/IpcConfig.h new file mode 100644 index 0000000..b5439b8 --- /dev/null +++ b/include/ipc/IpcConfig.h @@ -0,0 +1,21 @@ +#pragma once + +// Конфигурация IPC: выбор конкретного сериализатора +// В один момент времени используется только один сериализатор, +// выбор делается через type alias для конкретного типа IpcMessage. + +#include "BaseIpcMessage.h" +#include "IpcSerializer.h" + +// По умолчанию используем текстовый сериализатор +// Для переключения на JSON достаточно заменить на: +// using IpcMessage = BaseIpcMessage; +using IpcMessage = BaseIpcMessage; + +// Альтернативный вариант через макрос (если нужен compile-time выбор): +// #ifdef USE_JSON_SERIALIZER +// using IpcMessage = BaseIpcMessage; +// #else +// using IpcMessage = BaseIpcMessage; +// #endif + diff --git a/include/ipc/IpcMessage.h b/include/ipc/IpcMessage.h deleted file mode 100644 index 50e21a4..0000000 --- a/include/ipc/IpcMessage.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once - -#include -#include - -// Примитивное IPC‑сообщение с API add() / get(). -// Под капотом пока текстовый формат с типовыми тегами, но снаружи интерфейс не завязан на std::string. - -class IpcMessage { -public: - IpcMessage() = default; - - explicit IpcMessage(const std::string& raw) - : raw_(raw) {} - - IpcMessage(const IpcMessage& other) - : raw_(other.raw_) {} - - IpcMessage& operator=(const IpcMessage& other) { - if (this != &other) { - raw_ = other.raw_; - in_.clear(); - in_initialized_ = false; - out_.str(std::string{}); - } - return *this; - } - - // Конструирование исходящего сообщения. - template - void add(const T& v); - - // Чтение входящего сообщения по частям. - template - T get(); - - const std::string& raw() const { - return raw_; - } - - bool empty() const { - // Для входящих сообщений считаем "пустым" то, у которого - // больше не осталось непрочитанных данных во входном потоке. - // - // Для свежесозданного сообщения (ещё не инициализирован in_) - // поведение остаётся прежним: пусто == raw_.empty(). - if (!in_initialized_) { - return raw_.empty(); - } - - // Если поток уже инициализирован, смотрим, остались ли данные. - // peek() вернёт EOF, когда всё прочитано. - return in_.peek() == EOF; - } - -private: - std::string raw_; - std::ostringstream out_; - mutable std::istringstream in_; - mutable bool in_initialized_{false}; - - void ensureInput() const { - if (!in_initialized_) { - in_.str(raw_); - in_initialized_ = true; - } - } -}; - -// ===== Специализации по типам ===== - -// int -template<> -inline void IpcMessage::add(const int& v) { - out_ << 'i' << ' ' << v << ' '; - raw_ = out_.str(); -} - -template<> -inline int IpcMessage::get() { - ensureInput(); - char tag{}; - in_ >> tag; - // в PoC просто доверяем, что тип совпадает - int v{}; - in_ >> v; - return v; -} - -// std::string -template<> -inline void IpcMessage::add(const std::string& v) { - // формат: 's' - out_ << 's' << ' ' << v.size() << ' '; - out_.write(v.data(), static_cast(v.size())); - out_ << ' '; - raw_ = out_.str(); -} - -template<> -inline std::string IpcMessage::get() { - ensureInput(); - char tag{}; - in_ >> tag; - // ожидаем 's' - std::size_t len{}; - in_ >> len; - // съесть одиночный пробел перед данными - in_.get(); - std::string res(len, '\0'); - in_.read(&res[0], static_cast(len)); - // съесть завершающий пробел - in_.get(); - return res; -} - - diff --git a/include/ipc/IpcPipeChannel.h b/include/ipc/IpcPipeChannel.h index e7889df..34e92ed 100644 --- a/include/ipc/IpcPipeChannel.h +++ b/include/ipc/IpcPipeChannel.h @@ -33,22 +33,34 @@ public: if (fdOut_ < 0) { return; } - const std::string& data = msg.raw(); - ::write(fdOut_, data.c_str(), data.size()); - ::write(fdOut_, "\n", 1); + auto raw = msg.serialize(); // тип определяется сериализатором + + // Для текстовых форматов (std::string): + if constexpr (std::is_same_v) { + const std::string& data = raw; + ::write(fdOut_, data.c_str(), data.size()); + ::write(fdOut_, "\n", 1); + } + // Можно добавить другие форматы по мере необходимости } - + IpcMessage receive() override { if (fdIn_ < 0) { return IpcMessage{}; } - char buf[4096]; - const int n = ::read(fdIn_, buf, sizeof(buf) - 1); - if (n <= 0) { - return IpcMessage{}; + + // Для текстовых форматов: + if constexpr (std::is_same_v) { + char buf[4096]; + const int n = ::read(fdIn_, buf, sizeof(buf) - 1); + if (n <= 0) { + return IpcMessage{}; + } + buf[n] = 0; + return IpcMessage(std::string(buf)); } - buf[n] = 0; - return IpcMessage(std::string(buf)); + // Можно добавить другие форматы по мере необходимости + return IpcMessage{}; } private: diff --git a/include/ipc/IpcSerializer.h b/include/ipc/IpcSerializer.h new file mode 100644 index 0000000..98e1f98 --- /dev/null +++ b/include/ipc/IpcSerializer.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + +// Сериализаторы для IPC-сообщений. +// Каждый сериализатор определяет: +// - RawData - тип сырых (сериализованных) данных +// - serialize() - сериализация данных в RawData +// - deserialize() - десериализация из RawData + +// ===== Текстовый сериализатор (текущий формат) ===== +struct TextIpcSerializer { + // Тип сырых данных - строка (текстовый формат) + using RawData = std::string; + + static RawData serialize(const std::vector>& data) { + std::ostringstream out; + for (const auto& v : data) { + std::visit([&out](const auto& val) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + out << 'i' << ' ' << val << ' '; + } else if constexpr (std::is_same_v) { + out << 's' << ' ' << val.size() << ' '; + out.write(val.data(), static_cast(val.size())); + out << ' '; + } + }, v); + } + return out.str(); + } + + static std::vector> deserialize(const RawData& raw) { + std::vector> result; + std::istringstream in(raw); + + while (in.peek() != EOF) { + char tag{}; + in >> tag; + + if (tag == 'i') { + int v{}; + in >> v; + result.emplace_back(v); + } else if (tag == 's') { + std::size_t len{}; + in >> len; + in.get(); // пробел + std::string str(len, '\0'); + in.read(&str[0], static_cast(len)); + in.get(); // завершающий пробел + result.emplace_back(std::move(str)); + } + } + return result; + } +}; + +// ===== JSON сериализатор (пример для будущего использования, например yami4) ===== +struct JsonIpcSerializer { + // Тип сырых данных - строка (JSON - текстовый формат) + using RawData = std::string; + + static RawData serialize(const std::vector>& data) { + // Упрощенный JSON формат: ["i:42", "s:5:hello"] + // В реальности использовать библиотеку JSON + std::ostringstream out; + out << "["; + bool first = true; + for (const auto& v : data) { + if (!first) out << ","; + first = false; + std::visit([&out](const auto& val) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + out << "\"i:" << val << "\""; + } else if constexpr (std::is_same_v) { + // Экранирование для JSON (упрощенно) + out << "\"s:" << val.size() << ":"; + for (char c : val) { + if (c == '"' || c == '\\') out << '\\'; + out << c; + } + out << "\""; + } + }, v); + } + out << "]"; + return out.str(); + } + + static std::vector> deserialize(const RawData& raw) { + // Упрощенный парсинг JSON (в реальности использовать библиотеку JSON) + std::vector> result; + // TODO: реализовать парсинг JSON + return result; + } +}; +