RPC (original) (raw)

include: co/rpc.h.

Coost implements a coroutine-based RPC framework, which internally uses JSON as the data exchange format. Compared with RPC frameworks using binary protocols such as protobuf, it is more flexible and easier to use.

Since v3.0, the RPC framework also supports HTTP protocol, and we are able to send a RPC request with the HTTP POST method.

#rpc::Service

class Service {
   public:
     Service() = default;
     virtual ~Service() = default;

     typedef std::function<void(Json&, Json&)> Fun;

     virtual const char* name() const = 0;
     virtual const co::map<const char*, Fun>& methods() const = 0;
};

#rpc::Server

#Server::Server

#Server::add_service

1. Server& add_service(rpc::Service* s);
2. Server& add_service(const std::shared_ptr<rpc::Service>& s);

#Server::start

void start(
    const char* ip, int port,
    const char* url="/",
    const char* key=0, const char* ca=0
);

#Server::exit

#RPC server example

#Define a proto file

Here is a simple proto file hello_world.proto:

package xx

service HelloWorld {
    hello
    world
}

package defines the package name.

Coost v3.0.1 rewrote gen with flex and byacc, and we can alse define object (struct) in the proto file. For specific usage, please refer to j2s.

#Generate code for RPC service

gen is the RPC code generator provided by coost, which can be used to generate code for RPC service.

xmake -b gen             # build gen
cp gen /usr/local/bin    # put gen in the /usr/local/bin directory
gen hello_world.proto    # Generate code

The generated file hello_world.proto is as follow:

// Autogenerated.
// DO NOT EDIT. All changes will be undone.
#pragma once

#include "co/rpc.h"

namespace xx {

class HelloWorld : public rpc::Service {
  public:
    typedef std::function<void(Json&, Json&)> Fun;

    HelloWorld() {
        using std::placeholders::_1;
        using std::placeholders::_2;
        _methods["HelloWorld.hello"] = std::bind(&HelloWorld::hello, this, _1, _2);
        _methods["HelloWorld.world"] = std::bind(&HelloWorld::world, this, _1, _2);
    }

    virtual ~HelloWorld() {}

    virtual const char* name() const {
        return "HelloWorld";
    }

    virtual const co::map<const char*, Fun>& methods() const {
        return _methods;
    }

    virtual void hello(Json& req, Json& res) = 0;

    virtual void world(Json& req, Json& res) = 0;

  private:
    co::map<const char*, Fun> _methods;
};

} // xx

#Implement the RPC service

#include "hello_world.h"

namespace xx {

class HelloWorldImpl : public HelloWorld {
  public:
    HelloWorldImpl() = default;
    virtual ~HelloWorldImpl() = default;

    virtual void hello(Json& req, Json& res) {
        res = {
            { "result", {
                { "hello", 23 }
            }}
        };
    }

    virtual void world(Json& req, Json& res) {
        res = {
            { "error", "not supported"}
        };
    }
};

} // xx

#Start RPC server

int main(int argc, char** argv) {
    flag::parse(argc, argv);

    rpc::Server()
        .add_service(new xx::HelloWorldImpl)
        .start("127.0.0.1", 7788, "/xx");

    for (;;) sleep::sec(80000);
    return 0;
}
}

The start() method will not block the current thread, so we need a for loop to prevent the main function from exiting.

#Call RPC service with curl

In v3.0, the RPC framework supports HTTP protocol, so we can call the RPC service with the curl command:

curl http://127.0.0.1:7788/xx --request POST --data '{"api":"ping"}'
curl http://127.0.0.1:7788/xx --request POST --data '{"api":"HelloWorld.hello"}'

#rpc::Client

#Client::Client

1. Client(const char* ip, int port, bool use_ssl=false);
2. Client(const Client& c);

When rpc::Client was constructed, the connection is not established immediately.

#Client::~Client

#Client::call

void call(const Json& req, Json& res);

#Client::close

#Client::ping

#RPC client example

#Use rpc::Client directly

DEF_bool(use_ssl, false, "use ssl if true");
DEF_int32(n, 3, "request num");

void client_fun() {
    rpc::Client c("127.0.0.1", 7788, FLG_use_ssl);

    for (int i = 0; i < FLG_n; ++i) {
        co::Json req = {
            {"api", "HelloWorld.hello"}
        };
        co::Json res;
        c.call(req, res);
        co::sleep(1000);
    }

    c.close();
}

go(client_fun);

#Use connection pool

When a client needs to establish a large number of connections, co::pool can be used to manage these connections.

std::unique_ptr<rpc::Client> proto;

co::pool pool(
    []() { return (void*) new rpc::Client(*proto); },
    [](void* p) { delete (rpc::Client*) p; }
);

void client_fun() {
    co::pool_guard<rpc::Client> c(pool);

    while (true) {
        c->ping();
        co::sleep(3000);
    }
}

proto.reset(new rpc::Client("127.0.0.1", 7788));

for (int i = 0; i < 8; ++i) {
    go(client_fun);
}

#Config items

Coost uses co.flag to define config items for RPC.

#rpc_conn_idle_sec

DEF_int32(rpc_conn_idle_sec, 180, "#2 connection may be closed if no data...");

#rpc_conn_timeout

DEF_int32(rpc_conn_timeout, 3000, "#2 connect timeout in ms");

#rpc_log

DEF_bool(rpc_log, true, "#2 enable rpc log if true");

#rpc_max_idle_conn

DEF_int32(rpc_max_idle_conn, 128, "#2 max idle connections");

#rpc_max_msg_size

DEF_int32(rpc_max_msg_size, 8 << 20, "#2 max size of rpc message, default: 8M");

#rpc_recv_timeout

DEF_int32(rpc_recv_timeout, 3000, "#2 recv timeout in ms");

#rpc_send_timeout

DEF_int32(rpc_send_timeout, 3000, "#2 send timeout in ms");