gRPC Client Streaming — Sending Multiple Requests to Server
In this tutorial, you will learn about grpc client streaming. We cover key concepts, practical examples, and best practices to help you master this topic.
Client-side streaming RPCs send a stream of requests from the client to the server, which processes them and returns a single response — ideal for batch uploads and data ingestion.
Client Streaming Definition
service ScanService {
rpc SubmitScanResults(stream ScanResult) returns (ScanSummary);
rpc UploadLogs(stream LogEntry) returns (UploadStatus);
}
Server Implementation
class ScanServiceServicer(scan_pb2_grpc.ScanServiceServicer):
def SubmitScanResults(self, request_iterator, context):
total_scanned = 0
threats_found = 0
for scan_result in request_iterator:
total_scanned += 1
if scan_result.threats_count > 0:
threats_found += 1
db.record_threats(scan_result.device_id, scan_result.threats_count)
return scan_pb2.ScanSummary(
total_scanned=total_scanned,
threats_found=threats_found,
status="completed",
)
Client Implementation
def run():
channel = grpc.insecure_channel("localhost:50051")
stub = scan_pb2_grpc.ScanServiceStub(channel)
def generate_scan_results():
results = [
{"device_id": "dev-001", "threats_count": 2},
{"device_id": "dev-002", "threats_count": 0},
{"device_id": "dev-003", "threats_count": 5},
]
for result in results:
yield scan_pb2.ScanResult(
device_id=result["device_id"],
threats_count=result["threats_count"],
timestamp=time.time(),
)
summary = stub.SubmitScanResults(generate_scan_results())
print(f"Scanned: {summary.total_scanned}, Threats: {summary.threats_found}")
Common Mistakes
1. Not Handling Client Disconnection
If the client stops sending, the server blocks. Check context.is_active() and handle prematurely terminated streams.
2. Sending Messages Too Fast
Without flow control, rapid client sends overwhelm the server. Implement client-side rate limiting or batching.
3. Memory Accumulation in Server
Don't accumulate all requests in memory before processing. Process each request as it arrives.
4. No Client-Side Error Recovery
If the server returns an error mid-stream, the client must restart. Implement retry logic for partial failures.
5. Large Individual Messages
Each message in the stream should be reasonably sized. Chunk large payloads into smaller messages.
Practice Questions
- When is client streaming useful?
- How does the server iterate over client requests?
- How do you handle partial failures?
- What is the client-side generator pattern?
- How do you rate-limit client stream sends?
Answers:
- For batch uploads (scan results, logs), large file chunking, and progressive form submissions where the client sends data over time.
- The server receives a
request_<a href="/design-patterns/iterator/">Iterator</a>and iterates with a for loop. Each iteration yields one client message. - If some items failed, include error details in the response summary. The client can retry failed items.
- Define a generator function that yields request messages. Pass it to the stub method:
stub.RpcName(generator()). - Use
time.sleep()between sends or use a Semaphore to limit concurrent in-flight messages.
Mini Project
Build a client streaming gRPC service for DodaTech's log ingestion. Clients stream log entries to the server, which processes them and returns a summary. Include validation, error reporting, and rate limiting.
What's Next
| Topic | Description |
|---|---|
| BiDi Streaming | Both sides stream simultaneously |
| Unary RPC | Request-response pattern |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro