Bidirectional streaming is a request/response model where both sides send a stream of messages independently. Unlike a single request followed by a single response, both client and server can read and write at any time. gRPC implements this on top of HTTP/2 streams, and once you understand the mechanics it is one of the most powerful tools for real-time systems.
Why bidirectional?
Chat, multiplayer games, real-time analytics dashboards, voice/video transport — anywhere both ends need to push events to the other without polling. The alternative (long-poll over HTTP/1.1) wastes connections and adds latency. Bidi over HTTP/2 multiplexes one TCP connection across many streams, so you avoid head-of-line blocking and keep the round-trip latency low.
The wire-level mechanics
HTTP/2 frames let the client and server send DATA frames in either direction on the same stream ID. gRPC uses the Length-Prefixed-Message format inside each DATA frame: 1-byte compressed flag, 4-byte big-endian length, then the protobuf payload. The stream stays open until either side sends END_STREAM.
Server (Python) sample
import grpc, chat_pb2, chat_pb2_grpc
from concurrent import futures
class ChatService(chat_pb2_grpc.ChatServicer):
def Chat(self, request_iterator, context):
for msg in request_iterator: # read inbound
yield chat_pb2.Echo(text=f'<<{msg.text}>>') # write outbound
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
chat_pb2_grpc.add_ChatServicer_to_server(ChatService(), server)
server.add_insecure_port('[::]:50051')
server.start(); server.wait_for_termination()Client sample
import grpc, chat_pb2, chat_pb2_grpc, threading
channel = grpc.insecure_channel('localhost:50051')
stub = chat_pb2_grpc.ChatStub(channel)
def gen():
for line in iter(input, ''):
yield chat_pb2.Msg(text=line)
for echo in stub.Chat(gen()): # async iter both ways
print('server:', echo.text)Backpressure + flow control
HTTP/2 flow control is per-stream and per-connection. If the consumer doesn't read, WINDOW_UPDATE frames stop arriving and the producer blocks. Tune initialWindowSize if you push large messages. On the application layer, gRPC adds a per-call deadline — set it; don't rely on TCP keepalive alone.
When NOT to use bidi
If 95% of your traffic is request/response, stick with unary calls — they are cheaper to reason about. Bidi shines when the message rate is independent on both sides, not just bidirectional in the request-response sense.