Chapter 9: Channels, Connections, and Subchannels

A gRPC Channel is the client's view of a connection to a server. But it's much more than a raw TCP socket — it's a smart abstraction that handles connection pooling, load balancing, reconnection, and health checking.

The Channel Architecture

┌─────────────────────────────────────────────────────────┐ │ gRPC Channel │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Name Resolver │ │ │ │ "dns:///myservice.prod:443" │ │ │ │ → resolves to: [10.0.1.1:443, 10.0.1.2:443] │ │ │ └──────────────────────┬──────────────────────────┘ │ │ │ │ │ ┌──────────────────────▼──────────────────────────┐ │ │ │ Load Balancer (pick_first / round_robin)│ │ │ └───────┬──────────────────────────────┬──────────┘ │ │ │ │ │ │ ┌────────▼────────┐ ┌─────────▼────────┐ │ │ │ Subchannel 1 │ │ Subchannel 2 │ │ │ │ → 10.0.1.1:443 │ │ → 10.0.1.2:443 │ │ │ │ [HTTP/2 conn] │ │ [HTTP/2 conn] │ │ │ └─────────────────┘ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘

Channel States

A channel transitions through these states:

┌──────┐ connect() ┌────────────┐ │ IDLE │ ─────────────────► │ CONNECTING │ └──┬───┘ └─────┬──────┘ ▲ │ │ timeout │ success │ ▼ │ ┌──────────┐ │ │ READY │ ◄── RPCs flow here │ └────┬─────┘ │ │ failure │ ▼ │ ┌───────────────────┐ └────────────────────│ TRANSIENT_FAILURE │ └───────────────────┘ │ │ fatal ▼ ┌──────────┐ │ SHUTDOWN │ └──────────┘
StateMeaningRPCs?
IDLENo connection attempt yet (lazy connect)Queued, triggers connect
CONNECTINGTCP/TLS handshake in progressQueued
READYConnected, RPCs can flowYes
TRANSIENT_FAILUREConnection failed, will retry with backoffFail fast or queue
SHUTDOWNChannel closed by userRejected

Channel Creation

// C++ — Create a channel (this is CHEAP, do it once)
auto channel = grpc::CreateChannel(
    "myservice.example.com:443",
    grpc::SslCredentials(grpc::SslCredentialsOptions())
);

// Channel is LAZY — no connection until first RPC
// Channel is LONG-LIVED — reuse across your application's lifetime
// Channel is THREAD-SAFE — share across threads

// Create stub from channel
auto stub = MyService::NewStub(channel);
⚠️ Common Mistake

Do NOT create a new channel per RPC. Channels are expensive to set up (DNS resolution, TCP handshake, TLS handshake) but cheap to reuse. Create one channel at startup and share it. One channel can handle thousands of concurrent RPCs via HTTP/2 multiplexing.

Subchannels

A subchannel represents a connection to a single backend address. When a name resolves to multiple IPs, the channel creates one subchannel per address:

// Target: "dns:///myservice.prod:443"
// DNS resolves to: 10.0.1.1, 10.0.1.2, 10.0.1.3
// → Channel creates 3 subchannels
// → Load balancer picks which subchannel to use per RPC

Connection Keepalive

gRPC uses HTTP/2 PING frames to detect dead connections:

// C++ channel args for keepalive
grpc::ChannelArguments args;

// Send keepalive ping every 30 seconds if no activity
args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 30000);

// Wait 10 seconds for ping response before considering dead
args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 10000);

// Send keepalive even with no active RPCs
args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);

auto channel = grpc::CreateCustomChannel(target, creds, args);
💡 Why Keepalive Matters

In Kubernetes/cloud environments, load balancers and NAT gateways silently drop idle TCP connections (typically after 5-10 minutes). Without keepalive, your next RPC after idle time hits a dead connection and fails. Keepalive pings prevent this.

Channel vs Connection vs Stream

ConceptLifetimeMultiplicity
ChannelApplication lifetime1 per target service
SubchannelUntil address removed1 per backend address
HTTP/2 ConnectionUntil failure/GOAWAY1 per subchannel
HTTP/2 StreamDuration of one RPCMany per connection (100s)
Key Takeaways