bump readme
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
```text
|
||||
project/
|
||||
├── CMakeLists.txt
|
||||
├── README.md
|
||||
@@ -31,7 +31,8 @@ project/
|
||||
│ │ └── 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.
|
||||
|
||||
Reference in New Issue
Block a user