|
|
2 weeks ago | |
|---|---|---|
| include | 2 weeks ago | |
| src | 2 weeks ago | |
| tools | 2 weeks ago | |
| .gitignore | 2 weeks ago | |
| CMakeLists.txt | 2 weeks ago | |
| README.md | 2 weeks ago | |
README.md
Minimal C++ RPC PoC with Auto-Code Generation
Простой Proof-of-Concept реализации RPC для C++ с автоматической генерацией прокси и скелета по аннотациям в исходниках.
Проект демонстрирует:
- Парсинг исходников C++ с помощью
libclangдля поиска аннотированных классов и методов. - Автоматическую генерацию файлов:
*.proxy.h/cpp— клиентский прокси для вызова удалённых методов.*.skeleton.h/cpp— серверный скелет для приёма запросов и вызова реальных методов.
- Минимальный протокол передачи данных через именованные каналы (FIFO).
- Поддержка только типов
intдля аргументов и возвращаемого значения (PoC).
Структура проекта
project/
├── CMakeLists.txt
├── README.md
├── 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/
│ ├── proxy.cpp.j2
│ ├── proxy.h.j2
│ ├── skeleton.cpp.j2
│ └── skeleton.h.j2
└── build/ # создаётся при сборке
Архитектура (кратко)
- Уровень 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>(); - Сериализация вынесена в отдельные сериализаторы (
TextIpcSerializerи т.д.) - Тип сырых данных параметризован через сериализатор (по умолчанию
std::string, можно использоватьstd::vector<std::byte>для бинарных форматов) - Выбор сериализатора делается один раз в
IpcConfig.hчерез type alias
- Построение:
- Уровень канала:
IpcChannel+IpcPipeChannelIpcChannel— абстракция транспорта:send(const IpcMessage&),receive() -> IpcMessage.IpcPipeChannel— реализация поверх двух FIFO (/tmp/fifo_to_server,/tmp/fifo_to_client), которая внутри работает со строками, но наружу — только сIpcMessage.
- Уровень RPC-ядра:
IpcMarshaller— собираетIpcMessageиз имени метода и аргументов, отправляет черезIpcChannelи читает ответ.RpcInvoker— по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы черезget<T>().
- Сгенерированные обёртки:
*.proxy.*— шаблонные классы, зависящие только от абстрактногоimplс методомimpl.callTyped<Ret>(method, args...)и не знающие про конкретный транспорт.*.skeleton.*— используютRpcInvokerи принимают/возвращаютRpcValueдля диспетчеризации вызовов.
Зависимости
-
CMake ≥ 3.10
-
GCC или Clang с поддержкой C++17
-
Python 3.8+ с пакетами:
pip3 install jinja2 clang -
libclang (для Python) Если GCC используется только для сборки — libclang нужен только для генератора.
Важно: Сам libclang и пакет clang для Python должны быть одной версии.
Аннотации для экспорта
Управление тем, какие атрибуты каких классов следует экспортировать происходит с помощью аннотаций в исходном коде.
См. файл MyService.h:
class RPC_EXPORT MyService {
public:
RPC_EXPORT
int add(int a, int b);
int minus(int a, int b);
};
- Экспортируем класс
MyService:- Экспортируем метод
MyService::add; - Но не экспортируем метод
MyService::minus.
- Экспортируем метод
Сборка проекта
- Конфигурируем CMake:
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
Директива CMAKE_EXPORT_COMPILE_COMMANDS=ON нужна для корректного парсинга в libclang.
- Собираем проект:
cmake --build build
В результате получаем два бинарника:
./build/server
./build/client
Генерация кода
Автоматическая генерация прокси и скелета происходит при сборке через Python-скрипт tools/generate_rpc.py.
Пример ручного запуска генератора:
python3 tools/generate_rpc.py \
--out-dir build/generated \
--compile-commands build/compile_commands.json \
src/MyService.h
Сгенерированные файлы попадут в:
build/generated/
├─ MyService.proxy.h
├─ MyService.proxy.cpp
├─ MyService.skeleton.h
└─ MyService.skeleton.cpp
Запуск
- В одном терминале запускаем сервер:
./build/server
Сервер создаёт (при необходимости) именованные пайпы /tmp/fifo_to_server и /tmp/fifo_to_client, читает запросы из fifo_to_server и пишет ответы в fifo_to_client через IpcPipeChannel.
- В другом терминале — клиент:
./build/client
Клиент также открывает эти FIFO, но логически пишет в fifo_to_server и читает из fifo_to_client (через тот же IpcPipeChannel), а пользовательский код видит только вызов MyServiceProxy.
Ожидаемый вывод:
RESULT: 15