2.7. gRPC

gRPC is a remote procedure call mechanism for heterogeneous environments. A platform independent message format description language, called Protocol Buffers, is used to describe the remotely accessible interfaces. Individual language bindings rely on Protocol Buffers to provide standard message encoding and add language specific invocation interfaces.

2.7.1. Interface Description Language

Protocol Buffers Message Specification Example

syntax = "proto3";

package org.example;

message SomeMessage {

    // Field identifiers reserved after message changes.
    reserved 8, 100;

    // Many integer types with specific encodings.
    int32 aMostlyPositiveInteger = 1;
    sint64 aSignedInteger = 2;
    uint64 anUnsignedInteger = 3;
    fixed32 anOftenBigUnsignedInteger = 4;
    sfixed32 anOftenBigSignedInteger = 5;

    // String always with UTF 8 encoding.
    string aString = 10;

    // Another message type.
    AnotherMessage aMessage = 111;

    // Variable length content supported.
    repeated string aStringList = 200;
    map <int32, string> aMap = 222;
}
  • A spectrum of basic types

  • Packages and nested types

  • Fields can be repeated

  • Fields are optional

  • Explicit field identifiers for versioning

Protocol Buffer Service Specification Example

syntax = "proto3";

service AnInterface {
    rpc someMethod (SomeRequest) returns (SomeResponse) { }
    rpc secondMethod (SecondRequest) returns (stream SecondResponse) { }
    rpc thirdMethod (stream ThirdRequest) returns (ThirdResponse) { }
}

message SomeRequest { ... }
message SomeResponse { ... }
...
  • Single or stream arguments

  • Stream open during entire call

2.7.2. C++ Server Code Basics

C++ Server Implementation

Single Argument Method Implementation. 

class MyService : public AnExampleService::Service {
    grpc.Status OneToOne (grpc.ServerContext *context,
        const AnExampleRequest *request, AnExampleResponse *response) {

        // Method implementation goes here ...

        return (grpc.Status::OK);
    }
    ...
}

Server Initialization. 

MyService service;
grpc.ServerBuilder builder;
builder.AddListeningPort ("localhost:8888", grpc.InsecureServerCredentials ());
builder.RegisterService (&service);
std::unique_ptr<grpc.Server> server (builder.BuildAndStart ());

server->Wait ();
  • Sync mode uses internal thread pool

  • Async mode uses completion queues

Asynchronous Server Internals

This note looks at the example from https://github.com/grpc/grpc/tree/master/examples/cpp/helloworld in more detail, the code snippets were taken from gRPC 1.44. During initialization, the server requests that a completion queue is used with the server instance:

std::unique_ptr<ServerCompletionQueue> cq_;
ServerBuilder builder;

cq_ = builder.AddCompletionQueue ();

The completion queue is a thread safe object that one or more threads can query for events. The completion queue will deliver two events per remote procedure call, one when the call arrives and one when the call completes. The delivery of the events has to be requested explicitly, for the call arrival event through a generated asynchronous service object, for the call completion event through a template asynchronous writer object. In the code snippet, the CallData instances represent pending calls and the this reference serves as a tag that uniquely identifies the events:

class CallData {
    public:
        CallData (Greeter::AsyncService* service, ServerCompletionQueue* cq)
            : service_ (service), cq_ (cq), responder_ (&ctx_) ...
        ...
    private:
        ServerAsyncResponseWriter<HelloReply> responder_;
        ServerContext ctx_;
        HelloRequest request_;
        HelloReply reply_;
        ...
// Requesting call arrival event.
service_->RequestSayHello (&ctx_, &request_, &responder_, cq_, cq_, this);
// Requesting call termination event.
responder_.Finish(reply_, Status::OK, this);
// Waiting for (any) event.
void* tag;
bool ok;
while (true) {
    GPR_ASSERT (cq_->Next (&tag, &ok));
    GPR_ASSERT (ok);
    ...
}

2.7.3. Java Server Code Basics

Java Server Implementation

Single Argument Method Implementation. 

class MyService extends AnExampleServiceGrpc.AnExampleServiceImplBase {
    @Override public void OneToOne (
        AnExampleRequest request,
        io.grpc.stub.StreamObserver<AnExampleResponse> responseObserver) {

        // Method implementation goes here ...

        responseObserver.onNext (response);
        responseObserver.onCompleted ();
    }
    ...
}

Server Initialization. 

io.grpc.Server server = io.grpc.ServerBuilder
    .forPort (8888).addService (new MyService ()).build ().start ();

server.awaitTermination ();
  • Uses static cached thread pool by default

  • Can use provided executor

  • Can use transport thread

2.7.4. Python Server Code Basics

Python Server Implementation

Single Argument Method Implementation. 

class MyServicer (AnExampleServiceServicer):
    def OneToOne (self, request, context):

        # Method implementation goes here ...

        return response

Server Initialization. 

server = grpc.server (
    futures.ThreadPoolExecutor (
        max_workers = SERVER_THREAD_COUNT))
add_AnExampleServiceServicer_to_server (MyServicer (), server)
server.add_insecure_port ("localhost:8888")
server.start ()

server.wait_for_termination ()

Asynchronous Server Internals

This note looks at the example from https://github.com/grpc/grpc/tree/master/examples/python/helloworld in more detail, the code snippets were taken from gRPC 1.44. The implementation is an awaitable object:

class Greeter (helloworld_pb2_grpc.GreeterServicer):
    async def SayHello (self, request: helloworld_pb2.HelloRequest, context: grpc.aio.ServicerContext) -> helloworld_pb2.HelloReply:
        return helloworld_pb2.HelloReply (...)

The initialization uses the asynchronous version of the server interface:

async def serve ():
    server = grpc.aio.server ()
    ...
    await server.start ()
    await server.wait_for_termination ()

The main thread then launches the awaitable:

asyncio.run (serve ())

2.7.5. C++ Client Code Basics

C++ Client Implementation

Client Initialization. 

std::shared_ptr<grpc.Channel> channel = grpc.CreateChannel (
    "localhost:8888", grpc.InsecureChannelCredentials ());

Single Argument Method Call. 

grpc.ClientContext context;
AnExampleResponse response;
std::shared_ptr<AnExampleService::Stub> stub = AnExampleService::NewStub (channel);
grpc.Status status = stub->OneToOne (&context, request, &response);
if (status.ok ()) {

    // Response available here ...

}

Asynchronous Client Internals

This note looks at the example from https://github.com/grpc/grpc/tree/master/examples/cpp/helloworld in more detail, the code snippets were taken from gRPC 1.44. During invocation, the client represents an asynchronous invocation with a template asynchronous reader object connected to a completion queue:

HelloRequest request;
ClientContext context;
CompletionQueue cq;

std::unique_ptr<ClientAsyncResponseReader<HelloReply>> rpc (stub_->PrepareAsyncSayHello (&context, request, &cq));
rpc->StartCall ();

The call completion event is delivered through the completion queue. Again, the delivery of the event has to be requested explicitly, the 1 serves as a tag that uniquely identifies the event:

rpc->Finish (&reply, &status, (void*) 1);
void* got_tag;
bool ok = false;
GPR_ASSERT (cq.Next (&got_tag, &ok));
GPR_ASSERT (got_tag == (void*) 1);
GPR_ASSERT (ok);

if (status.ok ()) {
    ...
}

2.7.6. Java Client Code Basics

Java Client Implementation

Client Initialization. 

io.grpc.ManagedChannel channel = io.grpc.ManagedChannelBuilder
    .forAddress ("localhost", 8888)
    .usePlaintext (true)
    .build ();

Single Argument Method Call. 

AnExampleServiceGrpc.AnExampleServiceBlockingStub stub =
    AnExampleServiceGrpc.newBlockingStub (channel);
AnExampleResponse response = stub.oneToOne (request);

// Response available here ...

2.7.7. Python Client Code Basics

Python Client Implementation

Client Initialization. 

with grpc.insecure_channel ("localhost:8888") as channel:

Single Argument Method Call. 

stub = AnExampleServiceStub (channel)
response = stub.OneToOne (request)

# Response available here ...

Asynchronous Client Internals

This note looks at the example from https://github.com/grpc/grpc/tree/master/examples/python/helloworld in more detail, the code snippets were taken from gRPC 1.44. The initialization uses the asynchronous version of the channel interface:

async def run ():
    async with grpc.aio.insecure_channel ('localhost:50051') as channel:
        ...

The invocation is asynchronous, the stub is asynchronous by virtue of being created on an asynchronous channel:

stub = helloworld_pb2_grpc.GreeterStub (channel)
response = await stub.SayHello (helloworld_pb2.HelloRequest (...))

The main thread then launches the awaitable:

asyncio.run (run ())

2.7.8. References

  1. The gRPC Project Home Page. https://www.grpc.io