How long does it take to build an RPC service for Xiaobai who has just learned C++ using this open source framework?

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 futureand callbacktwo kinds of asynchronous call interface, meet different people interested

2. Quick start

rest_rpcDependence Boostprior to use should be properly installed Boost.

2.1 Installation

By git clonecommand 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/clientand examples/serverused directly visual studio open basic_client.vcxprojor basic_server.vcxprojcompiled to direct, official routine operating results as shown:

Note : The project needs Boost/asiosupport, if not installed Boostneed to be properly installed Boostwill Boostbe added to the project.

The Boost  method of adding in the project is as follows:

  1. After opening the project, click 项目→ in the menu bar 属性(shortcut key   Alt+ F7)

  2. Select the left VC++ 目录option, on the right 包含目录and 库目录add in Boostthe 根目录and 依赖库save

I used to Boost 1.75install 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.hppthis 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:

  1. rpc_server Instantiation of the object, setting the listening port and other properties

  2. Registration of service functions, defining which services the server provides

  3. Service start

1)rpc_server

rpc_serverFor the rest_rpcserver object is responsible for registering service, publish-subscribe, thread pool management, and other basic functions of the server, located rest_rpc::rpc_servicenamespace.

We need to use a instantiate rpc_serveran object and provides listening port, the thread pool size, for example:

rpc_server server(9000, 6); // 监听 9000 端口,线程池大小为 6

2) Server registration and startup

rpc_serverProvides a register_handlermethod of registering service and runmethod 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 functionmay 仿函数or lambdaexamples 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_handlermethod a Asynctemplate parameter (located rest_rpcnamespace):

/*异步服务返回类型为 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:

  1. rpc_client Object instantiation, setting the server address and port

  2. Connect to the server

  3. Call service

1)rpc_client

rpc_clientFor the rest_rpcclient objects, connecting the server, the server calls the service, serialized message, deserialize the message and other functions, is located in rest_rpcthe namespace.

Examples of the need to use an rpc_clientobject, and then provides the use connector async_connecta 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_clientit is also provided enable_auto_reconnectand enable_auto_heartbeatfunction, for each case remain connected.

2) Invoke remote services

rpc_clientOffers async_calland calltwo ways to asynchronous / synchronous call remote services, async_calland support callbackand futureare two ways to deal with the return message, this section describes the synchronization method call call.

In calling callupon 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_clientan 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_serverthe publishor publish_by_tokenmethod 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_clientthe   subscribemethod 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

Guess you like

Origin blog.csdn.net/a419240016/article/details/113931146