Remote Procedure Call (RPC) is a familiar and unfamiliar word. As long as communication is involved, certain network protocols are necessary. We’ve probably used HTTP, so what’s the difference between RPC and HTTP? What are the other features of RPC? What are the common types of selection?

1. What is RPC

RPC can be divided into two parts: user call interface + specific network protocol. The former is what the developer needs to care about, and the latter is what the framework does.

For example, we define a function that we want to make a local call if the input is “Hello World” and the output is “OK”. If a remote service receives “Hello World” and can return an “OK” to us, then this is a remote call. We will agree with the service on the name of the function to be called remotely. So, our user interface is: input, output, remote function names. For example, with SRPC, the client code would look like this:

int main(a)
{
    Example::SRPCClient client(IP, PORT);
    EchoRequest req; // User-defined request structure
    EchoResponse resp; // User-defined reply structure

    req.set_message("Hello World");
    client.Echo(&req, &resp, NULL); // Call the remote function named Echo
    return 0;
}
Copy the code

The specific network protocol is implemented by the framework, which packages the content that the developer wants to send and receive in some application layer protocol for network transmission and dispatch. An obvious comparison can be made with HTTP:

  • HTTP is also a network protocol, but the content of the package is fixed, must be: request line + request header + request body;
  • RPC is a user-defined network protocol and depends on the framework. For example, SRPC supports the following RPC protocols: SRPC/ Thrift /BRPC/tRPC

These RPC protocols are application-layer protocols parallel to HTTP. HTTP only contains specific network protocols. HTTP/1.1 200 OK can be returned, but there seems to be no user call interface. Why?

So what’s the function of the user interface? The most important features are two:

  • Locate the service to invoke;
  • Make our messages forward/backward compatible;

Let’s use a table to see how HTTP and RPC work separately:

Locate the service to invoke Message compatibility
HTTP URL Developers work it out in the message body
RPC Specify Service and Method names To the specific IDL

Therefore, HTTP calls reduce the user’s ability to call the interface’s functions, but sacrifice some of the freedom of forward/backward compatibility of messages. However, developers can choose the technology they prefer, because RPC and HTTP are mostly protocol interchangeable! Isn’t that amazing? Let’s look at the HIERARCHICAL architecture of RPC to see why different RPC frameworks can communicate with each other and how RPC and HTTP protocols can communicate.

2. What does RPC have

From the architectural level of SRPC, we can see what layers the RPC framework has and what features SRPC currently supports horizontally:

  • User code (client sending function/Server function implementation)
  • IDL (protobuf/ Thrift serialization)
  • Data organization (protobuf/thrift/json)
  • Compression (none/gzip/zlib/after/lz4)
  • Agreement (Sogou- STD/BidU – STD /Thrift- Framed /TRPC)
  • Communication (TCP/HTTP)

Let’s focus on the following three levels:

From left to right, the user is exposed to the most to the least level. The IDL layer generates code according to the request/reply structure defined by the developer. Protobuf and THRIFT are commonly used by the IDL layer, and the user interface and compatibility problems mentioned above are solved by the IDL layer. SRPC implements the user interface of the two IDLs as follows:

  • Thrift: IDL purely manual parsing, users do not need to chain thrift libraries to use SRPC!!
  • Protobuf: The definition part of a service is manually parsed

The middle column shows the specific network protocol, and each RPC can communicate with each other because each RPC implements the “language” of the other, so it can communicate with each other by protocol.

As RPC is parallel to HTTP, the second column and the third column can theoretically be combined in pairs. As long as the specific RPC protocol in the second column is sent, the HTTP related content should be specialized, and the communication between RPC and HTTP can be realized by sending according to the form required by HTTP rather than its own protocol.

3. RPC life cycle

Here we can use SRPC to see what happens when we send a request via method and process a response back:

According to the above, you can see more clearly mentioned various levels, including compression layer, serialization, protocol layer is decoupled through each other, on the SRPC code implementation is very unified, the transverse increase every compression algorithm or IDL or agreement does not need to also should not change the existing code, is a beautiful architecture ~

We’ve been talking about generating code, so what’s the point? As you can see in the figure, the generated code is a bridge between the user calling interface and the framework code. Here is an example of the simplest protobuf custom protocol: example.proto

syntax = "proto3";

message EchoRequest
{
    string message = 1;
};

message EchoResponse
{
    string message = 1;
};

service Example
{
    rpc Echo(EchoRequest) returns (EchoResponse);
};
Copy the code

Now that we have defined the request, reply, and remote service function names, we can generate the interface code example.srpc.h with the following command:

protoc example.proto --cpp_out=./ --proto_path=./
srpc_generator protobuf ./example.proto ./
Copy the code

Let’s take a look at what generated code can actually do:

/ / SERVER code
class Service : public srpc::RPCService
{
public:
    // The user needs to derive this function, which corresponds to the pb generated just now
    virtual void Echo(EchoRequest *request, EchoResponse *response, srpc::RPCContext *ctx) = 0;
};

/ / the CLIENT code
using EchoDone = std::function<void (echoresponse *, srpc::rpccontext *)>;

class SRPCClient : public srpc::SRPCClient 
{
public:
    // Asynchronous interface
    void Echo(const EchoRequest *req, EchoDone done);
    // Synchronize the interface
    void Echo(const EchoRequest *req, EchoResponse *resp, srpc::RPCSyncContext *sync_ctx);
    // Semi-synchronous interface
    WFFuture<std::pair<echoresponse, srpc::rpcsynccontext>> async_Echo(const EchoRequest *req);
};
Copy the code

As a high-performance RPC framework, the client code generated by SRPC includes: synchronous, semi-synchronous and asynchronous interfaces. The approach of a synchronous interface is shown at the beginning of this article.

And server interface is more simple, as a server, we need to do is – > receipt of a request processing logic – > return back, but this time, the framework has just mentioned network transceiver, decompression, and deserialization to ready, and then by generating code calls to the user implementation of derived function logic of the service class.

Since a protocol defines a client/server, there are several types of servers that we can also get as mentioned in Part 2:

  • SRPCServer
  • SRPCHttpServer
  • BRPCServer
  • TRPCServer
  • ThriftServer
  • .

4. A complete server example

Finally, with a complete Server example, we’ll look at how the user call interface is used and how to make calls across protocols using HTTP as a client. Srpc_generator generates empty user code as well as server.pb_skeleton.cc.

#include "example.srpc.h"
#include "workflow/WFFacilities.h"

using namespace srpc;
static WFFacilities::WaitGroup wait_group(1);

void sig_handler(int signo)
{
    wait_group.done(a); }class ExampleServiceImpl : public Example::Service
{
public:

    void Echo(EchoRequest *request, EchoResponse *response, srpc::RPCContext *ctx) override
    {
        response->set_message("OK"); // The concrete logic is added here, and we simply reply with an OK}};int main(a)
{
    unsigned short port = 80; // Because you want to start the Http service
    SRPCHttpServer server; // We need to construct a SRPCHttpServer

    ExampleServiceImpl example_impl;
    server.add_service(&example_impl);

    server.start(port);
    wait_group.wait(a); server.stop(a);return 0;
}
Copy the code

If SRPC is installed, you can run the following command to compile the executable file in Linux:

g++ -o server server.pb_skeleton.cc example.pb.cc -std=c++11 -lsrpc
Copy the code

With curl, you can make an HTTP request:

$The curl -i 127.0.0.1:80 / Example/Echo - H'Content-Type: application/json' -d '{message:"Hello World"}'
HTTP/1.1 200 OK
SRPC-Status: 1
SRPC-Error: 0
Content-Type: application/json
Content-Encoding: identity
Content-Length: 16
Connection: Keep-Alive

{"message":"OK"}
Copy the code

5. To summarize

Today our open source project, SRPC, based on C++ implementation, takes an in-depth look at the fundamentals of RPC. The overall code style of SRPC is simple and the architecture level is exquisite, with a total of about 10,000 lines of code. If you use C++, it may be very suitable for you to learn RPC architecture.

Through this article, I believe we can clearly understand what RPC is, what the interface looks like, and understand the protocol level by communicating with THE HTTP protocol. More importantly, we can know the specific vertical level of each level, and horizontal comparison of each of our common use patterns. If you are interested in more functions, you can also read SRPC source code for further understanding.

6. Project address

github.com/sogou/srpc

Welcome to use and star to support the author’s open source spirit!