bump readme

master
Сергей Маринкевич 2 weeks ago
parent 47092fc6f1
commit 9f5969342e

@ -15,7 +15,7 @@
## Структура проекта
```
```text
project/
├── CMakeLists.txt
├── README.md
@ -24,14 +24,15 @@ project/
│ │ ├── BaseIpcMessage.h # шаблонный класс сообщения
│ │ ├── IpcChannel.h
│ │ ├── IpcCodec.h
│ │ ├── IpcConfig.h # type alias: using IpcMessage = BaseIpcMessage<TextIpcSerializer>
│ │ ├── IpcConfig.h # type alias: using IpcMessage = BaseIpcMessage<TextIpcSerializer>
│ │ ├── IpcDispatcher.h
│ │ ├── IpcPipeChannel.h
│ │ ├── IpcMarshaller.h
│ │ └── IpcSerializer.h # сериализаторы (TextIpcSerializer)
│ └── rpc/
│ ├── rpc_export.h
│ ├── RpcInvoker.h
│ ├── RpcRegistry.h # реестр RPC-объектов (скелетонов)
│ ├── RpcInvoker.h # тонкий фасад над RpcRegistry
│ └── RpcValue.h
├── src/
│ ├── client.cpp
@ -53,20 +54,37 @@ project/
## Архитектура (кратко)
* **Уровень 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>();`
* Построение (PoC):
`msg.add<int>(objectId); msg.add<std::string>("MyService.add"); msg.add<int>(7); msg.add<int>(8);`
* Разбор:
`auto id = msg.get<int>(); auto name = msg.get<std::string>(); auto a = msg.get<int>(); auto b = msg.get<int>();`
* Сериализация вынесена в отдельные сериализаторы (`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-ядра**:
* `IpcMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `IpcChannel` и читает ответ.
* `RpcInvoker` — по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы через `get<T>()`.
* `IpcMarshaller` — собирает `IpcMessage` из идентификатора объекта (`ObjectId`), имени метода и аргументов, отправляет через `IpcChannel` и читает ответ.
* `RpcRegistry` — владеет RPC-объектами (`IRpcObject`), каждому выдаётся целочисленный `ObjectId` (PoC: `int`, совместимый с `BaseIpcMessage`).
* `IRpcObject` — базовый интерфейс для серверных RPC-объектов (обычно скелетоны), реализующий виртуальный `invoke(method, args)`.
* `RpcInvoker` — тонкий фасад: по `ObjectId` берёт объект из `RpcRegistry` и зовёт `obj->invoke(method, args)`.
* **Сгенерированные обёртки**:
* `*.proxy.*` — шаблонные классы, зависящие только от абстрактного `impl` с методом `impl.callTyped<Ret>(method, args...)` и не знающие про конкретный транспорт.
* `*.skeleton.*` — используют `RpcInvoker` и принимают/возвращают `RpcValue` для диспетчеризации вызовов.
* `*.proxy.*` — шаблонные классы, зависящие только от абстрактного `impl` с методом
`impl.callTyped<Ret>(method, args...)` и не знающие про конкретный транспорт.
В PoC роль `impl` выполняет `IpcMarshaller`, которому при создании передаётся `ObjectId`.
Для разных удалённых объектов создаются разные экземпляры маршаллера (с разными `ObjectId`).
* `*.skeleton.*` — реализуют `IRpcObject`:
* внутри держат ссылку/указатель на реальный объект (`MyService`);
* в `invoke()` по имени метода ищут соответствующий хендлер в статической `std::unordered_map<std::string, Handler>`;
* хендлеры (`call_<method>`) распаковывают `RpcArgs`, вызывают реальный метод на `MyService` и упаковывают результат в `RpcValue`.
Так достигается поддержка **нескольких объектов одного и того же сервиса** на сервере:
каждому объекту соответствует свой skeleton, зарегистрированный в `RpcRegistry` под уникальным `ObjectId`,
а клиентский код адресует конкретный объект через `IpcMarshaller`, «прошитый» этим `ObjectId`.
---
@ -126,7 +144,7 @@ cmake --build build
В результате получаем два бинарника:
```
```text
./build/server
./build/client
```
@ -143,12 +161,15 @@ cmake --build build
python3 tools/generate_rpc.py \
--out-dir build/generated \
--compile-commands build/compile_commands.json \
src/MyService.h
--templates tools/templates \
--header src/MyService.h \
--source src/MyService.cpp \
--out-base MyService
```
Сгенерированные файлы попадут в:
```
```text
build/generated/
├─ MyService.proxy.h
├─ MyService.proxy.cpp
@ -166,7 +187,10 @@ build/generated/
./build/server
```
Сервер создаёт (при необходимости) именованные пайпы `/tmp/fifo_to_server` и `/tmp/fifo_to_client`, читает запросы из `fifo_to_server` и пишет ответы в `fifo_to_client` через `IpcPipeChannel`.
Сервер создаёт (при необходимости) именованные пайпы `/tmp/fifo_to_server` и `/tmp/fifo_to_client`,
читает запросы из `fifo_to_server` и пишет ответы в `fifo_to_client` через `IpcPipeChannel`.
На стороне сервера `RpcRegistry` регистрирует два объекта `MyService`, обёрнутых в `MyServiceSkeleton`,
под `ObjectId = 0` и `ObjectId = 1`.
2. В другом терминале — клиент:
@ -174,10 +198,24 @@ build/generated/
./build/client
```
Клиент также открывает эти FIFO, но логически пишет в `fifo_to_server` и читает из `fifo_to_client` (через тот же `IpcPipeChannel`), а пользовательский код видит только вызов `MyServiceProxy`.
Клиент также открывает эти FIFO, но логически пишет в `fifo_to_server` и читает из `fifo_to_client`
(через тот же `IpcPipeChannel`), а пользовательский код видит только два прокси `MyServiceProxy`,
привязанных к разным `ObjectId`.
**Ожидаемый вывод:**
```text
RESULT: 15
$ ./server &
$ ./client
OBJ1: 16 OBJ2: 16
$ ./client
OBJ1: 32 OBJ2: 48
$ ./client
OBJ1: 48 OBJ2: 96
$ ./client
OBJ1: 64 OBJ2: 160
```
где:
- первый объект (`ObjectId = 0`) увеличивает свой счётчик через `add(7, 9)` → счётчик = 16;
- второй объект (`ObjectId = 1`) увеличивает свой счётчик на `obj1.get()`, то есть также до 16.

Loading…
Cancel
Save