disaggregate IpcMessage container and serdes

master
Сергей Маринкевич 2 months ago
parent 43f67275e2
commit 8284a36883

@ -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<TextIpcSerializer>
│ │ ├── 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<TextIpcSerializer>`)
* Построение: `msg.add<std::string>("MyService.add"); msg.add<int>(7); msg.add<int>(8);`
* Разбор: `auto name = msg.get<std::string>(); auto a = msg.get<int>(); auto b = msg.get<int>();`
* Внутри хранится строка с простыми типовыми тегами (`i` для `int`, `s` для `std::string`), что позволяет в будущем перейти на бинарный формат без изменения API.
* Сериализация вынесена в отдельные сериализаторы (`TextIpcSerializer` и т.д.)
* Тип сырых данных параметризован через сериализатор (по умолчанию `std::string`, можно использовать `std::vector<std::byte>` для бинарных форматов)
* Выбор сериализатора делается один раз в `IpcConfig.h` через type alias
* **Уровень канала**: `RpcChannel` + `IpcPipeChannel`
* `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`.
* `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`.

@ -0,0 +1,80 @@
#pragma once
#include <string>
#include <vector>
#include <variant>
// Базовый шаблонный класс IPC-сообщения с API add<T>() / get<T>().
// Сериализация вынесена в отдельные сериализаторы (см. IpcSerializer.h).
// Конкретный тип сообщения определяется через type alias в IpcConfig.h.
template<typename Serializer>
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<typename T>
void add(const T& v) {
data_.emplace_back(v);
}
// Чтение входящего сообщения по частям.
template<typename T>
T get() {
if (pos_ >= data_.size()) {
return T{}; // или throw
}
auto& v = data_[pos_++];
if constexpr (std::is_same_v<T, int>) {
if (auto* p = std::get_if<int>(&v)) {
return *p;
}
} else if constexpr (std::is_same_v<T, std::string>) {
if (auto* p = std::get_if<std::string>(&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<std::variant<int, std::string>> data_;
mutable std::size_t pos_{0};
};

@ -1,6 +1,6 @@
#pragma once
#include "IpcMessage.h"
#include "IpcConfig.h"
// Абстракция IPCканала, работающего с IpcMessage.
// Живёт отдельно от RPCкода, чтобы транспорт не зависел от конкретной RPCреализации.

@ -1,6 +1,6 @@
#pragma once
#include <ipc/IpcMessage.h>
#include <ipc/IpcConfig.h>
#include <rpc/RpcValue.h>
#include <string>

@ -0,0 +1,21 @@
#pragma once
// Конфигурация IPC: выбор конкретного сериализатора
// В один момент времени используется только один сериализатор,
// выбор делается через type alias для конкретного типа IpcMessage.
#include "BaseIpcMessage.h"
#include "IpcSerializer.h"
// По умолчанию используем текстовый сериализатор
// Для переключения на JSON достаточно заменить на:
// using IpcMessage = BaseIpcMessage<JsonIpcSerializer>;
using IpcMessage = BaseIpcMessage<TextIpcSerializer>;
// Альтернативный вариант через макрос (если нужен compile-time выбор):
// #ifdef USE_JSON_SERIALIZER
// using IpcMessage = BaseIpcMessage<JsonIpcSerializer>;
// #else
// using IpcMessage = BaseIpcMessage<TextIpcSerializer>;
// #endif

@ -1,117 +0,0 @@
#pragma once
#include <sstream>
#include <string>
// Примитивное IPCсообщение с API add<T>() / get<T>().
// Под капотом пока текстовый формат с типовыми тегами, но снаружи интерфейс не завязан на 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<typename T>
void add(const T& v);
// Чтение входящего сообщения по частям.
template<typename T>
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<int>(const int& v) {
out_ << 'i' << ' ' << v << ' ';
raw_ = out_.str();
}
template<>
inline int IpcMessage::get<int>() {
ensureInput();
char tag{};
in_ >> tag;
// в PoC просто доверяем, что тип совпадает
int v{};
in_ >> v;
return v;
}
// std::string
template<>
inline void IpcMessage::add<std::string>(const std::string& v) {
// формат: 's' <len> <bytes...>
out_ << 's' << ' ' << v.size() << ' ';
out_.write(v.data(), static_cast<std::streamsize>(v.size()));
out_ << ' ';
raw_ = out_.str();
}
template<>
inline std::string IpcMessage::get<std::string>() {
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<std::streamsize>(len));
// съесть завершающий пробел
in_.get();
return res;
}

@ -33,15 +33,24 @@ public:
if (fdOut_ < 0) {
return;
}
const std::string& data = msg.raw();
auto raw = msg.serialize(); // тип определяется сериализатором
// Для текстовых форматов (std::string):
if constexpr (std::is_same_v<typename IpcMessage::RawData, std::string>) {
const std::string& data = raw;
::write(fdOut_, data.c_str(), data.size());
::write(fdOut_, "\n", 1);
}
// Можно добавить другие форматы по мере необходимости
}
IpcMessage receive() override {
if (fdIn_ < 0) {
return IpcMessage{};
}
// Для текстовых форматов:
if constexpr (std::is_same_v<typename IpcMessage::RawData, std::string>) {
char buf[4096];
const int n = ::read(fdIn_, buf, sizeof(buf) - 1);
if (n <= 0) {
@ -50,6 +59,9 @@ public:
buf[n] = 0;
return IpcMessage(std::string(buf));
}
// Можно добавить другие форматы по мере необходимости
return IpcMessage{};
}
private:
int fdIn_{-1};

@ -0,0 +1,102 @@
#pragma once
#include <string>
#include <vector>
#include <variant>
#include <sstream>
// Сериализаторы для IPC-сообщений.
// Каждый сериализатор определяет:
// - RawData - тип сырых (сериализованных) данных
// - serialize() - сериализация данных в RawData
// - deserialize() - десериализация из RawData
// ===== Текстовый сериализатор (текущий формат) =====
struct TextIpcSerializer {
// Тип сырых данных - строка (текстовый формат)
using RawData = std::string;
static RawData serialize(const std::vector<std::variant<int, std::string>>& data) {
std::ostringstream out;
for (const auto& v : data) {
std::visit([&out](const auto& val) {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, int>) {
out << 'i' << ' ' << val << ' ';
} else if constexpr (std::is_same_v<T, std::string>) {
out << 's' << ' ' << val.size() << ' ';
out.write(val.data(), static_cast<std::streamsize>(val.size()));
out << ' ';
}
}, v);
}
return out.str();
}
static std::vector<std::variant<int, std::string>> deserialize(const RawData& raw) {
std::vector<std::variant<int, std::string>> 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<std::streamsize>(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<std::variant<int, std::string>>& 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<decltype(val)>;
if constexpr (std::is_same_v<T, int>) {
out << "\"i:" << val << "\"";
} else if constexpr (std::is_same_v<T, std::string>) {
// Экранирование для JSON (упрощенно)
out << "\"s:" << val.size() << ":";
for (char c : val) {
if (c == '"' || c == '\\') out << '\\';
out << c;
}
out << "\"";
}
}, v);
}
out << "]";
return out.str();
}
static std::vector<std::variant<int, std::string>> deserialize(const RawData& raw) {
// Упрощенный парсинг JSON (в реальности использовать библиотеку JSON)
std::vector<std::variant<int, std::string>> result;
// TODO: реализовать парсинг JSON
return result;
}
};
Loading…
Cancel
Save