gRPC and Protocol Buffers in C++: From .proto to Production
Why gRPC Over REST for Service-to-Service?
REST with JSON works well for browser-facing APIs. For internal service-to-service communication, gRPC has practical advantages:
- Strong typing: the
.protoschema is the contract — no undocumented JSON shapes - Binary encoding: Protocol Buffers typically encode 3-10x smaller than equivalent JSON
- HTTP/2 multiplexing: many RPCs share one connection with flow control
- Built-in streaming: server streaming, client streaming, and bidirectional streaming are first-class
- Code generation: client and server stubs in C++, Go, Python, Java, and others from one
.protofile
1. Protocol Buffers Basics
Define messages and service interfaces in .proto files:
// user_service.proto
syntax = "proto3";
package myapp;
// Messages define the data structures
message GetUserRequest {
int32 user_id = 1; // field number = 1, must never change
}
message UserResponse {
int32 user_id = 1;
string name = 2;
string email = 3;
int64 created_at = 4; // Unix timestamp
repeated string roles = 5; // array of strings
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
// Service defines the RPC methods
service UserService {
rpc GetUser(GetUserRequest) returns (UserResponse);
rpc CreateUser(CreateUserRequest) returns (UserResponse);
// Streaming variants (covered later)
rpc ListUsers(ListUsersRequest) returns (stream UserResponse);
}
Field numbers are permanent — the number (not the name) identifies a field in the binary encoding. Never reuse a field number for a different field; mark removed fields as reserved instead:
message UserResponse {
int32 user_id = 1;
string name = 2;
reserved 3; // was "phone", removed — prevents accidental reuse
string email = 4;
}
Generate C++ Code
# Install protoc and grpc_cpp_plugin (varies by platform)
# Ubuntu: apt install protobuf-compiler-grpc libgrpc++-dev
# macOS: brew install grpc
# Generate C++ stubs
protoc \
--cpp_out=./generated \
--grpc_out=./generated \
--plugin=protoc-gen-grpc=$(which grpc_cpp_plugin) \
user_service.proto
# Creates:
# generated/user_service.pb.h — message classes
# generated/user_service.pb.cc
# generated/user_service.grpc.pb.h — service/stub classes
# generated/user_service.grpc.pb.cc
2. gRPC C++ Server
// server.cc
#include <grpcpp/grpcpp.h>
#include "generated/user_service.grpc.pb.h"
#include <memory>
#include <iostream>
class UserServiceImpl final : public myapp::UserService::Service {
public:
grpc::Status GetUser(
grpc::ServerContext* ctx,
const myapp::GetUserRequest* req,
myapp::UserResponse* resp) override
{
// Validate input
if (req->user_id() <= 0) {
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
"user_id must be positive");
}
// Simulate database lookup
if (req->user_id() == 1) {
resp->set_user_id(1);
resp->set_name("Alice");
resp->set_email("alice@example.com");
resp->set_created_at(1700000000);
resp->add_roles("admin");
resp->add_roles("user");
return grpc::Status::OK;
}
return grpc::Status(grpc::StatusCode::NOT_FOUND,
"user " + std::to_string(req->user_id()) + " not found");
}
grpc::Status CreateUser(
grpc::ServerContext* ctx,
const myapp::CreateUserRequest* req,
myapp::UserResponse* resp) override
{
if (req->name().empty() || req->email().empty()) {
return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
"name and email are required");
}
// Create user in database...
resp->set_user_id(42);
resp->set_name(req->name());
resp->set_email(req->email());
return grpc::Status::OK;
}
};
void RunServer() {
std::string address = "0.0.0.0:50051";
UserServiceImpl service;
grpc::ServerBuilder builder;
builder.AddListeningPort(address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << address << '\n';
server->Wait();
}
int main() {
RunServer();
}
3. gRPC C++ Client
// client.cc
#include <grpcpp/grpcpp.h>
#include "generated/user_service.grpc.pb.h"
#include <memory>
#include <iostream>
class UserServiceClient {
std::unique_ptr<myapp::UserService::Stub> stub_;
public:
explicit UserServiceClient(std::shared_ptr<grpc::Channel> channel)
: stub_(myapp::UserService::NewStub(channel)) {}
std::optional<myapp::UserResponse> GetUser(int32_t userId) {
myapp::GetUserRequest request;
request.set_user_id(userId);
myapp::UserResponse response;
grpc::ClientContext ctx;
// Set deadline — always set deadlines on client calls
ctx.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(5));
grpc::Status status = stub_->GetUser(&ctx, request, &response);
if (status.ok()) {
return response;
}
std::cerr << "GetUser failed: ["
<< status.error_code() << "] "
<< status.error_message() << '\n';
return std::nullopt;
}
};
int main() {
// Create a channel — reuse it across calls, don't create per-call
auto channel = grpc::CreateChannel("localhost:50051",
grpc::InsecureChannelCredentials());
UserServiceClient client(channel);
auto user = client.GetUser(1);
if (user) {
std::cout << "User: " << user->name()
<< " (" << user->email() << ")\n";
for (const auto& role : user->roles()) {
std::cout << " role: " << role << '\n';
}
}
}
4. Streaming RPCs
gRPC has three streaming modes in addition to basic unary RPC:
Server Streaming (server sends multiple responses)
// Server sends a stream of UserResponse
rpc ListUsers(ListUsersRequest) returns (stream UserResponse);
// Server implementation
grpc::Status ListUsers(
grpc::ServerContext* ctx,
const myapp::ListUsersRequest* req,
grpc::ServerWriter<myapp::UserResponse>* writer) override
{
// Fetch and stream rows from database
for (const auto& user : db.getAllUsers()) {
myapp::UserResponse resp;
resp.set_user_id(user.id);
resp.set_name(user.name);
if (!writer->Write(resp)) {
break; // client disconnected or cancelled
}
if (ctx->IsCancelled()) {
return grpc::Status(grpc::StatusCode::CANCELLED, "cancelled");
}
}
return grpc::Status::OK;
}
// Client usage
grpc::ClientContext ctx;
myapp::ListUsersRequest req;
auto reader = stub_->ListUsers(&ctx, req);
myapp::UserResponse resp;
while (reader->Read(&resp)) {
std::cout << resp.name() << '\n';
}
grpc::Status status = reader->Finish();
if (!status.ok()) { /* handle error */ }
Client Streaming (client sends multiple messages)
// Client sends a stream of records to import
rpc BulkImport(stream ImportRecord) returns (ImportResult);
// Client usage
grpc::ClientContext ctx;
myapp::ImportResult result;
auto writer = stub_->BulkImport(&ctx, &result);
for (const auto& record : localData) {
myapp::ImportRecord req;
req.set_data(record.serialize());
if (!writer->Write(req)) break; // server closed early
}
writer->WritesDone();
grpc::Status status = writer->Finish();
5. Error Handling
gRPC status codes map to common error categories:
| Status code | Meaning | Example use |
|---|---|---|
OK | Success | — |
INVALID_ARGUMENT | Bad client input | Missing required field |
NOT_FOUND | Resource doesn’t exist | User ID not in database |
ALREADY_EXISTS | Duplicate | Email already registered |
PERMISSION_DENIED | Auth failed | Invalid token |
UNAVAILABLE | Server temporarily down | Database connection failed |
DEADLINE_EXCEEDED | Timeout | Slow query |
RESOURCE_EXHAUSTED | Rate limit or quota | Too many requests |
// Server — return meaningful status codes
if (!db.userExists(req->user_id())) {
return grpc::Status(grpc::StatusCode::NOT_FOUND,
"user " + std::to_string(req->user_id()) + " not found");
}
// Client — check and handle each code
grpc::Status status = stub_->GetUser(&ctx, request, &response);
switch (status.error_code()) {
case grpc::StatusCode::OK:
break;
case grpc::StatusCode::NOT_FOUND:
std::cerr << "User not found\n";
break;
case grpc::StatusCode::DEADLINE_EXCEEDED:
std::cerr << "Request timed out — retry?\n";
break;
default:
std::cerr << "Error " << status.error_code()
<< ": " << status.error_message() << '\n';
}
6. TLS for Production
Never use InsecureChannelCredentials in production. Use TLS:
// Server with TLS
grpc::SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_key_cert_pairs.push_back({
ReadFile("server.key"),
ReadFile("server.crt"),
});
// Optionally add CA cert for mutual TLS
ssl_opts.pem_root_certs = ReadFile("ca.crt");
builder.AddListeningPort("0.0.0.0:443",
grpc::SslServerCredentials(ssl_opts));
// Client with TLS
grpc::SslCredentialsOptions ssl_client_opts;
ssl_client_opts.pem_root_certs = ReadFile("ca.crt"); // CA to verify server
auto channel = grpc::CreateChannel("service.example.com:443",
grpc::SslCredentials(ssl_client_opts));
7. Production Checklist
- Always set deadlines:
ctx.set_deadline(std::chrono::system_clock::now() + timeout)— missing deadlines cause requests to hang indefinitely - Reuse channels and stubs: creating a channel per request is expensive (TLS handshake, HTTP/2 setup) — create once and share
- Check
IsCancelled()in streaming handlers: clients may disconnect; don’t keep writing to a closed stream - Handle
UNAVAILABLEwith retry: transient failures (network blip, pod restart) should trigger retry with exponential backoff - Use
reservedfor removed fields: prevents accidental reuse of field numbers in future .proto changes - Health checks: implement the gRPC health checking protocol for load balancer integration
- Message size limits: large payloads hit the default 4MB limit — set
grpc.max_receive_message_lengthin ChannelArguments
Key Takeaways
.protofiles define the schema — field numbers are permanent identifiers, not the namesprotocgenerates typed C++ classes; the client stub and server base class come fromgrpc_cpp_plugin- Every unary RPC returns
grpc::Status— always checkstatus.ok()before using the response - Streaming modes (server, client, bidi) are first-class in gRPC — use them for large payloads or live feeds
- Always set deadlines on client calls — no deadline means the call may wait indefinitely
- Reuse channels — they are expensive to create; create one per target service and share it
- Use TLS (
SslServerCredentials,SslCredentials) in any non-local environment
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Build C++ gRPC microservices with Protocol Buffers: .
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ gRPC 기초 완벽 가이드 | Protocol Buffers·Unary·스트리밍·실전 문제 해결
- C++ gRPC 완벽 가이드 | 마이크로서비스 RPC·문제 해결·성능 최적화 [#52-1]
- C++ Protocol Buffers 완벽 가이드 | 직렬화·스키마 진화·성능 최적화·프로덕션 패턴
Keywords Covered in This Article (Related Search Terms)
This article covers C++, gRPC, Protocol Buffers, RPC, Microservices.