Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
29 Jun 2025 · 6 min read ·Article 21 / 110
Go

21 Understanding the Concept of Streaming in gRPC

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

If you’re already familiar with REST APIs, the request-response communication model probably feels very natural. However, as applications grow more complex and the need for efficient two-way communication arises—such as real-time chat, system monitoring, or transferring large files—REST starts to show its limitations. This is exactly where the power of gRPC, and its streaming feature in particular, truly stands out.

In this article, we’ll dissect the concept of streaming in gRPC. We’ll understand how it works, explore the different types of streaming gRPC offers, and put it into easy-to-digest Go code examples. And of course, we’ll also include simulations, flow diagrams, and tables to make the concept stick firmly in your mind.


What Is Streaming in gRPC?

Put simply, streaming in gRPC is a mechanism in which the client and/or server can send a series of messages rather than just a single request/response. In other words, data can be ‘sent incrementally’ between the client and server, either asynchronously or synchronously, without having to wait for all of the data to be ready on the sender’s side.

Why Do We Need Streaming?

A few of the use cases that benefit most from streaming:

  • Large file transfers: Splitting a large file into chunks that are sent incrementally.
  • Real-time chat: Messages can flow continuously in both directions.
  • Live data feeds: Streaming server monitors, transaction dashboards, and so on.

Types of Streaming in gRPC

There are four kinds of RPC methods in gRPC:

TypeClient StreamServer StreamExample Scenario
UnaryRequest-Response
Server StreamingServer monitoring
Client StreamingFile chunk upload
BidirectionalLive chat

Let’s take a look at the mermaid diagram below to visualize it:

MERMAID
flowchart LR
    A[Unary] --> B1[Client Request] & B2[Server Response]
    C[Server Streaming] --> D1[Client Request] --> D2[Server Stream Data]
    E[Client Streaming] --> F1[Client Stream Data] --> F2[Server Response]
    G[Bidirectional] --> H1[Client Stream] <--> H2[Server Stream]

1. Unary RPC (Request-Response)

The simplest form: one request is answered with one response. There is no streaming. It typically looks like this in Protobuf:

proto
1service FileService {
2    rpc GetFileMetadata(FileRequest) returns (FileMetadata) {}
3}

2. Server Streaming RPC

The client sends a single request, and the server responds with a series of streamed data. Example use case: a dashboard that wants to display server status updates periodically.

proto
1service MonitorService {
2    rpc MonitorStatus(Empty) returns (stream Status) {}
3}

How it works:

  1. The client sends a request.
  2. The server sends data/responses multiple times until it’s done.
  3. The client reads the data stream until it’s exhausted.

Go Code Example (Server Streaming)

Protobuf definition:

proto
 1syntax = "proto3";
 2
 3service MonitorService {
 4    rpc MonitorStatus(Empty) returns (stream Status) {}
 5}
 6
 7message Status {
 8    string server = 1;
 9    string state = 2;
10    int32 cpuUsage = 3;
11}
12
13message Empty {}

Go server:

go
 1func (s *server) MonitorStatus(req *Empty, stream MonitorService_MonitorStatusServer) error {
 2    for i := 0; i < 5; i++ {
 3        status := &Status{
 4            Server: "MyApp",
 5            State: "UP",
 6            CpuUsage: rand.Int31n(100),
 7        }
 8        if err := stream.Send(status); err != nil {
 9            return err
10        }
11        time.Sleep(2 * time.Second)
12    }
13    return nil
14}

Go client:

go
1stream, _ := client.MonitorStatus(context.Background(), &Empty{})
2for {
3    status, err := stream.Recv()
4    if err == io.EOF {
5        break
6    }
7    fmt.Printf("Status: %+v\n", status)
8}


3. Client Streaming RPC

The client can send many messages to the server, and the server replies with a single response after all of the messages have been received.

Use case: Sending the pieces (chunks) of a file to be uploaded.

Protobuf:

proto
 1service FileService {
 2    rpc Upload(stream FileChunk) returns (UploadStatus) {}
 3}
 4
 5message FileChunk {
 6    bytes content = 1;
 7}
 8
 9message UploadStatus {
10    string message = 1;
11}

Go server:

go
 1func (s *server) Upload(stream FileService_UploadServer) error {
 2    var totalBytes int
 3    for {
 4        chunk, err := stream.Recv()
 5        if err == io.EOF {
 6            return stream.SendAndClose(&UploadStatus{Message: fmt.Sprintf("Received %d bytes", totalBytes)})
 7        }
 8        totalBytes += len(chunk.Content)
 9    }
10}

Go client:

go
1stream, _ := client.Upload(context.Background())
2for i := 0; i < 10; i++ {
3    stream.Send(&FileChunk{Content: []byte("part")})
4}
5resp, _ := stream.CloseAndRecv()
6fmt.Println(resp.Message)


4. Bidirectional Streaming

Both the client and the server can send streamed data to each other at any time and in any amount.

Use case: Real-time chat, collaborative editing, and so on.

Protobuf:

proto
1service ChatService {
2    rpc Chat(stream ChatMessage) returns (stream ChatMessage) {}
3}
4
5message ChatMessage {
6    string user = 1;
7    string text = 2;
8    int64 timestamp = 3;
9}

Go server:

go
 1func (s *server) Chat(stream ChatService_ChatServer) error {
 2    for {
 3        msg, err := stream.Recv()
 4        if err == io.EOF {
 5            return nil
 6        }
 7        // broadcast to other users (simplified)
 8        stream.Send(&ChatMessage{
 9            User: "Server",
10            Text: "Received: " + msg.Text,
11            Timestamp: time.Now().Unix(),
12        })
13    }
14}

Go client:

go
 1ctx := context.Background()
 2stream, _ := client.Chat(ctx)
 3go func() {
 4    for _, text := range []string{"Hi", "Apa kabar?"} {
 5        stream.Send(&ChatMessage{User: "Alice", Text: text, Timestamp: time.Now().Unix()})
 6        time.Sleep(time.Second)
 7    }
 8    stream.CloseSend()
 9}()
10for {
11    msg, err := stream.Recv()
12    if err == io.EOF {
13        break
14    }
15    fmt.Printf("Received: %s\n", msg.Text)
16}


Time & Efficiency Simulation

Let’s compare uploading a 100MB file using a unary RPC versus client streaming:

MethodChunk SizeTotal RequestsLatency per ReqEst. Total Time
Unary (100MB at once)-15s5s
Streaming (10MB)10MB100.5s5s
Streaming pipeline10MB10parallel (0.5s)~0.5s

With streaming, chunks can be sent incrementally and in a pipeline, so as long as there’s enough bandwidth, it’s faster and uses resources more efficiently.


Conclusion

gRPC Streaming opens up far more flexible, efficient, and real-time network communication compared to conventional (unary) RPC. Here are the key takeaways:

  • Use server streaming for broadcasting/monitoring.
  • Choose client streaming to upload large amounts of data incrementally.
  • Leverage bidirectional streaming for real-time two-way use cases.
  • Streaming is more efficient than unary for large or repetitive data.

This technology will only grow more crucial as applications shift toward being interactive, event-driven, and data intensive.

Hopefully the explanations, code, and diagrams above leave you feeling much more confident about streaming in gRPC. Ready to implement it in your production systems? 🚀

Related Articles

💬 Comments