Building a gRPC Client
A gRPC client is built by first establishing a grpc.ClientConn (or its language equivalent) to a target address, which manages an underlying HTTP/2 connection including connection pooling, automatic reconnection with exponential backoff, and load balancing across multiple backend addresses when a resolver returns more than one. From that connection, the generated code produces a strongly-typed stub, such as NewGreeterClient(conn), whose methods mirror the service definition exactly, so calling client.SayHello(ctx, req) is type-checked at compile time and automatically handles protobuf marshaling, HTTP/2 framing, and unmarshaling of the response.
Cricket analogy: Establishing a ClientConn is like a broadcaster setting up the satellite uplink to the stadium before commentary starts, a one-time setup that then lets every ball-by-ball feed (RPC call) flow without renegotiating the connection each time.
Making Unary and Streaming Calls
Calling a unary method on the stub blocks (or awaits, in async languages) until the server responds or the context deadline is exceeded, returning either the response message or an error wrapping a gRPC status code that the caller should check with status.FromError(err) rather than string comparison. For streaming calls, the client obtains a stream object from the stub, for instance stream, err := client.ListGreetings(ctx, req), and then repeatedly calls stream.Recv() in a loop until it receives an io.EOF sentinel signaling the server has finished sending, and for client- or bidirectional-streaming RPCs the client calls stream.Send() for each outbound message and stream.CloseSend() when done writing.
Cricket analogy: A unary call is like requesting a single DRS review and waiting for the third umpire's one verdict, while a server-streaming call is like watching the live ball-by-ball scorecard update continuously until the innings ends (io.EOF equivalent).
import grpc
import greeter_pb2
import greeter_pb2_grpc
def run():
# Channel setup: connection pooling and retry handled internally
options = [("grpc.keepalive_time_ms", 30000)]
with grpc.insecure_channel("localhost:50051", options=options) as channel:
stub = greeter_pb2_grpc.GreeterStub(channel)
# Unary call with a 2-second deadline
try:
response = stub.SayHello(
greeter_pb2.HelloRequest(name="Priya"), timeout=2.0
)
print("Unary response:", response.message)
except grpc.RpcError as e:
print(f"Unary call failed: {e.code()} - {e.details()}")
# Server-streaming call
request = greeter_pb2.HelloRequest(name="Priya")
try:
for reply in stub.ListGreetings(request, timeout=5.0):
print("Streamed:", reply.message)
except grpc.RpcError as e:
print(f"Stream failed: {e.code()} - {e.details()}")
if __name__ == "__main__":
run()Connection Management, Retries, and Load Balancing
A single ClientConn is designed to be created once and reused across the application's lifetime, since it maintains a persistent HTTP/2 connection (or several, under client-side load balancing) and internally multiplexes many concurrent RPCs over that connection rather than opening a new TCP handshake per call; creating a new connection per request is a common performance mistake. Production clients should configure a service config with retry policy (e.g. maxAttempts, initialBackoff, retryableStatusCodes) so transient failures like UNAVAILABLE are retried automatically, and should specify a resolver and load-balancing policy such as round_robin when the target resolves to multiple backend addresses, since gRPC does client-side load balancing rather than relying solely on an external proxy.
Cricket analogy: Reusing a single ClientConn is like a team keeping the same warmed-up bowler operating through several overs instead of bringing on a fresh, unwarmed bowler for every single delivery, which would be wasteful and slow.
Always reuse a single ClientConn per target across the lifetime of your application (or pool a small number for very high-throughput services); gRPC-Go and grpc-python already multiplex concurrent RPCs efficiently over one HTTP/2 connection.
Blindly retrying every RPC on failure is unsafe for non-idempotent unary calls (e.g. a payment charge); only mark retryable status codes like UNAVAILABLE in the service config, and use idempotency keys for mutating operations.
- A ClientConn should be created once and reused; it manages persistent HTTP/2 connections, pooling, and automatic reconnection internally.
- Generated client stubs mirror the .proto service definition exactly, giving compile-time type safety for every RPC call.
- Unary calls block or await until a response or deadline; streaming calls use Recv()/Send() loops with io.EOF or CloseSend() to signal completion.
- Errors should be inspected via status.FromError(err) to get a structured gRPC status code, not by parsing error message strings.
- Service config can define automatic retry policies for transient failures like UNAVAILABLE, but only for idempotent operations.
- gRPC performs client-side load balancing (e.g. round_robin) across resolved backend addresses rather than relying purely on an external proxy.
- Deadlines should be set on every call to avoid unbounded blocking; a missing deadline can hang a client indefinitely on a stalled server.
Practice what you learned
1. Why should a gRPC ClientConn be created once and reused rather than per RPC call?
2. What does a server-streaming client typically use to detect that the server has finished sending messages?
3. Why should you use status.FromError(err) instead of comparing error message strings when handling a failed gRPC call?
4. When configuring automatic retries via service config, which status code is a typical candidate for safe retry?
5. What is the purpose of specifying a load-balancing policy such as round_robin on a gRPC client?
Was this page helpful?
You May Also Like
Implementing a gRPC Server
Learn how to define a gRPC service, generate server stubs, implement unary and streaming handlers, and manage a production server's lifecycle safely.
gRPC Error Handling
Understand gRPC's canonical status code model, how to choose the right code and attach structured details, and how to propagate and recover from errors safely.
gRPC Deadlines and Cancellation
Understand how gRPC deadlines bound RPC execution time, how they propagate across service chains, and how explicit cancellation stops unneeded work.
gRPC Metadata and Headers
Learn how gRPC carries cross-cutting information like auth tokens and trace IDs via initial and trailing metadata, separate from the protobuf message body.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics