This article is suitable for friends with C++ foundation
Author: HelloGitHub- Anthony
In the "Explain Open Source Projects" series launched by HelloGitHub, this issue introduces the C++-based RPC open source framework- rest_rpc , a framework that allows Xiaobai to develop RPC services quickly (10 minutes).
Project address: https://github.com/qicosmos/rest_rpc
rest_rpc is a high-performance, easy-to-use, cross-platform, header only C++11 RPC library. Its goal is to make TCP communication very simple and easy to use. Even people who don’t understand network communication can use it directly and quickly. Get started. At the same time, users only need to pay attention to their own business logic.
In short, rest_rpc allows you to quickly write your own network program with a few lines of code without any network programming related knowledge , and it is very convenient to use. It is the best choice for getting started network programming and RPC framework!
1. Preliminary knowledge
1.1 What is RPC
RPC is the abbreviation of Remote Procedure Call.
1.2 What is the use of RPC
For example, there are two servers A and B. Now that the program on A wants to remotely call the function/method provided by the application on B, it needs to transmit the required message through the network.
But the network transmission of messages involves many things, such as:
The establishment, maintenance and disconnection of the TCP connection between the client and the server
Serialization and grouping of messages
Network transmission of messages
Deserialization of messages
and many more
The role of RPC is to shield network-related operations, so that programs that are not in a memory space or even in a machine can be called like ordinary functions.
1.3 advantages of rest_rpc
rest_rpc has many advantages:
Simple to use
Support subscription mode
Allow
future
andcallback
two kinds of asynchronous call interface, meet different people interested
2. Quick start
rest_rpc
Dependence Boost
prior to use should be properly installed Boost
.
2.1 Installation
By git clone
command to download the project locally:
git clone https://github.com/qicosmos/rest_rpc
2.2 Directory structure
The files in the root directory of the rest_rpc project and their meanings are shown in the table:
file name | effect |
---|---|
doc | rest_rpc performance test report |
examples | rest_rpc example, including client and server parts |
include | rest_rpc frame header file |
third | msgpack support library, used to serialize and deserialize messages |
2.3 Run the routine
rest_rpc routine for the visual studio project, client and server routines are stored in examples/client
and examples/server
used directly visual studio open basic_client.vcxproj
or basic_server.vcxproj
compiled to direct, official routine operating results as shown:
Note : The project needs
Boost/asio
support, if not installedBoost
need to be properly installedBoost
willBoost
be added to the project.
The Boost
method of adding in the project is as follows:
After opening the project, click
项目
→ in the menu bar属性
(shortcut keyAlt
+F7
)Select the left
VC++ 目录
option, on the right包含目录
and库目录
add inBoost
the根目录
and依赖库
save
I used to Boost 1.75
install directory D:\devPack\boost_1_75_0
, configuration as shown:
Three, detailed tutorial
3.1 Write in front
Whether 服务端
or 客户端
only included with include/rest_rpc.hpp
this file.
All sample codes use the following content as a framework :
#include <iostream>
#include <rest_rpc.hpp>
#include <chrono>
using namespace rest_rpc;
using namespace rest_rpc::rpc_service;
int main(){
// do something
}
3.2 Writing the server
There are several processes to generate a client that can provide services:
rpc_server
Instantiation of the object, setting the listening port and other propertiesRegistration of service functions, defining which services the server provides
Service start
1)rpc_server
rpc_server
For the rest_rpc
server object is responsible for registering service, publish-subscribe, thread pool management, and other basic functions of the server, located rest_rpc::rpc_service
namespace.
We need to use a instantiate rpc_server
an object and provides listening port, the thread pool size, for example:
rpc_server server(9000, 6); // 监听 9000 端口,线程池大小为 6
2) Server registration and startup
rpc_server
Provides a register_handler
method of registering service and run
method to start the server, specific examples are as follows:
/*服务函数第一个参数必须为 rpc_conn,然后才是实现功能需要的参数(为可变参数,数量可变,也可以没有*/
std::string hello(rpc_conn conn, std::string name){
/*可以为 void 返回类型,代表调用后不给远程客户端返回消息*/
return ("Hello " + name); /*返回给远程客户端的内容*/
}
int main(){
rpc_server server(9000, 6);
/*func_greet 为服务名,远程调用通过服务名确定调用函数*/
/*hello 为函数,绑定当前服务调用哪个函数*/
server.register_handler("func_greet", hello);
server.run();//启动服务端
return EXIT_SUCCESS;
}
Which function
may 仿函数
or lambda
examples are as follows:
Use functors :
/*仿函数方法*/
struct test_func{
std::string hello(rpc_conn conn){
return "Hello Github!";
}
};
int main(){
test_func greeting;
rpc_server server(9000, 6);
/*greet 为服务名,远程调用通过服务名确定调用函数*/
/*test_func::hello 为函数,绑定当前服务调用哪个函数*/
/*greeting 为实例化仿函数对象*/
server.register_handler("greet", &test_func::hello, &greeting);
server.run();//启动服务端
return EXIT_SUCCESS;
}
Examples of using lambda methods :
/*使用 lambda 方法*/
int main(){
rpc_server server(9000, 6);
/*call_lambda 为服务名,远程调用通过服务名确定调用函数*/
/*[&server](rpc_conn conn){...} 为 lambda 对象*/
server.register_handler("call_lambda",
/*除 conn 外其他参数为可变参数*/
[&server](rpc_conn conn /*其他参数可有可无*/) {
std::cout << "Hello Github!" << std::endl;
// 返回值可有可无
});
server.run();//启动服务端
return EXIT_SUCCESS;
}
3) Register asynchronous service
Sometimes for various reasons we can not or do not wish to synchronize a remote call return (such as the need to wait for a thread returns), this time just to register_handler
method a Async
template parameter (located rest_rpc
namespace):
/*异步服务返回类型为 void*/
void async_greet(rpc_conn conn, const std::string& name) {
auto req_id = conn.lock()->request_id();// 异步服务需要先保存请求 id
// 这里新建了一个线程,代表异步处理了一些任务
std::thread thd([conn, req_id, name] {
std::string ret = "Hello " + name + ", Welcome to Hello Github!";
/*这里的 conn 是一个 weak_ptr*/
auto conn_sp = conn.lock();// 使用 weak_ptr 的 lock 方法获取一个 shared_ptr
if (conn_sp) {
/*操作完成,返回;std::move(ret) 为返回值*/
conn_sp->pack_and_response(req_id, std::move(ret));
}
});
thd.detach();
}
int main(){
rpc_server server(9000, 6);
server.register_handler<Async>("async_greet", async_greet);// 使用 Async 作为模板参数
server.run();//启动服务端
return EXIT_SUCCESS;
}
rest_rpc supports registering multiple services on the same port, for example:
server.register_handler("func_greet", hello); server.register_handler("greet", &test_func::hello, &greeting); server.register_handler("call_lambda", /*除 conn 外其他参数为可变参数*/ [&server](rpc_conn conn /*其他参数可有可无*/) { std::cout << "Hello Github!" << std::endl; // 返回值可有可无 }); // 其他服务等等 server.run();
3.3 Writing the client
Generate a client that can make remote service calls to go through the following process:
rpc_client
Object instantiation, setting the server address and portConnect to the server
Call service
1)rpc_client
rpc_client
For the rest_rpc
client objects, connecting the server, the server calls the service, serialized message, deserialize the message and other functions, is located in rest_rpc
the namespace.
Examples of the need to use an rpc_client
object, and then provides the use connect
or async_connect
a method for synchronous / asynchronous connection to the server, such as:
rpc_client client;
bool has_connected = client.connect("127.0.0.1", 9000);//同步连接,返回是否连接成功
client.async_connect("127.0.0.1", 9000);//异步连接,无返回值
Of course, rpc_client
it is also provided enable_auto_reconnect
and enable_auto_heartbeat
function, for each case remain connected.
2) Invoke remote services
rpc_client
Offers async_call
and call
two ways to asynchronous / synchronous call remote services, async_call
and support callback
and future
are two ways to deal with the return message, this section describes the synchronization method call call
.
In calling call
upon the method of service if we return to the values you need to set the template parameters, such as remote service returns an integer need to specify the type of the return value call<int>
, if not specified represents no return value.
In the preparation of the server portion of each service we said at the time of registration has a name and can be called by a remote service name, and now we call the server the first example of a partial write:
int main(){
/* rest_rpc 在遇到错误(调用服务传入参数和远程服务需要参数不一致、连接失败等)时会抛出异常*/
try{
/*建立连接*/
rpc_client client("127.0.0.1", 9000);// IP 地址,端口号
/*设定超时 5s(不填默认为 3s),connect 超时返回 false,成功返回 true*/
bool has_connected = client.connect(5);
/*没有建立连接则退出程序*/
if (!has_connected) {
std::cout << "connect timeout" << std::endl;
exit(-1);
}
/*调用远程服务,返回欢迎信息*/
std::string result = client.call<std::string>("func_greet", "HG");// func_greet 为事先注册好的服务名,需要一个 name 参数,这里为 Hello Github 的缩写 HG
std::cout << result << std::endl;
}
/*遇到连接错误、调用服务时参数不对等情况会抛出异常*/
catch (const std::exception & e) {
std::cout << e.what() << std::endl;
}
return EXIT_SUCCESS;
}
Of course, some calls may not return any messages, this is the time directly client.call("xxx", ...)
to, then call the method return type void
.
3) Call remote services asynchronously
Sometimes we call remote services for various reasons need some time to return, this time using rpc_client
an asynchronous method invocation provided async_call
, it defaults to callback mode, the template parameter for the timeout time you want to use as future models will need to be specified.
In callback mode, the formal parameters of the callback function should be the same as those in the routine, and need to be added after the call client.run()
:
/*默认为 call back 模式,模板参数代表 timeout 2000ms,async_call 参数顺序为 服务名, 回调函数, 调用服务需要的参数(数目类型不定)*/
/*timeout 不指定则默认为 5s,设定为 0 代表不检查 timeout */
client.async_call<2000>("async_greet",
/*在远程服务返回时自动调用该回调函数,注意形参只能这样写*/
[&client](const boost::system::error_code & ec, string_view data) {
auto str = as<std::string>(data);
std::cout << str << std::endl;
},
"HG");// echo 服务将传入的参数直接返回
client.run(); // 启动服务线程,等待返回
// 其余部分和 call 的使用方法一样
Future mode:
auto f = client.async_call<FUTURE>("async_greet", "HG");
if (f.wait_for(std::chrono::milliseconds(50)) == std::future_status::timeout) {
std::cout << "timeout" << std::endl;
}
else {
auto ret = f.get().as<std::string>();// 转换为 string 对象,无返回值可以写 f.get().as()
std::cout << ret << std::endl;
}
3.4 Serialization
When using rest_rpc, if the parameter is a standard library-related object, you do not need to specify the serialization method separately. If you use a custom object, you need to use msgpack to define the serialization method. For example, to transmit such a structure:
struct person {
int id;
std::string name;
int age;
};
You need to add MSGPACK_DEFINE()
:
/*
注意:无论是服务端还是客户端都要进行这样的操作
客户端和服务端 MSGPACK_DEFINE() 中的填入的参数顺序必须一致,这一点和 msgpack 的序列化方式有
如客户端和服务端中 MSGPACK_DEFINE() 中参数顺序不一致可能会导致解包时发生错误
*/
struct person {
int id;
std::string name;
int age;
MSGPACK_DEFINE(id, name, age);//定义需要序列化的内容
};
The same applies to objects:
class person{
private:
int id;
std::string name;
int age;
public:
MSGPACK_DEFINE(id, name, age);//需要在 public 中
}
Then you can use person as a parameter type.
4. Features: publish/subscribe model
A major feature of rest_rpc is that it provides a publish-subscribe model, which is very useful when messages need to be continuously transmitted between the client and the server.
Server-side only need to use rpc_server
the publish
or publish_by_token
method to publish a subscription message that if you use the token subscribers need to use the same token to access, for example:
int main() {
rpc_server server(9000, 6);
std::thread broadcast([&server]() {
while (true) {
/*发布订阅消息,所有订阅了 greet 的客户端都可以获得消息*/
server.publish("greet", "Hello GitHub!");
/*只有订阅了 secret_greet 并且提供了 www.hellogithub.com 作为 token 才可以获得消息*/
server.publish_by_token("secret_greet", "www.hellogithub.com", "Hello Github! this is secret message");
std::this_thread::sleep_for(std::chrono::seconds(1));// 等待一秒
}
});
server.run();//启动服务端
return EXIT_SUCCESS;
}
The client simply use rpc_client
the subscribe
method to:
void test_subscribe() {
rpc_client client;
client.enable_auto_reconnect();// 自动重连
client.enable_auto_heartbeat();// 自动心跳包
bool r = client.connect("127.0.0.1", 9000);
if (!r) {
return;
}
// 直接订阅,无 token
client.subscribe("greet", [](string_view data) {
std::cout << data << std::endl;
});
// 需要 token 才能正常获得订阅消息
client.subscribe("secret_greet", "www.hellogithub.com", [](string_view data) {
std::cout << data << std::endl;
});
client.run();// 不断运行
}
int main() {
test_subscribe();
return EXIT_SUCCESS;
}
1) Transfer custom objects when subscribing
If there is such an object to be transmitted:
struct person {
int id;
std::string name;
int age;
MSGPACK_DEFINE(id, name, age);
};
The server can directly use it as a parameter, for example:
person p{ 1, "tom", 20 };
server.publish("key", p);
The client needs to deserialize :
client.subscribe("key",
[](string_view data) {
msgpack_codec codec;
person p = codec.unpack<person>(data.data(), data.size());
std::cout << p.name << std::endl;
});
Five, finally
RPC has many mature industrial frameworks such as:
Google grpc
Baidu's brpc etc.
But compared to rest_rpc, the configuration and use are more complicated. It is a very good choice for novices to use rest_rpc as an entry project for RPC.
At this point, I believe you have mastered most of the functions of rest_rpc, then it is time to start an RPC service!
Click to follow to receive the push as soon as possible
▼ Click to read the original submission