2018-12-18 10:01:53 +08:00
|
|
|
|
# rest_rpc
|
2024-01-22 16:03:58 +08:00
|
|
|
|
|
|
|
|
|
|
| OS (Compiler Version) | Status |
|
|
|
|
|
|
|------------------------------------------------|----------------------------------------------------------------------------------------------------------|
|
|
|
|
|
|
| Ubuntu 22.04 (clang 14.0.0) |  |
|
|
|
|
|
|
| Ubuntu 22.04 (gcc 11.2.0) |  |
|
|
|
|
|
|
| macOS Monterey 12 (AppleClang 14.0.0.14000029) |  |
|
|
|
|
|
|
| Windows Server 2022 (MSVC 19.33.31630.0) |  |
|
2021-01-05 23:30:56 +08:00
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
c++20, high performance, cross platform, easy to use rpc framework.
|
2018-12-20 13:47:06 +08:00
|
|
|
|
|
|
|
|
|
|
It's so easy to love RPC.
|
|
|
|
|
|
|
2018-12-20 13:49:32 +08:00
|
|
|
|
Modern C++开发的RPC库就是这么简单好用!
|
2018-12-20 13:47:06 +08:00
|
|
|
|
|
2019-06-05 08:56:29 +08:00
|
|
|
|
# rest_rpc简介
|
2019-05-09 13:43:49 +08:00
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
rest_rpc是一个高性能、易用、跨平台、header only的基于c++20 协程的rpc 库,它的目标是让tcp通信变得非常简单易用,即使不懂网络通信的人也可以直接使用它。它依赖header-only的standalone [asio](https://github.com/chriskohlhoff/asio)(tag:asio-1-36-0)
|
2022-01-16 14:52:04 +08:00
|
|
|
|
|
2019-06-05 08:56:29 +08:00
|
|
|
|
可以快速上手,使用者只需要关注自己的业务逻辑即可。
|
2019-05-09 13:43:49 +08:00
|
|
|
|
|
2025-10-16 10:37:18 +08:00
|
|
|
|
# 编译器版本
|
|
|
|
|
|
需要完全支持c++20 的编译器:
|
|
|
|
|
|
|
2025-10-16 10:53:26 +08:00
|
|
|
|
gcc12+, clang15+, msvc2022
|
2025-10-16 10:37:18 +08:00
|
|
|
|
|
2020-10-15 20:31:51 +08:00
|
|
|
|
# 谁在用rest_rpc
|
|
|
|
|
|
|
|
|
|
|
|
1. 博世汽车
|
|
|
|
|
|
|
2020-10-15 20:42:27 +08:00
|
|
|
|
[在这里增加用户](https://github.com/qicosmos/rest_rpc/wiki/%E4%BD%BF%E7%94%A8rest_rpc%E7%9A%84%E7%94%A8%E6%88%B7%E5%88%97%E8%A1%A8)
|
|
|
|
|
|
|
2019-06-05 08:56:29 +08:00
|
|
|
|
# rest_rpc的特点
|
2018-12-20 13:47:06 +08:00
|
|
|
|
|
2019-06-05 08:56:29 +08:00
|
|
|
|
## 易
|
2018-12-20 13:47:06 +08:00
|
|
|
|
|
2019-06-05 08:56:29 +08:00
|
|
|
|
rest_rpc为用户提供了非常简单易用的接口,几行代码就可以实现rpc通信了,来看第一个例子
|
|
|
|
|
|
|
|
|
|
|
|
### 一个加法的rpc服务
|
|
|
|
|
|
|
2025-10-17 08:23:16 +08:00
|
|
|
|
```cpp
|
2019-06-05 08:56:29 +08:00
|
|
|
|
//服务端注册加法rpc服务
|
|
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
int add(rpc_conn conn, int a, int b) { return a + b; }
|
2019-06-05 08:56:29 +08:00
|
|
|
|
|
|
|
|
|
|
int main(){
|
2025-10-16 10:17:49 +08:00
|
|
|
|
rpc_server server("127.0.0.1:9004", std::thread::hardware_concurrency());
|
|
|
|
|
|
|
|
|
|
|
|
server.register_handler<add>();
|
2019-06-05 08:56:29 +08:00
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
server.start();
|
2019-06-05 08:56:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-17 08:23:16 +08:00
|
|
|
|
```cpp
|
2019-06-05 08:56:29 +08:00
|
|
|
|
//客户端调用加法的rpc服务
|
|
|
|
|
|
int main(){
|
2025-10-16 10:17:49 +08:00
|
|
|
|
auto rpc_call = []() -> asio::awaitable<void> {
|
|
|
|
|
|
rpc_client client;
|
|
|
|
|
|
auto ec = co_await client.connect("127.0.0.1:9004");
|
|
|
|
|
|
if(ec) {
|
2025-10-16 11:41:21 +08:00
|
|
|
|
REST_LOG_ERROR << ec.message();
|
2025-10-16 10:17:49 +08:00
|
|
|
|
co_return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto r = co_await client.call<add>(1, 2);
|
2025-10-17 08:23:16 +08:00
|
|
|
|
if(r.ec == rpc_errc::ok) {
|
2025-10-16 10:17:49 +08:00
|
|
|
|
REST_LOG_INFO << "call result: " << r.value;
|
|
|
|
|
|
assert(r.value == 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-17 20:05:54 +08:00
|
|
|
|
sync_wait(rpc_call());
|
2019-06-05 08:56:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 获取一个对象的rpc服务
|
|
|
|
|
|
|
2025-10-17 08:23:16 +08:00
|
|
|
|
```cpp
|
2019-06-05 08:56:29 +08:00
|
|
|
|
//1.先定义person对象
|
|
|
|
|
|
struct person {
|
2025-10-16 10:17:49 +08:00
|
|
|
|
int id;
|
|
|
|
|
|
std::string name;
|
|
|
|
|
|
int age;
|
2019-06-05 08:56:29 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-16 11:41:21 +08:00
|
|
|
|
//2.提供rpc服务
|
2025-10-16 10:17:49 +08:00
|
|
|
|
person get_person(person p) {
|
|
|
|
|
|
p.name = "jack";
|
|
|
|
|
|
return p;
|
2019-06-05 08:56:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int main(){
|
2025-10-16 10:17:49 +08:00
|
|
|
|
rpc_server server("127.0.0.1:9004", std::thread::hardware_concurrency());
|
|
|
|
|
|
server.register_handler<get_person>();
|
|
|
|
|
|
server.start();
|
2019-06-05 08:56:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-17 08:23:16 +08:00
|
|
|
|
```cpp
|
2019-06-05 08:56:29 +08:00
|
|
|
|
//客户端调用获取person对象的rpc服务
|
|
|
|
|
|
int main(){
|
2025-10-16 10:17:49 +08:00
|
|
|
|
auto rpc_call = []() -> asio::awaitable<void> {
|
|
|
|
|
|
rpc_client client;
|
|
|
|
|
|
auto ec = co_await client.connect("127.0.0.1:9004");
|
|
|
|
|
|
if(ec) {
|
2025-10-16 11:41:21 +08:00
|
|
|
|
REST_LOG_ERROR << ec.message();
|
2025-10-16 10:17:49 +08:00
|
|
|
|
co_return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
person p{1, "tom", 20};
|
|
|
|
|
|
auto r = co_await client.call<get_person>(p);
|
|
|
|
|
|
if(r.ec==rpc_errc::ok) {
|
|
|
|
|
|
REST_LOG_INFO << "call result: " << r.value.name;
|
|
|
|
|
|
assert(r.value.name == "jack");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-17 20:05:54 +08:00
|
|
|
|
sync_wait(rpc_call());
|
2019-06-05 08:56:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-10-16 11:08:58 +08:00
|
|
|
|
## 酷
|
|
|
|
|
|
rest_rpc 比较有特色的一个功能是支持订阅发布。
|
|
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
以订阅某个topic为例:
|
|
|
|
|
|
|
|
|
|
|
|
server 端代码:
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
void publish() {
|
|
|
|
|
|
rpc_server server("127.0.0.1:9004", 4);
|
|
|
|
|
|
server.async_start();
|
|
|
|
|
|
|
|
|
|
|
|
REST_LOG_INFO << "will pubish, waiting for input";
|
|
|
|
|
|
auto pub = [&]() -> asio::awaitable<void> {
|
|
|
|
|
|
std::string str;
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
std::cin >> str;
|
|
|
|
|
|
if(str == "quit") {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 11:08:58 +08:00
|
|
|
|
// 向客户端发布一个string,你也可以发布一个对象,内部会自动序列化
|
|
|
|
|
|
co_await server.publish("topic1", str);
|
2025-10-16 10:17:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-17 20:05:54 +08:00
|
|
|
|
sync_wait(pub());
|
2019-06-05 08:56:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
client 端代码:
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
void subscribe() {
|
|
|
|
|
|
REST_LOG_INFO << "will subscribe, waiting for publish";
|
|
|
|
|
|
auto sub = [&]() -> asio::awaitable<void> {
|
|
|
|
|
|
rpc_client client;
|
|
|
|
|
|
co_await client.connect("127.0.0.1:9004");
|
|
|
|
|
|
while (true) {
|
2025-10-16 11:41:21 +08:00
|
|
|
|
// 订阅topic1,库会自动将结果反序列化为std::string, 如果publish是一个person对象,则subscribe参数填person,内部会自动反序列化
|
2025-10-16 10:17:49 +08:00
|
|
|
|
auto [ec, result] = co_await client.subscribe<std::string>("topic1");
|
|
|
|
|
|
if (ec != rpc_errc::ok) {
|
|
|
|
|
|
REST_LOG_ERROR << "subscribe failed: " << make_error_code(ec).message();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
REST_LOG_INFO << result;
|
2021-06-30 21:51:48 +08:00
|
|
|
|
}
|
2025-10-16 10:17:49 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-17 20:05:54 +08:00
|
|
|
|
sync_wait(sub());
|
2019-06-05 08:56:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 快
|
|
|
|
|
|
|
|
|
|
|
|
rest_rpc是目前最快的rpc库,具体和grpc和brpc做了性能对比测试,rest_rpc性能是最高的,远超grpc。
|
|
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
性能测试代码在这里:
|
|
|
|
|
|
|
|
|
|
|
|
https://github.com/qicosmos/rest_rpc/tree/master/tests/bench.cpp
|
|
|
|
|
|
|
2025-10-16 11:08:58 +08:00
|
|
|
|
## 活
|
|
|
|
|
|
rest_rpc 也是比较灵活的,允许用户替换默认的序列化库。
|
|
|
|
|
|
|
2025-10-16 10:17:49 +08:00
|
|
|
|
rest_rpc 默认使用yalantinglibs的struct_pack 去做系列化/反序列化的,它的性能非常好。
|
|
|
|
|
|
|
|
|
|
|
|
rest_rpc 也支持用户使用自己的序列化库,只需要去实现一个序列化和一个反序列化函数。
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
namespace user_codec {
|
|
|
|
|
|
// adl lookup in user_codec namespace
|
|
|
|
|
|
template <typename... Args>
|
|
|
|
|
|
std::string serialize(rest_adl_tag, Args &&...args) {
|
|
|
|
|
|
msgpack::sbuffer buffer(2 * 1024);
|
|
|
|
|
|
if constexpr (sizeof...(Args) > 1) {
|
|
|
|
|
|
msgpack::pack(buffer, std::forward_as_tuple(std::forward<Args>(args)...));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgpack::pack(buffer, std::forward<Args>(args)...);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return std::string(buffer.data(), buffer.size());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T> T deserialize(rest_adl_tag, std::string_view data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
static msgpack::unpacked msg;
|
|
|
|
|
|
msgpack::unpack(msg, data.data(), data.size());
|
|
|
|
|
|
return msg.get().as<T>();
|
|
|
|
|
|
} catch (...) {
|
|
|
|
|
|
return T{};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} // namespace user_codec
|
|
|
|
|
|
```
|
|
|
|
|
|
实现这两个函数之后rest_rpc 将会使用自定义的序列化/反序列化函数了。
|
2019-06-05 08:56:29 +08:00
|
|
|
|
|
|
|
|
|
|
# rest_rpc的更多用法
|
|
|
|
|
|
|
2025-10-16 11:41:21 +08:00
|
|
|
|
rest_rpc 支持零拷贝发数据,以echo函数为例:
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
std::string_view echo(std::string_view str) {
|
|
|
|
|
|
return str;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
假如用户希望发送很大的一个数据,可能有数GB,如果按照常规的做法,需要先序列化,这样就存在内存拷贝,rest_rpc 针对这种场景专门做了优化,当client调用rpc函数时传入的是std::string_view 时,
|
|
|
|
|
|
rest_rpc 将不会对传入的数据做拷贝,也不会去做序列化,直接通过socket发送到服务端。
|
|
|
|
|
|
|
|
|
|
|
|
rpc函数的返回类型为std::string_view 时,client收到的响应数据也不会做反序列化和内存拷贝,直接返回的是收到的socket 数据。
|
|
|
|
|
|
|
2025-10-17 08:23:16 +08:00
|
|
|
|
这样就可以实现rpc的零拷贝数据发送了,能获得最佳的性能。事实上当用户的rpc函数的参数为单参数并且类型为基本类型(字符串和数字类型)时,rest_rpc 不会做序列化,以获得更好的性能,只有多参数或者结构体时才会去序列化。
|
2025-10-16 11:41:21 +08:00
|
|
|
|
|
2025-10-17 08:23:16 +08:00
|
|
|
|
更多例子可以参考rest_rpc的example:
|
2019-06-05 08:56:29 +08:00
|
|
|
|
|
|
|
|
|
|
https://github.com/qicosmos/rest_rpc/tree/master/examples
|
2018-12-20 13:47:06 +08:00
|
|
|
|
|
2022-01-31 11:24:57 +08:00
|
|
|
|
## 社区和群
|
2025-10-16 11:41:21 +08:00
|
|
|
|
http://purecpp.cn
|
2022-01-31 11:24:57 +08:00
|
|
|
|
|
2026-06-23 21:02:18 +08:00
|
|
|
|
在线讨论:[项目讨论](https://purecpp.cn/chatroom.html)
|