Python’s grpcio library allows you to build high-performance, bidirectional streaming RPC services.

Let’s see it in action. We’ll define a simple "Greeter" service.

1. Define the Service (protobuf)

First, we need a Protocol Buffers (.proto) file to define our service and message structures. This is language-agnostic and forms the contract.

// greeter.proto
syntax = "proto3";

package greeter;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

2. Generate Python Code

We use the grpcio-tools package to compile this .proto file into Python code.

pip install grpcio grpcio-tools
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. greeter.proto

This generates two files: greeter_pb2.py (message definitions) and greeter_pb2_grpc.py (service stubs and server base classes).

3. Implement the Server

Now, we implement the actual logic for our Greeter service.

# server.py
import grpc
import time
from concurrent import futures

import greeter_pb2
import greeter_pb2_grpc

class GreeterServicer(greeter_pb2_grpc.GreeterServicer):
    """Provides methods that implement functionality of chat server."""

    def SayHello(self, request, context):
        return greeter_pb2.HelloReply(message='Hello, %s!' % request.name)

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    greeter_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    print("Server started on port 50051")
    try:
        while True:
            time.sleep(86400)
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()
  • GreeterServicer inherits from the generated greeter_pb2_grpc.GreeterServicer.
  • We implement the SayHello method, which matches the RPC defined in the .proto file. It takes a HelloRequest and returns a HelloReply.
  • grpc.server creates a gRPC server instance.
  • greeter_pb2_grpc.add_GreeterServicer_to_server registers our GreeterServicer with the gRPC server.
  • server.add_insecure_port('[::]:50051') binds the server to a network address. [::] means it listens on all available IPv4 and IPv6 interfaces. 50051 is a common port for gRPC examples.
  • server.start() begins listening for incoming requests.

4. Implement the Client

The client connects to the server and calls the RPC methods.

# client.py
import grpc
import greeter_pb2
import greeter_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = greeter_pb2_grpc.GreeterStub(channel)
        try:
            response = stub.SayHello(greeter_pb2.HelloRequest(name='World'))
            print("Greeter client received: " + response.message)
        except grpc.RpcError as e:
            print(f"Error calling SayHello: {e.code()} - {e.details()}")

if __name__ == '__main__':
    run()
  • grpc.insecure_channel('localhost:50051') creates a connection to the server. For production, you’d use grpc.secure_channel with TLS credentials.
  • greeter_pb2_grpc.GreeterStub(channel) creates a "stub" object. This stub is a proxy that allows you to call the server’s RPC methods as if they were local functions.
  • stub.SayHello(greeter_pb2.HelloRequest(name='World')) makes the actual RPC call. The arguments and return types are automatically serialized/deserialized using Protocol Buffers.

Running It:

  1. Save the .proto file as greeter.proto.
  2. Run the protoc command.
  3. Save the server code as server.py and run python server.py.
  4. Save the client code as client.py and run python client.py in a separate terminal.

You’ll see "Server started on port 50051" in the first terminal, and "Greeter client received: Hello, World!" in the second.

The core concept is the contract-first design enforced by Protocol Buffers. This ensures that both the client and server agree on the data structures and service methods, enabling efficient communication and code generation across different languages.

When you define an RPC like SayHello (HelloRequest) returns (HelloReply), grpcio-tools generates methods on the client stub (stub.SayHello) and a corresponding method signature on the server base class (GreeterServicer.SayHello). The library handles the underlying network communication, serialization (to bytes via Protocol Buffers), and deserialization.

The fact that gRPC uses HTTP/2 as its transport protocol is key. This allows for multiplexing multiple RPC calls over a single TCP connection, header compression, and server-side push, leading to much more efficient network utilization than traditional REST APIs over HTTP/1.1.

One thing most people don’t realize is how granularly you can control the gRPC channel’s behavior. For instance, when you create a channel, you can specify options like grpc.enable_retries(1) to enable client-side retries for idempotent methods, or grpc.keepalive_time_ms and grpc.keepalive_timeout_ms to tune how often the client pings the server to keep the connection alive and detect failures faster.

The next step is to explore bidirectional streaming RPCs, which allow for a continuous flow of messages in both directions over a single RPC call.

Want structured learning?

Take the full Grpc course →