Backend Development 22 min read

Fundamentals of gRPC: Concepts, Asynchronous Client/Server, Streaming, Protocol and Generated Code

The article explains gRPC fundamentals, illustrating service definition in .proto files, synchronous and asynchronous client and server implementations with CompletionQueue and CallData state machines, streaming RPC patterns, HTTP/2 protocol details, metadata handling, and the structure of generated stub and service code.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Fundamentals of gRPC: Concepts, Asynchronous Client/Server, Streaming, Protocol and Generated Code

This article introduces the basic concepts of gRPC, starting with an overview diagram that shows the relationships between Service, RPC, API, Client, Stub, Channel, Server, and ServiceBuilder.

It then explains how to define a service in a .proto file and shows a minimal example:

// helloworld.proto
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

On the client side, a GreeterClient class wraps a generated Stub to perform RPC calls:

class GreeterClient {
public:
  GreeterClient(std::shared_ptr
channel)
      : stub_(Greeter::NewStub(channel)) {}
  std::string SayHello(const std::string& user);
private:
  std::unique_ptr
stub_;
};

Creating a channel and invoking the RPC looks like this:

std::string target_str = "localhost:50051";
auto channel = grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials());
GreeterClient greeter(channel);
std::string user("world");
std::string reply = greeter.SayHello(user);

Asynchronous client

The asynchronous client uses a CompletionQueue to manage pending operations. The basic steps are:

Bind a CompletionQueue to an RPC call.

Use a unique void* tag to identify the request.

Call CompletionQueue::Next() to wait for completion and dispatch based on the tag.

Example of an asynchronous unary call:

auto rpc = stub_->PrepareAsyncSayHello(&context, request, &cq);
rpc->StartCall();
rpc->Finish(&reply, &status, (void*)this);

Callback‑style asynchronous calls are experimental. A callback client can be written as:

stub_->async()->SayHello(&context, &request, &reply,
    [μ, &cv, &done, &status](Status s) {
      status = std::move(s);
      std::lock_guard
lock(mu);
      done = true;
      cv.notify_one();
    });

Asynchronous server

The server creates a CallData object for each incoming request. Its state machine goes through CREATE → PROCESS → FINISH, using the same CompletionQueue and tags to drive the workflow.

class CallData {
public:
  CallData(Greeter::AsyncService* service, ServerCompletionQueue* cq)
      : service_(service), cq_(cq), responder_(&ctx_), status_(CREATE) {
    Proceed();
  }
  void Proceed();
private:
  Greeter::AsyncService* service_;
  ServerContext ctx_;
  ServerAsyncResponseWriter
responder_;
  enum CallStatus { CREATE, PROCESS, FINISH } status_;
};

The server builder pattern is used to register the service and start the server:

ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr
server(builder.BuildAndStart());

Streaming RPCs

gRPC supports four streaming patterns:

Unary RPC

Server‑streaming RPC

Client‑streaming RPC

Bidirectional streaming RPC

Example of a server‑streaming method signature:

// rpc ListFeatures(Rectangle) returns (stream Feature) {}
Status ListFeatures(ServerContext* context, const Rectangle* rectangle,
                    ServerWriter
* writer);

Client‑streaming and bidirectional examples use ServerReader and ServerReaderWriter respectively.

Communication protocol

gRPC messages are transmitted over HTTP/2. A request consists of:

Request → Request‑Headers *Length‑Prefixed‑Message EOS

where Length‑Prefixed‑Message is composed of a compression flag, message length, and the protobuf‑encoded payload.

Responses can be either (Response‑Headers *Length‑Prefixed‑Message Trailers) or Trailers‑Only . Both end with the HTTP/2 END_STREAM flag.

Wireshark captures show the Call‑Definition headers (method, scheme, path, content‑type, etc.) and the binary protobuf payload.

Context and metadata

Both client and server can attach custom metadata via ClientContext::AddMetadata and ServerContext::AddInitialMetadata / AddTrailingMetadata . Context is also used for compression, authentication, timeout, and performance tracing.

Generated code

The *.grpc.pb.h file defines a Greeter class containing:

A Stub for client‑side calls (synchronous, asynchronous, and experimental callback methods).

A Service base class for server implementations (synchronous).

An AsyncService derived class that registers asynchronous methods and disables the synchronous ones.

Different service types (e.g., Service , AsyncService , CallbackService ) are generated by template inheritance that sets the internal ApiType (SYNC, ASYNC, etc.).

Stub creation is performed via the static method:

static std::unique_ptr
NewStub(const std::shared_ptr
& channel);

Asynchronous stubs expose methods such as AsyncSayHello and PrepareAsyncSayHello , while callback stubs expose async()->SayHello(..., callback) .

References

1. Core concepts, architecture and lifecycle 2. Analyzing gRPC messages with Wireshark 3. gRPC over HTTP/2 documentation

Author

PAN Zhongxian – Tencent Operations Development Engineer, graduate of Xi'an University of Electronic Science and Technology, focuses on backend systems and performance debugging.

RPCstreamingasynchronousgRPCC++Protobufprotocol
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.