C#/.NET gRPC Service with Server Streaming

One of the main advantage of gRPC over REST API’s is that gRPC supports streaming methods in addition to the traditional request-response style. There are 3 kinds of streaming support in gRPC namely Client Streaming, Server Streaming and Duplex (bi-directional streaming). In this post we talk about Server Streaming and how to implement it in C#/.NET

What is Server Streaming

In Server streaming calls the client sends a request to the server and gets a stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages. With gRPC, message ordering is guaranteed within an individual RPC call.. Once the client has received all the messages from the server, the server sends a final message containing the status code, status message, and optionally, some metadata in the Trailer/Header.

A definition of a server streaming method is shown below. Note the use of the keyword stream in the response.

rpc GetNotifications(GetNotificationsRequest) returns (stream GetNotificationsResponse);

So, why would one use server streaming over a Unary call that sends back the *entire* list of messages in the response? It seems to be just unnecessarily complex. In a way that is true, server streaming (or streaming in general) introduces additional complexity for both client and server side processing. It however affords several benefits and server streaming should be considered for the following scenarios

  • When high throughput is required
  • When low latency is required
  • When the size of the entire response payload is large enough to cause performance issues

Implementation

To create a gRPC project follow the steps in Your First gRPC Project. By default, dotnet creates a greet.proto file which contains the service definition of the generated GreeterService. Let’s edit this file and replace its content with the following

syntax = "proto3";
import "google/protobuf/timestamp.proto";


option csharp_namespace = "ServerStreaming";

package greet;

// The greeting service definition.
service Greeter {
    rpc GetNotifications(GetNotificationsRequest) returns (stream GetNotificationsResponse);
}

// The request message containing the user's name.
message GetNotificationsRequest {
  string name = 1;
}

 

message GetNotificationsResponse{
  string name = 1;
  string message = 2;
  google.protobuf.Timestamp sent = 3;
}

Build the project/solution to ensure that everything is correct. Now, open up GreeterService.cs and add the implementation of the GetNotifications method. Note that the 2nd parameter of the method is an IServerStreamWriter<T>. This is what we will use to write to the response stream.

using Google.Protobuf.WellKnownTypes;

using Grpc.Core;

using ServerStreaming;

namespace ServerStreaming.Services;

public class GreeterService : Greeter.GreeterBase {
    private readonly ILogger<GreeterService> _logger;
    private readonly List<GetNotificationsResponse> _notifications;
    public GreeterService(ILogger<GreeterService> logger) {
        _logger = logger;
        
        //Sample data -  simulate DB
        this._notifications = new List<GetNotificationsResponse>();
        for (int i = 0; i < 100; i++) {
            this._notifications.Add(new GetNotificationsResponse() {
                Name = i % 2 == 0 ? "Joe" : "Mary",
                Message = $"Notification {i}",
                Sent = Timestamp.FromDateTimeOffset(DateTimeOffset.Now)
            });
        }
    }

    public override async Task GetNotifications(GetNotificationsRequest request, IServerStreamWriter<GetNotificationsResponse> responseStream, ServerCallContext context) {
        //simulate a db call
        var notifications = this._notifications.Where(m => m.Name == request.Name);

        foreach (var notif in notifications) {
            //write to the response stream
            await responseStream.WriteAsync(notif);
        }
    }
}

Hit F5 to run the project and take note of the port where it is running

Test the method

Now that the service is running, let’s fire up FintX (https://github.com/namigop/FintX) to verify that the service is working fine. FintX is an open-source, native, and cross-platform gRPC client. I’m on a mac, so I have installed the macOS package (Windows and Linux downloads are also available)

Click on the plus icon to add a client. The value entered in the http address should match the running service


I created 2 request files, one to hold the request for Joe and the other one for Mary. Note that our sample data holds notifications for either Joe or Mary. Open either *.fxrq file and click the Run button

And there it is! In the lower, right-hand side of the app (response stream panel ) the streamed 50 responses are displayed

Others

The full source code of this tutorial can be downloaded fromĀ https://github.com/namigop/grpctutorials/tree/main/ServerStreaming. Happy coding!

Leave a Reply