[2026] C++ gRPC Complete Guide | Microservice RPC, Troubleshooting, Performance Optimization [#52-1]
이 글의 핵심
Struggling with connection timeouts, serialization costs, and error handling when using gRPC instead of C++ REST API for microservice communication? From Protocol Buffers to complete server/client examples, common errors, performance…
Introduction: Microservice Communication is Slow with REST API
Real Problem Scenarios
Scenario 1: Order Service → Inventory Service Call Delay
Order API calls another service via HTTP REST to check inventory. JSON serialization/deserialization cost and HTTP overhead take 50-100ms per request. Bottleneck occurs at 1000 orders per second.
Scenario 2: Real-time Log Collection Connection Drops
Multiple microservices need to stream logs to central log server. HTTP polling is inefficient, WebSocket needs separate implementation. When connection drops, must implement reconnection and retry logic directly.
Scenario 3: Client/Server Mismatch on Schema Change
REST API has schema scattered across code and docs. After updating server, old client sending wrong fields causes runtime errors. Version compatibility must be managed manually.
Scenario 4: “Connection refused” Error After gRPC Introduction
Started gRPC server but client cannot connect. Many things to check like HTTP/2, TLS settings, port firewall, overwhelming.
Scenario 5: Memory Explosion on Large Response
Returning 100,000 records as JSON array at once with REST API causes memory usage spike on both server and client. Even with pagination, HTTP request overhead accumulates.
Scenario 6: Version Mismatch Between Services
When Service A expects v1 API and Service B expects v2, REST causes errors at runtime with missing/added fields. Schema version management is difficult.
Solved with gRPC:
- Binary serialization (Protocol Buffers): 3-10x smaller payload vs JSON, 5-10x faster serialization
- HTTP/2 based: Multiplexing, multiple streams over single connection
- Schema-based: Define service and messages in .proto → code generation for type safety
- Standard streaming support: Server/client/bidirectional streaming 아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
flowchart LR
subgraph rest[REST API]
R1[Client] -->|JSON/HTTP| R2[Server]
R2 -->|JSON/HTTP| R1
end
subgraph grpc[gRPC]
G1[Client] -->|Protobuf/HTTP2| G2[Server]
G2 -->|Protobuf/HTTP2| G1
end
gRPC Communication Flow
아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
sequenceDiagram participant C as Client participant S as Server C->>S: EchoRequest (Protobuf serialization) Note over S: Business logic processing S->>C: EchoResponse (Protobuf serialization) C->>C: Check status.ok()
REST vs gRPC Comparison
| Item | REST + JSON | gRPC + Protobuf |
|---|---|---|
| Serialization | Text, slow | Binary, fast |
| Schema | OpenAPI, etc., separate | Integrated in .proto |
| Streaming | WebSocket separate implementation | Standard support |
| HTTP | HTTP/1.1 (default) | HTTP/2 (multiplexing) |
| What This Guide Covers: |
- Protocol Buffers: .proto definition and code generation
- Complete gRPC C++ server and client examples
- Common errors and solutions
- Performance optimization tips
- Production deployment patterns
Table of Contents
- Environment Setup
- Protocol Buffers Basics
- Complete gRPC Example
- Streaming Examples
- Common Errors and Solutions
- Performance Optimization
- Production Patterns
- Implementation Checklist
1. Environment Setup
Required Dependencies
| Item | Version | Note |
|---|---|---|
| C++ | C++14 or higher | C++17 recommended |
| gRPC | 1.50+ | vcpkg or source build |
| Protocol Buffers | 3.21+ | Check version compatibility with gRPC |
| CMake | 3.16+ | FindPackage support |
Install with vcpkg
# Install gRPC with vcpkg (includes Protobuf)
vcpkg install grpc
Basic CMakeLists.txt Setup
다음은 cmake를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
cmake_minimum_required(VERSION 3.16)
project(grpc_example LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(Protobuf REQUIRED)
find_package(gRPC CONFIG REQUIRED)
# Generate C++ code from .proto file
set(PROTO_PATH "${CMAKE_CURRENT_SOURCE_DIR}/proto")
set(GENERATED_PROTOBUF_PATH "${CMAKE_BINARY_DIR}/generated")
file(MAKE_DIRECTORY ${GENERATED_PROTOBUF_PATH})
set(PROTO_FILES "${PROTO_PATH}/echo.proto")
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILES})
# Set generated file paths
include_directories(${GENERATED_PROTOBUF_PATH})
include_directories(${PROTOBUF_INCLUDE_DIRS})
add_executable(grpc_server
server.cc
${PROTO_SRCS}
)
target_link_libraries(grpc_server
PRIVATE gRPC::grpc++ gRPC::grpc++_reflection protobuf::libprotobuf
)
add_executable(grpc_client
client.cc
${PROTO_SRCS}
)
target_link_libraries(grpc_client
PRIVATE gRPC::grpc++ gRPC::grpc++_reflection protobuf::libprotobuf
)
2. Protocol Buffers Basics
.proto File Definition
Field numbers are important for schema compatibility. Maintaining backward compatibility by not changing existing numbers and only adding new fields. 다음은 protobuf를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
syntax = "proto3";
package echo;
// Echo service: Server returns message as-is when client sends
service EchoService {
// Single request-response (Unary)
rpc Echo(EchoRequest) returns (EchoResponse);
// Server streaming: Client 1 request → Server multiple responses
rpc ServerStream(EchoRequest) returns (stream EchoResponse);
// Client streaming: Client multiple requests → Server 1 response
rpc ClientStream(stream EchoRequest) returns (EchoResponse);
// Bidirectional streaming
rpc BidirectionalStream(stream EchoRequest) returns (stream EchoResponse);
}
message EchoRequest {
string message = 1;
int32 sequence = 2;
}
message EchoResponse {
string message = 1;
int32 sequence = 2;
}
Code Generation
다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Generate C++ code with protoc (using gRPC plugin)
protoc -I proto --cpp_out=generated --grpc_out=generated \
--plugin=protoc-gen-grpc=`which grpc_cpp_plugin` \
proto/echo.proto
Generated files:
echo.pb.h,echo.pb.cc: Message classesecho.grpc.pb.h,echo.grpc.pb.cc: Service Stub and Service base
3. Complete gRPC Example
3.1 Server Implementation (Synchronous)
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <grpcpp/grpcpp.h>
#include <iostream>
#include <memory>
#include <string>
#include "echo.grpc.pb.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
class EchoServiceImpl final : public echo::EchoService::Service {
public:
// Unary RPC: 1 request → 1 response
Status Echo(ServerContext* context,
const echo::EchoRequest* request,
echo::EchoResponse* response) override {
// 1. Request validation
if (request->message().empty()) {
return Status(grpc::StatusCode::INVALID_ARGUMENT,
"message must not be empty");
}
// 2. Business logic: Return message as-is
response->set_message(request->message());
response->set_sequence(request->sequence());
return Status::OK;
}
};
void RunServer() {
std::string server_address("0.0.0.0:50051");
EchoServiceImpl service;
ServerBuilder builder;
// Insecure channel without authentication (for development)
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wait(); // Blocking, exit with Ctrl+C
}
int main() {
RunServer();
return 0;
}
Code Explanation:
EchoServiceImpl: Inheritsecho::EchoService::Serviceto implement RPC methodsStatus::OK: Return on successStatus(StatusCode, message): Return with detailed message on errorServerBuilder: Set address, authentication, options, then start server withBuildAndStart()
3.2 Client Implementation (Synchronous)
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <grpcpp/grpcpp.h>
#include <iostream>
#include <memory>
#include <string>
#include "echo.grpc.pb.h"
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
class EchoClient {
public:
EchoClient(std::shared_ptr<Channel> channel)
: stub_(echo::EchoService::NewStub(channel)) {}
std::string Echo(const std::string& message) {
echo::EchoRequest request;
request.set_message(message);
request.set_sequence(1);
echo::EchoResponse response;
ClientContext context;
// Set deadline: Fail if no response within 5 seconds
std::chrono::system_clock::time_point deadline =
std::chrono::system_clock::now() + std::chrono::seconds(5);
context.set_deadline(deadline);
Status status = stub_->Echo(&context, request, &response);
if (status.ok()) {
return response.message();
} else {
std::cerr << "RPC failed: " << status.error_code()
<< " - " << status.error_message() << std::endl;
return "";
}
}
private:
std::unique_ptr<echo::EchoService::Stub> stub_;
};
int main() {
// Create channel: Server address, authentication (none)
auto channel = grpc::CreateChannel(
"localhost:50051",
grpc::InsecureChannelCredentials());
// Wait for connection (optional)
if (channel->WaitForConnected(
std::chrono::system_clock::now() + std::chrono::seconds(5))) {
EchoClient client(channel);
std::string reply = client.Echo("Hello, gRPC!");
std::cout << "Echo received: " << reply << std::endl;
} else {
std::cerr << "Failed to connect to server" << std::endl;
return 1;
}
return 0;
}
Code Explanation:
CreateChannel: Create channel with server address and credentials. Channel is reusableClientContext: Per-request metadata, deadline settingsset_deadline: Set timeout to prevent infinite waitstatus.ok(): Check success, on failure check details witherror_code()anderror_message()
3.3 Execution Order
아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Terminal 1: Run server
./grpc_server
# Terminal 2: Run client
./grpc_client
Expected Output (client): Echo received: Hello, gRPC!
3.4 Error Handling Wrapper (For Production)
Real projects need branching by error code and logging. 다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
template<typename Func>
grpc::Status CallWithLogging(const char* rpc_name, Func&& func) {
auto start = std::chrono::steady_clock::now();
grpc::Status status = func();
auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start).count();
if (status.ok()) {
LOG(INFO) << rpc_name << " OK in " << dur << "ms";
} else {
LOG(ERROR) << rpc_name << " FAILED: " << status.error_code()
<< " - " << status.error_message() << " (" << dur << "ms)";
}
return status;
}
// Usage example
Status status = CallWithLogging("Echo", [&]() {
return stub_->Echo(&context, request, &response);
});
4. Streaming Examples
4.1 Server Streaming
Client makes 1 request → Server streams multiple responses. Example: Log query, large data chunk transmission. 다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Server side
Status ServerStream(ServerContext* context,
const echo::EchoRequest* request,
grpc::ServerWriter<echo::EchoResponse>* writer) override {
for (int i = 0; i < 5; ++i) {
echo::EchoResponse response;
response.set_message(request->message() + " #" + std::to_string(i));
response.set_sequence(i);
// Check client disconnection
if (context->IsCancelled()) {
return Status(grpc::StatusCode::CANCELLED, "Client disconnected");
}
if (!writer->Write(response)) {
break; // Write failed (client disconnected, etc.)
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return Status::OK;
}
// Client side
void ServerStream(const std::string& message) {
echo::EchoRequest request;
request.set_message(message);
ClientContext context;
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(10));
auto reader = stub_->ServerStream(&context, request);
echo::EchoResponse response;
while (reader->Read(&response)) {
std::cout << "Received: " << response.message() << std::endl;
}
Status status = reader->Finish();
if (!status.ok()) {
std::cerr << "Stream failed: " << status.error_message() << std::endl;
}
}
4.2 Client Streaming
Client sends multiple requests → Server gives 1 response. Example: Sending multiple file chunks and receiving hash. 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Server side
Status ClientStream(ServerContext* context,
grpc::ServerReader<echo::EchoRequest>* reader,
echo::EchoResponse* response) override {
std::string concatenated;
echo::EchoRequest request;
int count = 0;
while (reader->Read(&request)) {
concatenated += request.message() + " ";
++count;
}
response->set_message(concatenated);
response->set_sequence(count);
return Status::OK;
}
// Client side
std::string ClientStream(const std::vector<std::string>& messages) {
ClientContext context;
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(10));
echo::EchoResponse response;
auto writer = stub_->ClientStream(&context, &response);
for (const auto& msg : messages) {
echo::EchoRequest request;
request.set_message(msg);
if (!writer->Write(request)) break;
}
writer->WritesDone();
Status status = writer->Finish();
if (!status.ok()) return "";
return response.message();
}
4.3 Bidirectional Streaming
Client and server read/write simultaneously. Example: Chat, real-time collaboration. 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Server side
Status BidirectionalStream(
ServerContext* context,
grpc::ServerReaderWriter<echo::EchoResponse, echo::EchoRequest>* stream) override {
echo::EchoRequest request;
while (stream->Read(&request)) {
echo::EchoResponse response;
response.set_message(request.message());
response.set_sequence(request.sequence());
stream->Write(response);
}
return Status::OK;
}
// Client side
void BidirectionalStream() {
ClientContext context;
auto stream = stub_->BidirectionalStream(&context);
// Write thread
std::thread writer([&]() {
for (int i = 0; i < 10; ++i) {
echo::EchoRequest request;
request.set_message("msg" + std::to_string(i));
stream->Write(request);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
stream->WritesDone();
});
// Read thread (main)
echo::EchoResponse response;
while (stream->Read(&response)) {
std::cout << "Received: " << response.message() << std::endl;
}
writer.join();
Status status = stream->Finish();
}
5. Common Errors and Solutions
Error 1: “Connection refused” / “Failed to connect”
Symptom: Client cannot connect to server. Causes:
- Server not running
- Wrong address/port
- Firewall blocking Solutions: 다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Check server port listening
netstat -an | grep 50051
# Or
lsof -i :50051
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Client: Wait before connection
auto channel = grpc::CreateChannel("localhost:50051",
grpc::InsecureChannelCredentials());
// Channel is lazy connect. Attempts connection on first RPC.
// Explicit wait:
channel->WaitForConnected(
std::chrono::system_clock::now() + std::chrono::seconds(5));
Error 2: “DEADLINE_EXCEEDED”
Symptom: RPC call fails with timeout. Cause: Server processing time exceeds deadline. Solutions: 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Set sufficient deadline
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(30));
// ✅ Server side: Periodically check IsCancelled() for long-running tasks
Status LongRunningRpc(ServerContext* context, ...) {
for (int i = 0; i < 1000; ++i) {
if (context->IsCancelled()) {
return Status(grpc::StatusCode::CANCELLED, "Deadline exceeded");
}
DoWork(i);
}
return Status::OK;
}
Error 3: “UNAVAILABLE” (Server Restarting)
Symptom: Client requests fail during server redeployment. Cause: Temporary UNAVAILABLE returned when connection drops. Solutions: 다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Retry + exponential backoff
std::string EchoWithRetry(const std::string& message, int max_retries = 3) {
for (int i = 0; i < max_retries; ++i) {
ClientContext context;
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(5));
echo::EchoResponse response;
Status status = stub_->Echo(&context, request, &response);
if (status.ok()) return response.message();
if (status.error_code() == grpc::StatusCode::UNAVAILABLE) {
std::this_thread::sleep_for(std::chrono::milliseconds(100 * (1 << i)));
continue;
}
break; // Non-retryable error
}
return "";
}
Error 4: “INVALID_ARGUMENT” - Missing Protobuf Field
Symptom: request->message() call returns only default value.
Cause: Client did not set field. In proto3, default values (empty string, 0, etc.) used when unset.
Solutions:
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Wrong code
echo::EchoRequest request;
// message setting missing!
// ✅ Correct code
echo::EchoRequest request;
request.set_message("Hello");
request.set_sequence(1);
// ✅ Validate on server
if (request->message().empty()) {
return Status(grpc::StatusCode::INVALID_ARGUMENT, "message required");
}
Error 5: “CANCELLED” - Client Exit During Streaming
Symptom: Write() fails or IsCancelled() true during server streaming.
Cause: Client disconnected or deadline exceeded.
Solutions:
아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ Server: Check IsCancelled() before each Write
while (...) {
if (context->IsCancelled()) {
return Status(grpc::StatusCode::CANCELLED, "Client disconnected");
}
writer->Write(response);
}
Error 6: Memory Leak - Channel/CompletionQueue Not Released
Symptom: Memory usage increases over long run.
Cause: grpc::Channel, CompletionQueue kept global/static without calling Shutdown.
Solutions:
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ Manage channel with stub in scope
{
auto channel = grpc::CreateChannel("localhost:50051",
grpc::InsecureChannelCredentials());
auto stub = echo::EchoService::NewStub(channel);
// ....RPC calls
} // Auto-released at scope end
// ✅ On server shutdown
server->Shutdown();
server->Wait(); // Wait for Shutdown completion
Error 7: “Protocol Buffers version mismatch”
Symptom: Link or runtime error. Cause: Mismatch between code generated by protoc and linked libprotobuf version. Solutions: 다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Check protoc and library versions
protoc --version
# libprotobuf version should match built gRPC/Protobuf
# When using vcpkg, built from same source so matches
Error 8: “ResourceExhausted” - Concurrent Connection Limit
Symptom: RESOURCE_EXHAUSTED or connection failure under load.
Cause: Exceeds server’s maximum concurrent streams/connections.
Solutions:
아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Server: Relax concurrent processing limit (check defaults)
builder.SetMaxReceiveMessageSize(4 * 1024 * 1024); // 4MB
builder.SetMaxSendMessageSize(4 * 1024 * 1024);
// Channel: Connection pooling (load balance multiple channels)
std::vector<std::shared_ptr<Channel>> channels;
for (const auto& addr : server_addresses) {
channels.push_back(grpc::CreateChannel(addr, creds));
}
// Select stub with round-robin, etc.
6. Performance Optimization
6.1 Channel Reuse
Channels are thread-safe and should be reused across multiple RPCs. Creating new channel per request has large connection overhead. 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ Bad example: New channel per request
void BadClient() {
for (int i = 0; i < 1000; ++i) {
auto channel = grpc::CreateChannel("localhost:50051",
grpc::InsecureChannelCredentials());
auto stub = echo::EchoService::NewStub(channel);
// RPC...
}
}
// ✅ Good example: Reuse channel and stub
void GoodClient() {
auto channel = grpc::CreateChannel("localhost:50051",
grpc::InsecureChannelCredentials());
auto stub = echo::EchoService::NewStub(channel);
for (int i = 0; i < 1000; ++i) {
// RPC...
}
}
6.2 Message Reuse (Repeated RPC)
Reusing Request/Response objects in repeated calls reduces allocation count. 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ Reuse Response
echo::EchoResponse response;
for (int i = 0; i < 1000; ++i) {
request.set_message("msg" + std::to_string(i));
request.set_sequence(i);
response.Clear(); // Initialize previous value
stub_->Echo(&context, request, &response);
}
6.3 Large Data Transfer with Streaming
Sending large data in single request-response has high memory and serialization cost. Use chunk-based streaming. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Large file transfer: Chunk streaming
message FileChunk {
bytes data = 1;
int64 offset = 2;
bool last_chunk = 3;
}
6.4 Keepalive Settings
Prevents long idle connections from dropping. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Channel options
grpc::ChannelArguments args;
args.SetInt("grpc.keepalive_time_ms", 10000); // Keepalive every 10 seconds
args.SetInt("grpc.keepalive_timeout_ms", 5000);
args.SetInt("grpc.keepalive_permit_without_calls", 1);
auto channel = grpc::CreateCustomChannel(
"localhost:50051",
grpc::InsecureChannelCredentials(),
args);
6.5 Performance Comparison (Reference)
| Method | Serialization | Approx Latency (same network) |
|---|---|---|
| REST + JSON | Slow, large payload | 1x baseline |
| gRPC + Protobuf | Fast, small payload | 0.2~0.5x |
| gRPC + Streaming | Chunk-based, memory efficient | Advantageous for large data |
7. Production Patterns
7.1 TLS Authentication
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Server: TLS credentials
grpc::SslServerCredentialsOptions::PemKeyCertPair keycert = {
private_key_content,
cert_chain_content
};
grpc::SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_key_cert_pairs.push_back(keycert);
auto creds = grpc::SslServerCredentials(ssl_opts);
builder.AddListeningPort(server_address, creds);
// Client: TLS channel
auto creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
auto channel = grpc::CreateChannel("myservice.example.com:443", creds);
7.2 Metadata (Auth Token, Tracing)
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Client: Add metadata
ClientContext context;
context.AddMetadata("authorization", "Bearer " + token);
context.AddMetadata("x-request-id", GenerateRequestId());
stub_->Echo(&context, request, &response);
// Server: Read metadata
void Echo(ServerContext* context, ...) {
auto auth = context->client_metadata().find("authorization");
if (auth == context->client_metadata().end()) {
return Status(grpc::StatusCode::UNAUTHENTICATED, "Missing token");
}
// Validate token...
}
7.3 Health Check
다음은 간단한 protobuf 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse);
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
}
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
Status HealthCheck(ServerContext* context,
const HealthCheckRequest* request,
HealthCheckResponse* response) override {
if (IsHealthy()) {
response->set_status(HealthCheckResponse::SERVING);
} else {
response->set_status(HealthCheckResponse::NOT_SERVING);
}
return Status::OK;
}
7.4 Graceful Shutdown
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
void RunServer() {
std::unique_ptr<Server> server(builder.BuildAndStart());
// Register signal handler
std::signal(SIGINT, [&](int) { server->Shutdown(); });
server->Wait(); // Returns when Shutdown() called
}
7.5 Logging and Metrics
아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// Log before and after RPC
Status Echo(ServerContext* context,
const echo::EchoRequest* request,
echo::EchoResponse* response) override {
auto start = std::chrono::steady_clock::now();
LOG(INFO) << "Echo request: " << request->message();
Status s = DoEcho(request, response);
auto dur = std::chrono::steady_clock::now() - start;
LOG(INFO) << "Echo completed in " << dur.count() << " ns, status=" << s.error_code();
return s;
}
7.6 Multiple Service Registration
One server can provide multiple gRPC services. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
EchoServiceImpl echo_service;
HealthServiceImpl health_service;
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&echo_service);
builder.RegisterService(&health_service);
std::unique_ptr<Server> server(builder.BuildAndStart());
7.7 Environment-specific Config (Dev/Staging/Production)
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
struct GrpcConfig {
std::string address;
bool use_tls;
int deadline_seconds;
};
GrpcConfig LoadConfig() {
const char* env = std::getenv("GRPC_ENV");
if (!env || strcmp(env, "production") != 0) {
return {"localhost:50051", false, 5}; // Development
}
return {"myservice:443", true, 30}; // Production
}
void RunClient() {
auto config = LoadConfig();
auto creds = config.use_tls
? grpc::SslCredentials(grpc::SslCredentialsOptions())
: grpc::InsecureChannelCredentials();
auto channel = grpc::CreateChannel(config.address, creds);
// ...
}
8. Implementation Checklist
- Define Protocol Buffers .proto (fix field numbers)
- Generate C++ code with protoc
- Link gRPC and Protobuf with CMake/vcpkg
- Server:
RegisterService,BuildAndStart,Wait - Client:
CreateChannel,NewStub, set deadline - Error handling: Check
status.ok(), logerror_message() - Streaming: Periodically check
IsCancelled() - Reuse channel and stub (no new channel per request)
- Production: TLS, metadata auth, health check, Graceful Shutdown
Problem Scenario Solution Summary
| Problem | gRPC Solution |
|---|---|
| REST delay and bottleneck | Protobuf binary serialization, HTTP/2 multiplexing |
| Real-time log streaming | Server streaming RPC standard support |
| Schema mismatch | .proto-based code generation, backward compatibility with field numbers |
| Connection refused | Check port/firewall, use WaitForConnected |
| Large response memory | Chunk-based transmission with streaming |
| Version mismatch | Maintain .proto field numbers, add only new fields |
Summary
| Item | Summary |
|---|---|
| Protocol Buffers | Define schema in .proto → Generate C++ code, maintain compatibility with field numbers |
| gRPC Server | ServerBuilder → RegisterService → BuildAndStart → Wait |
| gRPC Client | CreateChannel → NewStub → RPC call, deadline required |
| Streaming | ServerWriter/Reader, ClientReader/Writer, check IsCancelled() |
| Errors | Retry DEADLINE_EXCEEDED and UNAVAILABLE, handle CANCELLED |
| Performance | Reuse channel, reuse messages, streaming, Keepalive |
| Production | TLS, metadata, health check, Graceful Shutdown |
Frequently Asked Questions (FAQ)
Q. When should I use gRPC instead of REST?
A. gRPC is advantageous for microservice communication, high-performance internal APIs, and when streaming is needed. If calling directly from browser, REST/JSON is convenient.
Q. Can I communicate with languages other than C++?
A. Yes. gRPC supports multiple languages. Can generate C++, Go, Java, Python, etc., clients/servers from same .proto.
Q. When to use async API?
A. Use CompletionQueue-based async API when processing many RPCs simultaneously on high-load server. Covered in gRPC Master (#52-2).
One-line Summary: Build type-safe and high-performance microservice RPC with gRPC and Protobuf.
Next Post: gRPC Master: Streaming, Interceptors, Load Balancing (#52-2)
Previous Post: C++ Series Index
Related Posts
- C++ gRPC Basics Complete Guide | Protocol Buffers, Unary, Streaming, Practical Problem Solving
- API Design Guide | REST vs GraphQL vs gRPC Comparison
Keywords
C++, gRPC, Protocol Buffers, RPC, Microservices, HTTP/2, Streaming, Performance, FFmpeg, libx264, Tutorial