Skip to content

gRPC Bidirectional Streaming — Full-Duplex Real-Time Communication

DodaTech Updated 2026-06-28 2 min read

In this tutorial, you will learn about grpc bidirectional streaming. We cover key concepts, practical examples, and best practices to help you master this topic.

Bidirectional streaming enables both client and server to send messages simultaneously over a single gRPC stream — the most powerful and complex gRPC pattern.

Bidirectional Definition

service ThreatMonitor {
  rpc MonitorThreats(stream ThreatQuery) returns (stream ThreatAlert);
}

Server Implementation

class ThreatMonitorServicer(threat_pb2_grpc.ThreatMonitorServicer):
    async def MonitorThreats(self, request_iterator, context):
        async def send_alerts():
            async for alert in alert_stream():
                if not context.is_active():
                    break
                yield alert
        
        async def handle_queries():
            async for query in request_iterator:
                print(f"Client watching: {query.severity_filter}")
                # Update alert filter based on client query
        
        # Run both concurrently
        await asyncio.gather(
            send_alerts(),
            handle_queries(),
        )

Client Implementation

async def run():
    channel = grpc.aio.insecure_channel("localhost:50051")
    stub = threat_pb2_grpc.ThreatMonitorStub(channel)
    
    async def send_queries():
        queries = [
            threat_pb2.ThreatQuery(severity_filter="CRITICAL"),
            threat_pb2.ThreatQuery(severity_filter="ALL"),
        ]
        for query in queries:
            yield query
            await asyncio.sleep(30)
    
    async def receive_alerts():
        async for alert in stub.MonitorThreats(send_queries()):
            print(f"ALERT: {alert.message} ({alert.severity})")
    
    await receive_alerts()

Common Mistakes

1. Assuming Sequential Message Order

In bidirectional streams, messages from both sides can interleave. Design your protocol to handle out-of-order messages.

2. Not Handling Concurrent Writes

Both sides can write simultaneously. Use proper synchronization (async locks) if shared state is involved.

3. Blocking the Event Loop

Bidirectional streams should use async/await. Blocking calls in sync handlers block the entire channel.

4. No Keepalive Pings

Idle bidirectional streams may be dropped by proxies. Configure keepalive pings to maintain the connection.

5. Ignoring context.is_active()

Always check if the context is still active before processing. The client may disconnect at any time.

Practice Questions

  1. What makes bidirectional streaming different from server streaming?
  2. How do you implement bidirectional streaming with async?
  3. When should you use bidirectional over other streaming types?
  4. How do you handle concurrent sends and receives?
  5. What is the keepalive configuration for bidirectional streams?

Answers:

  1. Both sides send multiple messages independently. Server streaming has one client request and many server responses.
  2. Use grpc.aio (async IO) in Python. Define async generators for both sending and receiving.
  3. For real-time chat, live collaborative editing, gaming, and any application where both sides need to exchange messages asynchronously.
  4. Use async generators and asyncio.gather to run reading and writing concurrently. Never block on writes while reading.
  5. Set grpc.keepalive_time_ms, grpc.keepalive_timeout_ms, and grpc.keepalive_permit_without_calls on both client and server.

Mini Project

Build a bidirectional streaming service for DodaTech's real-time threat monitoring. Clients connect, subscribe with severity filters, and receive alerts. Clients can change their filter mid-stream. Include keepalive and error handling.

What's Next

Topic Description
Channels Connection management
Interceptors Middleware for gRPC
⬅ Client Streaming
➡ gRPC Channels

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro