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;
};
- This class is a pure interface, which represents a RPC service. A RPC server may have multiple services.
name()
returns the service name,methods()
returns all the RPC methods.
#rpc::Server
#Server::Server
Server();
- The default constructor, users do not need to care.
#Server::add_service
1. Server& add_service(rpc::Service* s);
2. Server& add_service(const std::shared_ptr<rpc::Service>& s);
- Add a service, the parameter
s
in the first one must be dynamically created withoperator new
. - Users can call this method multiple times to add multiple services, and different services must have different names.
#Server::start
void start(
const char* ip, int port,
const char* url="/",
const char* key=0, const char* ca=0
);
- Start the RPC server, this method will not block the current thread.
- The parameter
ip
is the server ip, which can be an IPv4 or IPv6 address, and the parameterport
is the server port. - The parameter
url
is the url of the HTTP server, and must start with/
. - The parameter key is path of a PEMfile which stores the SSL private key, and the parameter ca is path of a PEM file which stores the SSL certificate. They are NULL by default, and SSL is disabled.
- Starting from v3.0, the server no longer depends on the
rpc::Server
object after startup.
#Server::exit
void exit();
- Added since v2.0.2.
- Exit the RPC server, close the listening socket, and no longer receive new connections.
- Since v3.0, after the RPC server exits, previously established connections will be reset in the future.
#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.
-
package defines the package name, which corresponds to namespace in C++.
-
service defines a RPC service, it has 2 methods, hello and world.
-
Since the RPC request and response are both JSON, there is no need to define the structure in the proto file.
-
At most one service can be defined in a proto file.
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
- As you can see, the
HelloWorld
class inherits from rpc::Service, and it has already implementedname()
andmethods()
inrpc::Service
. - Users only need to inherit the
HelloWorld
class and implement the methodshello
andworld
.
#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
- The above is just a very simple example. In actual applications, it is generally necessary to perform corresponding business processing according to the parameters in
req
, and then fill inres
.
#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;
}
}
- First call add_service() to add the service, then call start() to start the server.
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"}'
-
The above use
curl
to send a POST request to the RPC server, the parameter is a JSON string, and a"api"
field must be provided to indicate the RPC method to be called. -
"ping"
is a built-in method of the RPC framework, generally used for testing or sending heartbeats. -
/xx
in the url should be consistent with the url specified when the RPC server is started.
#rpc::Client
#Client::Client
1. Client(const char* ip, int port, bool use_ssl=false);
2. Client(const Client& c);
- 1, the parameter
ip
is ip of the server, which can be a domain name, IPv4 or IPv6 address; the parameterport
is port of the server; the parameteruse_ssl
indicates whether to enable SSL transmission, the default is false, and SSL is disabled.
When rpc::Client was constructed, the connection is not established immediately.
#Client::~Client
Client::~Client();
- Destructor, close the internal connection.
#Client::call
void call(const Json& req, Json& res);
- Perform a RPC request, it must be called in coroutine.
- The parameter
req
must contain the"api"
field, its value is generally in the form of"service.method"
. - The parameter
res
is the response of the RPC request. - If the RPC request is not sent, or no response from the server is received, res will not be filled.
- This method checks the connection status before sending the RPC request, and establishes the connection first if it is not connected.
#Client::close
void close();
- Close the connection, it is safe to call this function multiple times.
#Client::ping
void ping();
- Send a
ping
request to the server, generally used for testing or sending heartbeats.
#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);
- In the above example, the client sends an RPC request to the server every 1 second.
#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);
}
- In the above example, co::pool is used to store the clients, and multiple coroutines can share these clients.
- The ccb of co::pool uses copy construction to copy a client from
proto
.
#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...");
- Timeout in seconds for idle connections in rpc::Server. If a connection does not receive any data within this time, the server may close the connection.
#rpc_conn_timeout
DEF_int32(rpc_conn_timeout, 3000, "#2 connect timeout in ms");
- Connect timeout in milliseconds for rpc::Client.
#rpc_log
DEF_bool(rpc_log, true, "#2 enable rpc log if true");
- Whether to print RPC logs, the default is true, rpc::Server and rpc::Client will print RPC requests and responses.
#rpc_max_idle_conn
DEF_int32(rpc_max_idle_conn, 128, "#2 max idle connections");
- Maximum number of idle connections for rpc::Server. The default is 128. When this number is exceeded, the server will close some idle connections.
#rpc_max_msg_size
DEF_int32(rpc_max_msg_size, 8 << 20, "#2 max size of rpc message, default: 8M");
- The maximum length of RPC messages, the default is 8M.
#rpc_recv_timeout
DEF_int32(rpc_recv_timeout, 3000, "#2 recv timeout in ms");
- RPC recv timeout in milliseconds.
#rpc_send_timeout
DEF_int32(rpc_send_timeout, 3000, "#2 send timeout in ms");
- RPC send timeout in milliseconds.