gRPC Streaming — Server & Bidirectional Guide
In this tutorial, you'll learn about gRPC streaming. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
gRPC streaming enables long-lived connections where servers push multiple responses, clients send multiple requests, or both sides exchange data asynchronously over a single HTTP/2 stream.
What You'll Learn
You will learn the three gRPC streaming patterns — server-side, client-side, and bidirectional — with Protocol Buffer definitions and Python implementations using a threat intelligence feed as the example.
Why Streaming Matters
Unary RPCs (request-response) do not scale for real-time data feeds. Streaming eliminates polling overhead and enables instant event propagation. DodaTech's Durga Antivirus Pro uses gRPC bidirectional streaming to push threat intelligence updates to enterprise firewalls in real time.
Defining Streaming Services in Protobuf
syntax = "proto3";
package dodatech.threat;
service ThreatFeed {
// Unary: single request, single response
rpc GetThreat(ThreatRequest) returns (Threat);
// Server-side streaming: one request, stream of responses
rpc ListThreats(ThreatFilter) returns (stream Threat);
// Client-side streaming: stream of requests, one response
rpc SubmitThreats(stream ThreatSubmission) returns (SubmissionSummary);
// Bidirectional streaming: stream of requests, stream of responses
rpc MonitorThreats(stream ThreatQuery) returns (stream ThreatAlert);
}
message ThreatRequest {
string threat_id = 1;
}
message Threat {
string id = 1;
string name = 2;
string severity = 3;
string detected_at = 4;
bytes hash = 5;
}
message ThreatFilter {
string severity = 1;
int32 limit = 2;
}
message ThreatSubmission {
string name = 1;
string hash = 2;
string source = 3;
}
message SubmissionSummary {
int32 accepted = 1;
int32 rejected = 2;
repeated string errors = 3;
}
message ThreatQuery {
string severity_filter = 1;
int32 poll_interval_ms = 2;
}
message ThreatAlert {
string threat_id = 1;
string alert_type = 2;
string message = 3;
}
Server-Side Streaming
import grpc
import threat_pb2
import threat_pb2_grpc
from concurrent import futures
import time
class ThreatFeedServicer(threat_pb2_grpc.ThreatFeedServicer):
def ListThreats(self, request, context):
"""Server-side streaming: yields threats matching the filter."""
limit = request.limit or 10
count = 0
for threat in fetch_threats_from_db(severity=request.severity):
if count >= limit:
break
yield threat_pb2.Threat(
id=threat["id"],
name=threat["name"],
severity=threat["severity"],
detected_at=threat["detected_at"],
)
count += 1
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
threat_pb2_grpc.add_ThreatFeedServicer_to_server(
ThreatFeedServicer(), server
)
server.add_insecure_port("[::]:50051")
server.start()
server.wait_for_termination()
Expected client output:
# Client receives threats one by one as they stream in
Threat: id="thr_001" name="Emotet" severity="high"
Threat: id="thr_002" name="Mirai" severity="critical"
Threat: id="thr_003" name="AgentTesla" severity="medium"
Client-Side Streaming
class ThreatFeedServicer(threat_pb2_grpc.ThreatFeedServicer):
def SubmitThreats(self, request_iterator, context):
"""Client-side streaming: accepts a stream of submissions."""
accepted = 0
rejected = 0
errors = []
for submission in request_iterator:
if validate_hash(submission.hash):
store_threat(submission.name, submission.hash, submission.source)
accepted += 1
else:
rejected += 1
errors.append(f"Invalid hash for: {submission.name}")
return threat_pb2.SubmissionSummary(
accepted=accepted,
rejected=rejected,
errors=errors,
)
Expected client output:
# Client streams threats
submissions = [
threat_pb2.ThreatSubmission(name="Trojan A", hash="abc123", source="honeypot"),
threat_pb2.ThreatSubmission(name="Ransomware B", hash="def456", source="partner"),
]
summary = stub.SubmitThreats(iter(submissions))
print(f"Accepted: {summary.accepted}, Rejected: {summary.rejected}")
# Accepted: 2, Rejected: 0
Bidirectional Streaming
class ThreatFeedServicer(threat_pb2_grpc.ThreatFeedServicer):
def MonitorThreats(self, request_iterator, context):
"""Bidirectional streaming: client sends queries, server pushes alerts."""
def process_alerts():
for alert in threat_alert_generator():
yield threat_pb2.ThreatAlert(
threat_id=alert["id"],
alert_type=alert["type"],
message=alert["message"],
)
# Stream alerts and handle client queries concurrently
for client_query in request_iterator:
print(f"Client watching severity: {client_query.severity_filter}")
# Start alert stream
for alert in process_alerts():
if context.is_active():
yield alert
Expected behavior:
# Client connects and sends initial query
→ Server starts streaming alerts matching the filter
→ Client can send new queries to change the filter
→ Server adjusts the alert stream dynamically
→ Both sides exchange messages concurrently
flowchart LR
subgraph "gRPC Streaming Patterns"
SS["Server Streaming\nRequest → Stream Response\n(Threat Feed)"]
CS["Client Streaming\nStream Request → Response\n(Batch Submission)"]
BS["Bidirectional\nStream Request → Stream Response\n(Live Monitoring)"]
end
C["Client"] -->|"HTTP/2 Stream"| S["gRPC Server"]
subgraph "Use Cases"
U1["Real-time threat feed"]
U2["Batch file upload"]
U3["Live dashboard updates"]
end
SS --> U1
CS --> U2
BS --> U3
style SS fill:#dbeafe,stroke:#2563eb
style CS fill:#bbf7d0,stroke:#16a34a
style BS fill:#fef3c7,stroke:#d97706
Client Implementation (Python)
import grpc
import threat_pb2
import threat_pb2_grpc
def run():
channel = grpc.insecure_channel("localhost:50051")
stub = threat_pb2_grpc.ThreatFeedStub(channel)
# Server-side streaming
print("=== Server Streaming: List Threats ===")
for threat in stub.ListThreats(
threat_pb2.ThreatFilter(severity="critical", limit=5)
):
print(f"Threat: {threat.name} ({threat.severity})")
# Bidirectional streaming
print("=== Bidirectional: Monitor Threats ===")
def client_queries():
yield threat_pb2.ThreatQuery(severity_filter="critical")
time.sleep(30)
yield threat_pb2.ThreatQuery(severity_filter="all")
for alert in stub.MonitorThreats(client_queries()):
print(f"ALERT: {alert.message}")
if __name__ == "__main__":
run()
Expected output:
=== Server Streaming: List Threats ===
Threat: Log4j Exploit (critical)
Threat: Zero-Day RCE (critical)
Threat: Ransomware Variant (critical)
=== Bidirectional: Monitor Threats ===
ALERT: New critical threat detected: ID thr_042
ALERT: Threat signature updated: ID thr_018
Common Errors
1. Not Handling Context Cancellation
When a client disconnects, the context.is_active() check prevents the server from continuing to stream data to a dead connection.
2. Blocking the Event Loop
Stream Processing with blocking I/O (database queries) holds up the gRPC Thread pool. Use async gRPC or a dedicated Thread pool for I/O-bound work.
3. Message Size Exceeds Limits
gRPC has a default message size limit of 4MB. Large threat payloads may need grpc.max_send_message_length and grpc.max_receive_message_length configuration.
4. Missing Error Handling in Streams
An exception in a streaming handler terminates the entire stream. Wrap stream logic in try/except blocks and log errors before re-raising.
5. No Keepalive Pings
Idle streams may be dropped by intermediate proxies. Configure gRPC keepalive pings to maintain the connection.
Practice Questions
1. What HTTP/2 feature enables gRPC streaming?
HTTP/2 multiplexed streams allow multiple concurrent messages over a single TCP connection without head-of-line blocking.
2. How does bidirectional streaming differ from server-side streaming?
Server-side: client sends one request, server sends multiple responses. Bidirectional: both sides send multiple messages independently over the same stream.
3. What is the default gRPC max message size?
4MB. This can be increased with channel and server configuration options.
4. Challenge: Implement a gRPC health check service with server-side streaming that reports server status changes.
Use a WatchHealth RPC that streams health status (SERVING, NOT_SERVING) whenever the server State changes, and a unary Check RPC for immediate status.
Mini Project: Threat Alert Stream
Build a gRPC bidirectional streaming service for DodaTech's threat alert system. Enterprise firewall clients connect, subscribe with severity filters, and receive real-time threat alerts. Include keepalive, error handling, and reconnection logic.
Related Tutorials
gRPC — RESTful API Design — WebSocket
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-24.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro