Compare commits
10 Commits
7ce411b643
...
40170d553d
| Author | SHA1 | Date | |
|---|---|---|---|
| 40170d553d | |||
| 9f48a74677 | |||
| 785fca07f1 | |||
| 4433568545 | |||
| 7495f540d2 | |||
| c6fed622ee | |||
| 66c4e5190b | |||
| 0712460db0 | |||
| 99ea8d1646 | |||
| 1f16ef6b37 |
+43
-26
@@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
|
|
||||||
# include source includes
|
# include source includes
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
# find python interpreter
|
# find python interpreter
|
||||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||||
@@ -16,38 +17,54 @@ file(MAKE_DIRECTORY ${GENERATED_DIR})
|
|||||||
set(RPC_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_rpc.py)
|
set(RPC_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_rpc.py)
|
||||||
set(RPC_TEMPLATES ${CMAKE_CURRENT_SOURCE_DIR}/tools/templates)
|
set(RPC_TEMPLATES ${CMAKE_CURRENT_SOURCE_DIR}/tools/templates)
|
||||||
|
|
||||||
# inputs to parse
|
# helper to generate RPC code for an annotated header/source pair
|
||||||
set(RPC_INPUTS
|
function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.h
|
set(out_proxy_h "${GENERATED_DIR}/${OUT_BASENAME}.proxy.h")
|
||||||
)
|
set(out_proxy_cpp "${GENERATED_DIR}/${OUT_BASENAME}.proxy.cpp")
|
||||||
|
set(out_skeleton_h "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.h")
|
||||||
|
set(out_skeleton_cpp "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.cpp")
|
||||||
|
|
||||||
# command to run generator
|
add_custom_command(
|
||||||
add_custom_command(
|
OUTPUT
|
||||||
OUTPUT
|
${out_proxy_h}
|
||||||
${GENERATED_DIR}/MyService.proxy.h
|
${out_proxy_cpp}
|
||||||
${GENERATED_DIR}/MyService.proxy.cpp
|
${out_skeleton_h}
|
||||||
${GENERATED_DIR}/MyService.skeleton.h
|
${out_skeleton_cpp}
|
||||||
${GENERATED_DIR}/MyService.skeleton.cpp
|
COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs for ${OUT_BASENAME}..."
|
||||||
COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs..."
|
COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR}
|
||||||
COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR}
|
--compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json
|
||||||
--compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json
|
--templates ${RPC_TEMPLATES}
|
||||||
--templates ${RPC_TEMPLATES}
|
--header ${HEADER}
|
||||||
${RPC_INPUTS}
|
--source ${SOURCE}
|
||||||
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${RPC_INPUTS}
|
--out-base ${OUT_BASENAME}
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${HEADER} ${SOURCE}
|
||||||
COMMENT "Running RPC code generator"
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
VERBATIM
|
COMMENT "Running RPC code generator for ${OUT_BASENAME}"
|
||||||
)
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
# accumulate all generated files into a global property so we can
|
||||||
|
# have one umbrella target that depends on all of them
|
||||||
|
set_property(GLOBAL APPEND PROPERTY AUTOCODE_GENERATED_FILES
|
||||||
|
${out_proxy_h}
|
||||||
|
${out_proxy_cpp}
|
||||||
|
${out_skeleton_h}
|
||||||
|
${out_skeleton_cpp}
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
include_directories(${GENERATED_DIR})
|
include_directories(${GENERATED_DIR})
|
||||||
|
|
||||||
add_custom_target(rpc_generated DEPENDS
|
# declare all annotated files here
|
||||||
${GENERATED_DIR}/MyService.proxy.h
|
autocode_annotated_file(MyService
|
||||||
${GENERATED_DIR}/MyService.proxy.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.h
|
||||||
${GENERATED_DIR}/MyService.skeleton.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.cpp
|
||||||
${GENERATED_DIR}/MyService.skeleton.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# umbrella target that depends on all generated RPC files
|
||||||
|
get_property(_all_generated GLOBAL PROPERTY AUTOCODE_GENERATED_FILES)
|
||||||
|
add_custom_target(rpc_generated DEPENDS ${_all_generated})
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
add_executable(server
|
add_executable(server
|
||||||
src/server.cpp
|
src/server.cpp
|
||||||
|
|||||||
@@ -21,9 +21,19 @@ project/
|
|||||||
├── README.md
|
├── README.md
|
||||||
├── src
|
├── src
|
||||||
│ ├── client.cpp
|
│ ├── client.cpp
|
||||||
|
│ ├── common
|
||||||
|
│ │ ├── ipc
|
||||||
|
│ │ │ ├── IpcChannel.h
|
||||||
|
│ │ │ ├── IpcMessage.h
|
||||||
|
│ │ │ └── IpcPipeChannel.h
|
||||||
|
│ │ ├── proxy
|
||||||
|
│ │ │ └── ProxyMarshaller.h
|
||||||
|
│ │ └── rpc
|
||||||
|
│ │ ├── rpc_export.h
|
||||||
|
│ │ ├── RpcInvoker.h
|
||||||
|
│ │ └── RpcSerializer.h
|
||||||
│ ├── MyService.cpp
|
│ ├── MyService.cpp
|
||||||
│ ├── MyService.h
|
│ ├── MyService.h
|
||||||
│ ├── rpc_export.h
|
|
||||||
│ └── server.cpp
|
│ └── server.cpp
|
||||||
├── tools
|
├── tools
|
||||||
│ ├── generate_rpc.py
|
│ ├── generate_rpc.py
|
||||||
@@ -37,6 +47,24 @@ project/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Архитектура (кратко)
|
||||||
|
|
||||||
|
* **Уровень IPC-сообщений**: `IpcMessage`
|
||||||
|
* Построение: `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`
|
||||||
|
* `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`.
|
||||||
|
* `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`.
|
||||||
|
* **Уровень RPC-ядра**:
|
||||||
|
* `ProxyMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `RpcChannel` и читает ответ.
|
||||||
|
* `RpcInvoker` — по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы через `get<T>()`.
|
||||||
|
* **Сгенерированные обёртки**:
|
||||||
|
* `*.proxy.*` — используют `ProxyMarshaller` и `RpcChannel`, не зависят от конкретного транспорта.
|
||||||
|
* `*.skeleton.*` — используют `RpcInvoker` и принимают/возвращают `IpcMessage` для диспетчеризации вызовов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
* CMake ≥ 3.10
|
* CMake ≥ 3.10
|
||||||
@@ -130,17 +158,21 @@ build/generated/
|
|||||||
1. В одном терминале запускаем сервер:
|
1. В одном терминале запускаем сервер:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./server
|
./build/server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Сервер создаёт (при необходимости) именованные пайпы `/tmp/fifo_to_server` и `/tmp/fifo_to_client`, читает запросы из `fifo_to_server` и пишет ответы в `fifo_to_client` через `IpcPipeChannel`.
|
||||||
|
|
||||||
2. В другом терминале — клиент:
|
2. В другом терминале — клиент:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./client
|
./build/client
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Клиент также открывает эти FIFO, но логически пишет в `fifo_to_server` и читает из `fifo_to_client` (через тот же `IpcPipeChannel`), а пользовательский код видит только вызов `MyServiceProxy`.
|
||||||
|
|
||||||
**Ожидаемый вывод:**
|
**Ожидаемый вывод:**
|
||||||
|
|
||||||
```
|
```text
|
||||||
RESULT: 15
|
RESULT: 15
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IpcMessage.h"
|
||||||
|
|
||||||
|
// Абстракция IPC‑канала, работающего с IpcMessage.
|
||||||
|
// Живёт отдельно от RPC‑кода, чтобы транспорт не зависел от конкретной RPC‑реализации.
|
||||||
|
class IpcChannel {
|
||||||
|
public:
|
||||||
|
virtual ~IpcChannel() = default;
|
||||||
|
|
||||||
|
virtual void send(const IpcMessage& msg) = 0;
|
||||||
|
virtual IpcMessage receive() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#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 {
|
||||||
|
return raw_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string raw_;
|
||||||
|
std::ostringstream out_;
|
||||||
|
std::istringstream in_;
|
||||||
|
bool in_initialized_{false};
|
||||||
|
|
||||||
|
void ensureInput() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IpcChannel.h"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// IPC‑канал поверх именованных pipe.
|
||||||
|
// Инкапсулирует работу с файловыми дескрипторами и обмен сообщениями IpcMessage.
|
||||||
|
// readPipe — тот FIFO, который этот endpoint читает; writePipe — тот, в который пишет.
|
||||||
|
|
||||||
|
class IpcPipeChannel : public IpcChannel {
|
||||||
|
public:
|
||||||
|
IpcPipeChannel(const char* readPipe, const char* writePipe) {
|
||||||
|
// Канал не создаёт FIFO, только открывает.
|
||||||
|
// Открываем оба конца как O_RDWR, чтобы избежать блокировок на open(O_RDONLY/O_WRONLY).
|
||||||
|
// При этом логически читаем только из readPipe, а пишем только в writePipe.
|
||||||
|
fdIn_ = ::open(readPipe, O_RDWR);
|
||||||
|
fdOut_ = ::open(writePipe, O_RDWR);
|
||||||
|
}
|
||||||
|
|
||||||
|
~IpcPipeChannel() override {
|
||||||
|
if (fdIn_ >= 0) {
|
||||||
|
::close(fdIn_);
|
||||||
|
}
|
||||||
|
if (fdOut_ >= 0) {
|
||||||
|
::close(fdOut_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(const IpcMessage& msg) override {
|
||||||
|
if (fdOut_ < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string& data = msg.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{};
|
||||||
|
}
|
||||||
|
buf[n] = 0;
|
||||||
|
return IpcMessage(std::string(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fdIn_{-1};
|
||||||
|
int fdOut_{-1};
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ipc/IpcChannel.h>
|
||||||
|
|
||||||
|
class ProxyMarshaller {
|
||||||
|
public:
|
||||||
|
explicit ProxyMarshaller(IpcChannel& ch) : channel(ch) {}
|
||||||
|
|
||||||
|
template<typename Ret, typename... Args>
|
||||||
|
Ret call(const std::string& method, const Args&... args) {
|
||||||
|
IpcMessage msg;
|
||||||
|
|
||||||
|
// имя метода
|
||||||
|
msg.add(method);
|
||||||
|
|
||||||
|
// аргументы
|
||||||
|
(msg.add(args), ...);
|
||||||
|
|
||||||
|
// отправить
|
||||||
|
channel.send(msg);
|
||||||
|
|
||||||
|
// получить ответ
|
||||||
|
IpcMessage resp = channel.receive();
|
||||||
|
return resp.template get<Ret>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
IpcChannel& channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "RpcSerializer.h"
|
#include <ipc/IpcMessage.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -15,26 +14,24 @@ public:
|
|||||||
const std::string& name,
|
const std::string& name,
|
||||||
Ret (Obj::*method)(Args...)) {
|
Ret (Obj::*method)(Args...)) {
|
||||||
handlers[name] =
|
handlers[name] =
|
||||||
[instance, method](const std::string& req) -> std::string {
|
[instance, method](const IpcMessage& req) -> IpcMessage {
|
||||||
std::istringstream in(req);
|
IpcMessage msg = req;
|
||||||
|
|
||||||
// пропустить имя метода
|
// пропустить имя метода
|
||||||
std::string skip;
|
(void)msg.template get<std::string>();
|
||||||
in >> skip;
|
|
||||||
|
|
||||||
// читать аргументы и вызвать метод
|
// читать аргументы и вызвать метод
|
||||||
return callMethod<Ret, Obj, Args...>(instance, method, in);
|
return callMethod<Ret, Obj, Args...>(instance, method, msg);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string dispatch(const std::string& request) {
|
IpcMessage dispatch(const IpcMessage& request) {
|
||||||
std::istringstream in(request);
|
IpcMessage tmp = request;
|
||||||
std::string method;
|
const std::string method = tmp.get<std::string>();
|
||||||
in >> method;
|
|
||||||
|
|
||||||
auto it = handlers.find(method);
|
auto it = handlers.find(method);
|
||||||
if (it == handlers.end()) {
|
if (it == handlers.end()) {
|
||||||
return "ERR";
|
return IpcMessage{};
|
||||||
}
|
}
|
||||||
|
|
||||||
return it->second(request);
|
return it->second(request);
|
||||||
@@ -42,25 +39,25 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename Ret, typename Obj, typename... Args>
|
template<typename Ret, typename Obj, typename... Args>
|
||||||
static std::string callMethod(Obj* obj,
|
static IpcMessage callMethod(Obj* obj,
|
||||||
Ret (Obj::*method)(Args...),
|
Ret (Obj::*method)(Args...),
|
||||||
std::istringstream& in) {
|
IpcMessage& msg) {
|
||||||
auto tuple = readArgs<Args...>(in);
|
auto tuple = readArgs<Args...>(msg);
|
||||||
Ret result =
|
Ret result =
|
||||||
std::apply(method, std::tuple_cat(std::make_tuple(obj), tuple));
|
std::apply(method, std::tuple_cat(std::make_tuple(obj), tuple));
|
||||||
|
|
||||||
std::ostringstream out;
|
IpcMessage out;
|
||||||
RpcSerializer::write(out, result);
|
out.add(result);
|
||||||
return out.str();
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
static std::tuple<Args...> readArgs(std::istringstream& in) {
|
static std::tuple<Args...> readArgs(IpcMessage& msg) {
|
||||||
return std::tuple<Args...>{RpcSerializer::read<Args>(in)...};
|
return std::tuple<Args...>{msg.template get<Args>()...};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<std::string,
|
std::unordered_map<std::string,
|
||||||
std::function<std::string(const std::string&)>>
|
std::function<IpcMessage(const IpcMessage&)>>
|
||||||
handlers;
|
handlers;
|
||||||
};
|
};
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <rpc_export.h>
|
#include <rpc/rpc_export.h>
|
||||||
|
|
||||||
// annotate with clang attribute or via comment annotation recognized by libclang
|
// annotate with clang attribute or via comment annotation recognized by libclang
|
||||||
// Use ANNOTATE attribute supported by clang: __attribute__((annotate("export")))
|
// Use ANNOTATE attribute supported by clang: __attribute__((annotate("export")))
|
||||||
|
|||||||
+14
-37
@@ -1,48 +1,25 @@
|
|||||||
#include "MyService.proxy.h"
|
#include "MyService.proxy.h"
|
||||||
#include "rpc/RpcChannel.h"
|
#include "ipc/IpcPipeChannel.h"
|
||||||
|
#include "proxy/ProxyMarshaller.h"
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RpcChannelFifoClient : public RpcChannel {
|
|
||||||
public:
|
|
||||||
RpcChannelFifoClient(const char* inPipe, const char* outPipe) {
|
|
||||||
fdOut = open(inPipe, O_WRONLY);
|
|
||||||
fdIn = open(outPipe, O_RDONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send(const std::string& data) override {
|
|
||||||
::write(fdOut, data.c_str(), data.size());
|
|
||||||
::write(fdOut, "\n", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string receive() override {
|
|
||||||
char buf[4096];
|
|
||||||
int n = ::read(fdIn, buf, sizeof(buf) - 1);
|
|
||||||
if (n <= 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
buf[n] = 0;
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int fdIn{};
|
|
||||||
int fdOut{};
|
|
||||||
};
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
mkfifo("/tmp/rpc_in", 0666);
|
// Создание FIFO — часть пользовательского IPC‑кода.
|
||||||
mkfifo("/tmp/rpc_out", 0666);
|
mkfifo("/tmp/fifo_to_server", 0666);
|
||||||
|
mkfifo("/tmp/fifo_to_client", 0666);
|
||||||
|
|
||||||
RpcChannelFifoClient ch("/tmp/rpc_in", "/tmp/rpc_out");
|
// IPC‑уровень: канал поверх pipe.
|
||||||
MyServiceProxy proxy(ch);
|
// Клиент пишет в fifo, который читает сервер (fifo_to_server),
|
||||||
|
// и читает из fifo, в который пишет сервер (fifo_to_client).
|
||||||
|
IpcPipeChannel ch("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
||||||
|
|
||||||
int r = proxy.add(7, 8);
|
// RPC‑уровень: создаём marshaller поверх канала и передаём его в прокси.
|
||||||
|
ProxyMarshaller marshaller(ch);
|
||||||
|
MyServiceProxy proxy(marshaller);
|
||||||
|
|
||||||
|
int r = proxy.add(7, 9);
|
||||||
std::cout << "RESULT: " << r << std::endl;
|
std::cout << "RESULT: " << r << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class RpcChannel {
|
|
||||||
public:
|
|
||||||
virtual ~RpcChannel() = default;
|
|
||||||
|
|
||||||
virtual void send(const std::string& data) = 0;
|
|
||||||
virtual std::string receive() = 0;
|
|
||||||
};
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#include "RpcChannel.h"
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RpcChannelFifo : public RpcChannel {
|
|
||||||
public:
|
|
||||||
RpcChannelFifo(const char* inPipe, const char* outPipe) {
|
|
||||||
fdOut = open(inPipe, O_WRONLY);
|
|
||||||
fdIn = open(outPipe, O_RDONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send(const std::string& data) override {
|
|
||||||
::write(fdOut, data.c_str(), data.size());
|
|
||||||
::write(fdOut, "\n", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string receive() override {
|
|
||||||
char buf[4096];
|
|
||||||
int n = ::read(fdIn, buf, sizeof(buf) - 1);
|
|
||||||
if (n <= 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
buf[n] = 0;
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int fdIn{};
|
|
||||||
int fdOut{};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "RpcChannel.h"
|
|
||||||
#include "RpcSerializer.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RpcClient {
|
|
||||||
public:
|
|
||||||
explicit RpcClient(RpcChannel& ch) : channel(ch) {}
|
|
||||||
|
|
||||||
template<typename Ret, typename... Args>
|
|
||||||
Ret call(const std::string& method, const Args&... args) {
|
|
||||||
std::ostringstream out;
|
|
||||||
|
|
||||||
// имя метода
|
|
||||||
RpcSerializer::write(out, method);
|
|
||||||
|
|
||||||
// аргументы
|
|
||||||
(RpcSerializer::write(out, args), ...);
|
|
||||||
|
|
||||||
// отправить
|
|
||||||
channel.send(out.str());
|
|
||||||
|
|
||||||
// получить ответ
|
|
||||||
std::string resp = channel.receive();
|
|
||||||
std::istringstream in(resp);
|
|
||||||
|
|
||||||
return RpcSerializer::read<Ret>(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RpcChannel& channel;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
+10
-38
@@ -1,57 +1,29 @@
|
|||||||
#include "MyService.h"
|
#include "MyService.h"
|
||||||
#include "MyService.skeleton.h"
|
#include "MyService.skeleton.h"
|
||||||
|
|
||||||
#include "rpc/RpcChannel.h"
|
#include "ipc/IpcPipeChannel.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class RpcChannelFifoServer : public RpcChannel {
|
|
||||||
public:
|
|
||||||
RpcChannelFifoServer(const char* inPipe, const char* outPipe) {
|
|
||||||
fdIn = open(inPipe, O_RDONLY);
|
|
||||||
fdOut = open(outPipe, O_WRONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send(const std::string& data) override {
|
|
||||||
::write(fdOut, data.c_str(), data.size());
|
|
||||||
::write(fdOut, "\n", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string receive() override {
|
|
||||||
char buf[4096];
|
|
||||||
int n = ::read(fdIn, buf, sizeof(buf) - 1);
|
|
||||||
if (n <= 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
buf[n] = 0;
|
|
||||||
return std::string(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int fdIn{};
|
|
||||||
int fdOut{};
|
|
||||||
};
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
mkfifo("/tmp/rpc_in", 0666);
|
// Создание FIFO — часть пользовательского IPC‑кода.
|
||||||
mkfifo("/tmp/rpc_out", 0666);
|
mkfifo("/tmp/fifo_to_server", 0666);
|
||||||
|
mkfifo("/tmp/fifo_to_client", 0666);
|
||||||
|
|
||||||
RpcChannelFifoServer ch("/tmp/rpc_in", "/tmp/rpc_out");
|
// IPC‑уровень: канал поверх pipe.
|
||||||
|
// Сервер читает из fifo_to_server и пишет в fifo_to_client.
|
||||||
|
IpcPipeChannel ch("/tmp/fifo_to_server", "/tmp/fifo_to_client");
|
||||||
|
|
||||||
|
// RPC‑уровень: скелет поверх того же канала.
|
||||||
MyService realObj;
|
MyService realObj;
|
||||||
MyServiceSkeleton skeleton(realObj);
|
MyServiceSkeleton skeleton(realObj);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
std::string req = ch.receive();
|
IpcMessage req = ch.receive();
|
||||||
if (req.empty()) {
|
if (req.empty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::string resp = skeleton.dispatch(req);
|
IpcMessage resp = skeleton.dispatch(req);
|
||||||
ch.send(resp);
|
ch.send(resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-20
@@ -73,11 +73,22 @@ def load_compile_flags(compile_commands_path, src_path):
|
|||||||
if skip_next:
|
if skip_next:
|
||||||
skip_next = False
|
skip_next = False
|
||||||
continue
|
continue
|
||||||
if tok.endswith('g++') or tok.endswith('clang++') or tok.endswith('clang') or tok.endswith('g++') or tok.endswith('cc'):
|
# skip the compiler binary itself
|
||||||
|
if (
|
||||||
|
tok.endswith("g++")
|
||||||
|
or tok.endswith("clang++")
|
||||||
|
or tok.endswith("clang")
|
||||||
|
or tok.endswith("cc")
|
||||||
|
or tok.endswith("c++")
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
if tok == "-c":
|
if tok == "-c":
|
||||||
skip_next = True
|
skip_next = True
|
||||||
continue
|
continue
|
||||||
|
# drop output file flag: -o <file>
|
||||||
|
if tok == "-o":
|
||||||
|
skip_next = True
|
||||||
|
continue
|
||||||
filtered.append(tok)
|
filtered.append(tok)
|
||||||
return filtered
|
return filtered
|
||||||
return []
|
return []
|
||||||
@@ -183,7 +194,13 @@ def main():
|
|||||||
p = argparse.ArgumentParser()
|
p = argparse.ArgumentParser()
|
||||||
p.add_argument("--out-dir", "-o", required=True)
|
p.add_argument("--out-dir", "-o", required=True)
|
||||||
p.add_argument("--compile-commands", "-c", default=None)
|
p.add_argument("--compile-commands", "-c", default=None)
|
||||||
p.add_argument("inputs", nargs="+")
|
p.add_argument("--header", required=True, help="C++ header file to scan for exported RPC classes")
|
||||||
|
p.add_argument("--source", required=True, help="C++ source file to use for resolving compile flags (from compile_commands.json)")
|
||||||
|
p.add_argument(
|
||||||
|
"--out-base",
|
||||||
|
required=True,
|
||||||
|
help="Base name for generated files: <out-base>.proxy.[h|cpp], <out-base>.skeleton.[h|cpp]",
|
||||||
|
)
|
||||||
p.add_argument("--templates", "-t", default=os.path.join(os.path.dirname(__file__), "templates"))
|
p.add_argument("--templates", "-t", default=os.path.join(os.path.dirname(__file__), "templates"))
|
||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
|
|
||||||
@@ -199,28 +216,68 @@ def main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("WARNING: cannot set libclang path:", e)
|
print("WARNING: cannot set libclang path:", e)
|
||||||
|
|
||||||
|
# determine compile flags from the source file (which is what appears in compile_commands.json)
|
||||||
|
if not os.path.exists(args.header):
|
||||||
|
print("ERROR: header file not found:", args.header)
|
||||||
|
return 1
|
||||||
|
if not os.path.exists(args.source):
|
||||||
|
print("ERROR: source file not found:", args.source)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
compile_args = load_compile_flags(args.compile_commands, args.source)
|
||||||
|
if not any(a.startswith("-x") for a in compile_args):
|
||||||
|
compile_args = ["-x", "c++", "-std=c++17"] + compile_args
|
||||||
|
|
||||||
|
print("Parsing", args.header, "with args:", compile_args)
|
||||||
|
|
||||||
index = Index.create()
|
index = Index.create()
|
||||||
all_classes = []
|
classes = parse_file(index, args.header, compile_args)
|
||||||
|
|
||||||
for inp in args.inputs:
|
if not classes:
|
||||||
if not os.path.exists(inp):
|
print("No exported classes/methods found in header. Nothing to generate.")
|
||||||
print("WARN: input file not found:", inp)
|
|
||||||
continue
|
|
||||||
compile_args = load_compile_flags(args.compile_commands, inp)
|
|
||||||
# ensure -x c++ if missing
|
|
||||||
if not any(a.startswith("-x") for a in compile_args):
|
|
||||||
compile_args = ["-x", "c++", "-std=c++17"] + compile_args
|
|
||||||
print("Parsing", inp, "with args:", compile_args)
|
|
||||||
classes = parse_file(index, inp, compile_args)
|
|
||||||
all_classes.extend(classes)
|
|
||||||
|
|
||||||
if not all_classes:
|
|
||||||
print("No exported classes/methods found. Nothing to generate.")
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# render templates
|
# For this PoC we expect a single exported service per header.
|
||||||
render_templates(all_classes, out_dir, args.templates)
|
# If there are multiple, refuse to guess which one should define the filenames.
|
||||||
print("Generated files for classes:", ", ".join(c.name for c in all_classes))
|
if len(classes) > 1:
|
||||||
|
print(
|
||||||
|
"ERROR: multiple exported classes found in header; "
|
||||||
|
"current generator expects exactly one when using --out-base."
|
||||||
|
)
|
||||||
|
for c in classes:
|
||||||
|
print(" -", c.name)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# render templates using the single discovered class but the user‑provided base name
|
||||||
|
cls = classes[0]
|
||||||
|
|
||||||
|
env = Environment(
|
||||||
|
loader=FileSystemLoader(args.templates),
|
||||||
|
autoescape=False,
|
||||||
|
trim_blocks=True,
|
||||||
|
lstrip_blocks=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
proxy_h = env.get_template("proxy.h.j2")
|
||||||
|
proxy_cpp = env.get_template("proxy.cpp.j2")
|
||||||
|
skeleton_h = env.get_template("skeleton.h.j2")
|
||||||
|
skeleton_cpp = env.get_template("skeleton.cpp.j2")
|
||||||
|
|
||||||
|
base = args.out_base
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{base}.proxy.h", "w") as f:
|
||||||
|
f.write(proxy_h.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{base}.proxy.cpp", "w") as f:
|
||||||
|
f.write(proxy_cpp.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{base}.skeleton.h", "w") as f:
|
||||||
|
f.write(skeleton_h.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{base}.skeleton.cpp", "w") as f:
|
||||||
|
f.write(skeleton_cpp.render(cls=cls))
|
||||||
|
|
||||||
|
print("Generated files for class", cls.name, "into base", base)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
#include "{{ cls.name }}.proxy.h"
|
#include "{{ cls.name }}.proxy.h"
|
||||||
#include "rpc/RpcClient.h"
|
|
||||||
|
|
||||||
class {{ cls.name }}Proxy::Impl {
|
{{ cls.name }}Proxy::{{ cls.name }}Proxy(ProxyMarshaller& marshaller)
|
||||||
public:
|
: impl(marshaller) {}
|
||||||
explicit Impl(RpcChannel& ch)
|
|
||||||
: client(ch) {}
|
|
||||||
|
|
||||||
RpcClient client;
|
|
||||||
};
|
|
||||||
|
|
||||||
{{ cls.name }}Proxy::{{ cls.name }}Proxy(RpcChannel& ch)
|
|
||||||
: impl(new Impl(ch)) {}
|
|
||||||
|
|
||||||
{% for m in cls.methods %}
|
{% 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 %}) {
|
{{ 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->client.call<{{ m.return_type }}>("{{ cls.name }}.{{ m.name }}"{% for a in m.args %}, {{ a.name }}{% endfor %});
|
return impl.call<{{ m.return_type }}>("{{ cls.name }}.{{ m.name }}"{% for a in m.args %}, {{ a.name }}{% endfor %});
|
||||||
}
|
}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "rpc/RpcChannel.h"
|
#include "proxy/ProxyMarshaller.h"
|
||||||
|
|
||||||
class {{ cls.name }}Proxy {
|
class {{ cls.name }}Proxy {
|
||||||
public:
|
public:
|
||||||
explicit {{ cls.name }}Proxy(RpcChannel& ch);
|
explicit {{ cls.name }}Proxy(ProxyMarshaller& marshaller);
|
||||||
{% for m in cls.methods %}
|
{% 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 %});
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class Impl;
|
ProxyMarshaller& impl;
|
||||||
Impl* impl;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string {{ cls.name }}Skeleton::dispatch(const std::string& req) {
|
IpcMessage {{ cls.name }}Skeleton::dispatch(const IpcMessage& req) {
|
||||||
return invoker.dispatch(req);
|
return invoker.dispatch(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,11 @@
|
|||||||
#include "{{ cls.name }}.h"
|
#include "{{ cls.name }}.h"
|
||||||
#include "rpc/RpcInvoker.h"
|
#include "rpc/RpcInvoker.h"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class {{ cls.name }}Skeleton {
|
class {{ cls.name }}Skeleton {
|
||||||
public:
|
public:
|
||||||
explicit {{ cls.name }}Skeleton({{ cls.name }}& obj);
|
explicit {{ cls.name }}Skeleton({{ cls.name }}& obj);
|
||||||
|
|
||||||
std::string dispatch(const std::string& req);
|
IpcMessage dispatch(const IpcMessage& req);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RpcInvoker invoker;
|
RpcInvoker invoker;
|
||||||
|
|||||||
Reference in New Issue
Block a user