Compare commits
4 Commits
43f67275e2
...
e7aa646a80
| Author | SHA1 | Date | |
|---|---|---|---|
| e7aa646a80 | |||
| 31d0496a93 | |||
| 0334b9a42b | |||
| 8284a36883 |
@@ -19,48 +19,54 @@
|
||||
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
|
||||
│ │ ├── IpcMarshaller.h
|
||||
│ │ └── IpcSerializer.h # сериализаторы (TextIpcSerializer)
|
||||
│ └── 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.
|
||||
* **Уровень канала**: `RpcChannel` + `IpcPipeChannel`
|
||||
* Сериализация вынесена в отдельные сериализаторы (`TextIpcSerializer` и т.д.)
|
||||
* Тип сырых данных параметризован через сериализатор (по умолчанию `std::string`, можно использовать `std::vector<std::byte>` для бинарных форматов)
|
||||
* Выбор сериализатора делается один раз в `IpcConfig.h` через type alias
|
||||
* **Уровень канала**: `IpcChannel` + `IpcPipeChannel`
|
||||
* `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`.
|
||||
* `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`.
|
||||
* **Уровень RPC-ядра**:
|
||||
* `ProxyMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `RpcChannel` и читает ответ.
|
||||
* `IpcMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `IpcChannel` и читает ответ.
|
||||
* `RpcInvoker` — по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы через `get<T>()`.
|
||||
* **Сгенерированные обёртки**:
|
||||
* `*.proxy.*` — используют `ProxyMarshaller` и `RpcChannel`, не зависят от конкретного транспорта.
|
||||
* `*.skeleton.*` — используют `RpcInvoker` и принимают/возвращают `IpcMessage` для диспетчеризации вызовов.
|
||||
* `*.proxy.*` — шаблонные классы, зависящие только от абстрактного `impl` с методом `impl.callTyped<Ret>(method, args...)` и не знающие про конкретный транспорт.
|
||||
* `*.skeleton.*` — используют `RpcInvoker` и принимают/возвращают `RpcValue` для диспетчеризации вызовов.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <rpc/RpcInvoker.h>
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
// Серверный диспетчер, который получает IpcMessage с канала,
|
||||
// декодирует его в RPC-вызов, вызывает RpcInvoker и шлёт ответ.
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
#include <ipc/IpcCodec.h>
|
||||
#include <rpc/RpcValue.h>
|
||||
|
||||
class ProxyMarshaller {
|
||||
// Маршаллер, который знает, как превратить типизированный RPC-вызов
|
||||
// в IpcMessage и обратно. Живёт в IPC-слое и опирается на IpcChannel
|
||||
// и IpcCodec, но снаружи предъявляет только callTyped<T>(...).
|
||||
class IpcMarshaller {
|
||||
public:
|
||||
explicit ProxyMarshaller(IpcChannel& ch) : channel(ch) {}
|
||||
explicit IpcMarshaller(IpcChannel& ch)
|
||||
: channel(ch) {}
|
||||
|
||||
// Базовый type-erased вызов: принимает вектор RpcValue и возвращает RpcValue.
|
||||
RpcValue call(const std::string& method, const RpcArgs& args) {
|
||||
@@ -21,7 +25,7 @@ public:
|
||||
return IpcCodec::decodeResponse(resp);
|
||||
}
|
||||
|
||||
// Удобный шаблонный хелпер для сгенерированных прокси.
|
||||
// Типизированный хелпер: контрактом является только наличие этого метода.
|
||||
template<typename Ret, typename... Args>
|
||||
Ret callTyped(const std::string& method, const Args&... args) {
|
||||
RpcArgs packed;
|
||||
@@ -38,3 +42,4 @@ private:
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <iostream>
|
||||
|
||||
// IPC‑канал поверх именованных pipe.
|
||||
// Инкапсулирует работу с файловыми дескрипторами и обмен сообщениями IpcMessage.
|
||||
@@ -18,6 +20,31 @@ public:
|
||||
// При этом логически читаем только из readPipe, а пишем только в writePipe.
|
||||
fdIn_ = ::open(readPipe, O_RDWR);
|
||||
fdOut_ = ::open(writePipe, O_RDWR);
|
||||
|
||||
// Проверяем, что каналы открыты успешно
|
||||
if (fdIn_ < 0) {
|
||||
std::cerr << "Failed to open read pipe: " << readPipe << " (errno: " << errno << ")" << std::endl;
|
||||
}
|
||||
if (fdOut_ < 0) {
|
||||
std::cerr << "Failed to open write pipe: " << writePipe << " (errno: " << errno << ")" << std::endl;
|
||||
}
|
||||
|
||||
// Очищаем канал для чтения от остаточных данных
|
||||
// Это важно, чтобы избежать чтения старых сообщений при повторном открытии канала
|
||||
if (fdIn_ >= 0) {
|
||||
char buf[4096];
|
||||
// Устанавливаем неблокирующий режим для очистки
|
||||
int flags = ::fcntl(fdIn_, F_GETFL);
|
||||
::fcntl(fdIn_, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
// Читаем все доступные данные (старые сообщения)
|
||||
while (::read(fdIn_, buf, sizeof(buf)) > 0) {
|
||||
// Просто выбрасываем старые данные
|
||||
}
|
||||
|
||||
// Возвращаем блокирующий режим
|
||||
::fcntl(fdIn_, F_SETFL, flags);
|
||||
}
|
||||
}
|
||||
|
||||
~IpcPipeChannel() override {
|
||||
@@ -33,22 +60,64 @@ 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<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{};
|
||||
}
|
||||
char buf[4096];
|
||||
const int n = ::read(fdIn_, buf, sizeof(buf) - 1);
|
||||
if (n <= 0) {
|
||||
return IpcMessage{};
|
||||
|
||||
// Для текстовых форматов:
|
||||
if constexpr (std::is_same_v<typename IpcMessage::RawData, std::string>) {
|
||||
std::string line;
|
||||
char c;
|
||||
|
||||
// Читаем построчно до символа новой строки
|
||||
while (true) {
|
||||
const int n = ::read(fdIn_, &c, 1);
|
||||
if (n < 0) {
|
||||
// Ошибка чтения
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
// Неблокирующий режим и нет данных - это нормально, возвращаем пустое сообщение
|
||||
return IpcMessage{};
|
||||
}
|
||||
// Другая ошибка - логируем и возвращаем пустое сообщение
|
||||
std::cerr << "read() error: " << errno << std::endl;
|
||||
return IpcMessage{};
|
||||
}
|
||||
if (n == 0) {
|
||||
// EOF - канал закрыт на стороне записи или нет открытых дескрипторов записи
|
||||
// Для именованных каналов это может означать, что клиент еще не открыл канал для записи
|
||||
// В этом случае read() должен блокироваться, но если канал открыт в O_RDWR,
|
||||
// то read() может вернуть 0 немедленно
|
||||
// Если мы уже прочитали что-то, возвращаем это, иначе пустое сообщение
|
||||
if (line.empty()) {
|
||||
return IpcMessage{};
|
||||
}
|
||||
break; // EOF, но есть данные - возвращаем их
|
||||
}
|
||||
if (c == '\n') {
|
||||
break; // Достигли конца строки
|
||||
}
|
||||
line += c;
|
||||
}
|
||||
|
||||
if (line.empty()) {
|
||||
return IpcMessage{};
|
||||
}
|
||||
return IpcMessage(line);
|
||||
}
|
||||
buf[n] = 0;
|
||||
return IpcMessage(std::string(buf));
|
||||
// Можно добавить другие форматы по мере необходимости
|
||||
return IpcMessage{};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
+6
-1
@@ -1,5 +1,10 @@
|
||||
#include "MyService.h"
|
||||
|
||||
int MyService::add(int a, int b) {
|
||||
return a + b;
|
||||
counter_ += (a + b);
|
||||
return counter_;
|
||||
}
|
||||
|
||||
int MyService::get() {
|
||||
return counter_;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
// annotate with clang attribute or via comment annotation recognized by libclang
|
||||
// Use ANNOTATE attribute supported by clang: __attribute__((annotate("export")))
|
||||
class RPC_EXPORT MyService {
|
||||
private:
|
||||
int counter_ = 0;
|
||||
|
||||
public:
|
||||
RPC_EXPORT
|
||||
int add(int a, int b);
|
||||
RPC_EXPORT
|
||||
int get();
|
||||
int minus(int a, int b);
|
||||
};
|
||||
|
||||
+7
-6
@@ -1,6 +1,6 @@
|
||||
#include "MyService.proxy.h"
|
||||
#include "ipc/IpcPipeChannel.h"
|
||||
#include "proxy/ProxyMarshaller.h"
|
||||
#include "ipc/IpcMarshaller.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -16,10 +16,11 @@ int main() {
|
||||
// и читает из fifo, в который пишет сервер (fifo_to_client).
|
||||
IpcPipeChannel ch("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
||||
|
||||
// RPC‑уровень: создаём marshaller поверх канала и передаём его в прокси.
|
||||
ProxyMarshaller marshaller(ch);
|
||||
MyServiceProxy proxy(marshaller);
|
||||
// RPC‑уровень: создаём IpcMarshaller поверх канала и передаём его в прокси.
|
||||
IpcMarshaller marshaller(ch);
|
||||
MyServiceProxy<IpcMarshaller> proxy(marshaller);
|
||||
|
||||
int r = proxy.add(7, 9);
|
||||
std::cout << "RESULT: " << r << std::endl;
|
||||
proxy.add(7, 9);
|
||||
int counter = proxy.get();
|
||||
std::cout << "RESULT: " << counter << std::endl;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
#include "{{ cls.name }}.proxy.h"
|
||||
|
||||
{{ cls.name }}Proxy::{{ cls.name }}Proxy(ProxyMarshaller& marshaller)
|
||||
: impl(marshaller) {}
|
||||
|
||||
{% for m in cls.methods %}
|
||||
{{ m.return_type }} {{ cls.name }}Proxy::{{ m.name }}({% for a in m.args %}{{ a.type }} {{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %}) {
|
||||
return impl.callTyped<{{ m.return_type }}>("{{ cls.name }}.{{ m.name }}"{% for a in m.args %}, {{ a.name }}{% endfor %});
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
// Реализация шаблонного прокси целиком находится в заголовочном файле.
|
||||
// Этот cpp остаётся пустым, чтобы сгенерированные файлы по‑прежнему
|
||||
// могли участвовать в сборке как отдельная единица трансляции.
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "proxy/ProxyMarshaller.h"
|
||||
#include "{{ cls.name }}.h"
|
||||
|
||||
// Шаблонный прокси, который зависит только от контракта Impl:
|
||||
// Impl должен предоставлять метод
|
||||
// template<typename Ret, typename... Args>
|
||||
// Ret callTyped(const std::string& method, const Args&... args);
|
||||
template<typename Impl>
|
||||
class {{ cls.name }}Proxy {
|
||||
public:
|
||||
explicit {{ cls.name }}Proxy(ProxyMarshaller& marshaller);
|
||||
explicit {{ cls.name }}Proxy(Impl& impl)
|
||||
: impl(impl) {}
|
||||
|
||||
{% for m in cls.methods %}
|
||||
{{ m.return_type }} {{ m.name }}({% for a in m.args %}{{ a.type }} {{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %});
|
||||
{{ m.return_type }} {{ m.name }}({% for a in m.args %}{{ a.type }} {{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %}) {
|
||||
return impl.template callTyped<{{ m.return_type }}>("{{ cls.name }}.{{ m.name }}"{% for a in m.args %}, {{ a.name }}{% endfor %});
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
private:
|
||||
ProxyMarshaller& impl;
|
||||
Impl& impl;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user