* `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с`IpcMessage`.
* **Уровень RPC-ядра**:
* `IpcMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `IpcChannel` и читает ответ.
* `RpcInvoker` — по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы через `get<T>()`.
* `IpcMarshaller` — собирает `IpcMessage` из идентификатора объекта (`ObjectId`), имени метода и аргументов, отправляет через `IpcChannel` и читает ответ.
* `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.