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.
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 EOSwhere 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.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.