Skip to content

gRPC Client Streaming — Sending Multiple Requests to Server

DodaTech Updated 2026-06-28 2 min read

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

  1. When is client streaming useful?
  2. How does the server iterate over client requests?
  3. How do you handle partial failures?
  4. What is the client-side generator pattern?
  5. How do you rate-limit client stream sends?

Answers:

  1. For batch uploads (scan results, logs), large file chunking, and progressive form submissions where the client sends data over time.
  2. The server receives a request_<a href="/design-patterns/iterator/">Iterator</a> and iterates with a for loop. Each iteration yields one client message.
  3. If some items failed, include error details in the response summary. The client can retry failed items.
  4. Define a generator function that yields request messages. Pass it to the stub method: stub.RpcName(generator()).
  5. 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
⬅ Server Streaming
➡ Bidirectional Streaming

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro