gRPC unary RPCs are the most common communication pattern, but they’re not just a simple request-response; they’re a tightly coupled, bidirectional stream where both client and server are always sending messages until the connection is closed.

Let’s see it in action. Imagine a simple Greeter service with a SayHello RPC.

syntax = "proto3";

package greet;

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

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Here’s a Python client calling SayHello:

import grpc
import greet_pb2
import greet_pb2_grpc

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

if __name__ == '__main__':
    run()

And a Go server implementing it:

package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "path/to/your/greet" // Replace with your actual proto package
)

type server struct {
	pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

When the client calls stub.SayHello(), it doesn’t just send a single request and wait. Under the hood, it initiates a stream. The client writes its HelloRequest message onto this stream, and the server, upon receiving it, writes its HelloReply message back onto the same stream. Once the server has sent its response, it signals the end of its stream, which in turn tells the client that the RPC is complete and it can process the received HelloReply. The connection is then typically closed. This bidirectional stream is the foundation, even for a seemingly simple one-off request.

The core problem gRPC unary RPCs solve is providing a standardized, efficient, and language-agnostic way for services to communicate. Instead of dealing with raw TCP sockets, HTTP/2 framing, and protocol buffer serialization manually, gRPC abstracts all of that. It uses HTTP/2 for transport, Protocol Buffers for efficient serialization, and provides generated client/server stubs in numerous languages, allowing developers to focus on business logic rather than the plumbing.

The key levers you control are defined in your .proto files: the service definitions, RPC method signatures, and message structures. These .proto files are compiled into language-specific code that handles the serialization, deserialization, and network communication. On the server side, you implement the methods defined in the service interface. On the client side, you call the generated stub methods. The context.Context in Go and Python is crucial for managing request-scoped values, cancellations, and deadlines.

What most people miss is that even though a unary RPC appears to be a single request and a single response, the underlying mechanism is a full-duplex stream. The client sends the request on a stream, and the server sends the response on the same stream. The RPC completes when the server finishes writing its response and closes its send side of the stream. This stream-based nature is what allows gRPC to efficiently handle things like backpressure and allows for future extensions like streaming RPCs without fundamentally changing the transport.

The next concept to explore is how to handle multiple requests on the same connection without re-establishing the underlying HTTP/2 session.

Want structured learning?

Take the full Grpc course →