Introduction

gRPC is a modern open source high performance RPC framework that can run in any environment, initially developed by Google. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

The main usage scenarios of gRPC are:

gRPC passes data through protocol buffers or protobuf. They are defined by Google’s developer guide as:

“… a flexible, efficient, automated mechanism for serializing structured data — think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, and then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages. You can even update your data structure without breaking deployed programs that are compiled against the “old” format.”

So, protocol buffers are to gRPC what XML or JSON are to REST. They’re a fast, small, and flexible way of serializing data to be passed over the network.

grpc-overview
gRPC Overview

 

Just like REST, gRPC can be used cross-language which means that if we have written a web service in Golang, a Java written application can still use that web service, which makes gRPC web services very scalable. gRPC uses HTTP/2 to support highly performant and scalable API’s and makes use of binary data rather than just text which makes the communication more compact and more efficient. It is also type-safe. This basically means that we can’t give an apple while a banana is expected. When the server expects an integer, gRPC won’t allow us to send a string because these are two different types.

In this article, we will use following steps to create a typical client-server application using gRPC:

  1. Define a service in a .proto file
  2. Create batch file for compiling .proto file
  3. Generate server and client code using the protocol buffers compiler
  4. Create the server application, implementing the generated services interfaces and spawning the gRPC server
  5. Create the client application, making RPC calls using generated stubs

 

Prerequisites

Before starting, we will need to ensure that .NET Core is installed in machine. Along with it, we will also need to install dependencies libraries from nuget package manager.

  1. Grpc : It is a c# implementation of gRPC. We can install it through following command in package manager console. PM> Install-Package Grpc -Version 1.17.0
  2. Tools: This library is gRPC and protocol buffer compiler for managed C# and native C++ projects. We can install it through following command in package manager console. PM> Install-Package Grpc.Tools -Version 1.17.0
  3. Protobuf: It is a C# runtime library for Protocol Buffers – Google’s data interchange format. Following command is used to install this library in package manager console. PM> Install-Package Google.Protobuf -Version 3.6.1

 

Define a protobuf service

Before defining service, we should know that gRPC supports C# from proto3 only. Currently, proto2 is the default protocol buffers version. It supports C#, Dart, Go and Ruby from proto3.

Here we have created a proto file ‘javraservice.proto’

 

syntax="proto3";
package javra.grpc.test;

service JavraService{
    rpc Search(SearchRequest) returns (SearchResponse);
}

message SearchRequest{
    string query=1;
    int32 page_number=2;
    enum ServiceType{
        UNIVERSAL=0;
        WEB=1;
        LOCAL=2;
    }

    ServiceType service_type=3;
}

message SearchResponse{
    string result=1;
}

 

As mentioned earlier, we have defined the version of protocol buffers as proto3 through

syntax=proto3;

And we have defined the package as javra.grpc.test; This same package name will be used as namespace when converting protocol buffers file into C# generated code.

We have defined two messages, SearchRequest and SearchResponse. SearchRequest has three properties, query as string, page_number as integer and ServiceType which is an enum. Similarly, SearchResponse has one property, result which is string.

Then, we have defined a service: JavraService which contains a method Search(). This service has SearchRequest message in its parameter and will return SearchResponse as result. To specify service, we specify named service in .proto file. Then we define rpc methods inside our service definition, specifying request and response type.

In this article we have used simple RPC where the client sends a request to the server using the client object and waits for response to come back, just like a normal function call. It is also knows as Unary RPC. There are other RPC too.

A server-side streaming RPC is where a client sends request to server, and gets stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages. We specify a server-side streaming method by placing the stream keyword before the response type as following:

rpc Search(SearchRequest) returns (stream SearchResponse);

A client-side streaming RPC is where the client writes a sequence of messages and sends them to the server, again using a provided stream. Once the client has finished writing the messages, it waits for the server to read them all and return its response. We specify a client-side streaming method by placing the stream keyword before the request type.

rpc Search(stream SearchRequest) returns (SearchResponse);

A bidirectional streaming RPC is where both sides send a sequence of messages using a read-write stream. The two streams operate independently, so clients and servers can read and write in whatever order they like. We specify this type of method by placing the stream keyword before both the request and the response.

rpc Search(stream SearchRequest) returns (stream SearchResponse);

 

Creating batch file for compiling protocol buffers file

Following batch code is used to compile the provided ‘javraservice.proto’

setlocal

set PROTOC=%UserProfile%\.nuget\packages\Google.Protobuf.Tools\3.6.1\tools\windows_x64\protoc.exe

set PLUGIN=%UserProfile%\.nuget\packages\Grpc.Tools\1.17.0\tools\windows_x64\grpc_csharp_plugin.exe

REM Defining proto file location. There shouldn't be whitespace before path
set ProtoFilePath=protos\javraservice.proto

REM Project path for proto
set ProtoOutputPath=JavraGrpc\JavraGrpc\Protos

REM Services path
set ServiceOutputPath=JavraGrpc\JavraGrpc\Services

REM Create folder if path doesnot exist
MKDIR .\JavraGrpc\JavraGrpc\Protos
MKDIR .\JavraGrpc\JavraGrpc\Services

REM compile protofile
%PROTOC% -I.\ --csharp_out %ProtoOutputPath% %ProtoFilePath% --grpc_out %ServiceOutputPath% --plugin=protoc-gen-grpc=%PLUGIN%

endlocal

 

PROTOC variable refers to the location of protoc.exe, which is generated when we install Grpc.Tools library through nuget package manager. Similarly PLUGIN refers to the location of grpc_csharp_plugin.exe

NOTE:  Root directory for given batch code will be the path where the batch file is located.

ProtoFilePath refers to the path where .proto file is located from root directory. In case of multiple proto files in Windows OS, we will have to define path for each individual files. However in Linux, we can easily get all proto files by using wildcard (*). i.e.,

proto_directory_path/*.proto

ProtoOutputPath refers to the path where a C# generated code of messages to populate, serialize, and retrieve request and response of protocol buffers after compiling protocol buffers will be saved. Here JavraGrpc\JavraGrpc refers to SolutionName\ProjectName

ServiceOutputPath refers to the path where a C# generated code of service defined in protocol buffers file will be saved. It includes an abstract class JavraService.JavraServiceBase to inherit from when defining JavraService service implementation. It also includes JavraService.JavraServiceClient that can be used to access remote JavraService instances

 

%PROTOC% -I.\ --csharp_out %ProtoOutputPath% %ProtoFilePath% --grpc_out %ServiceOutputPath% --plugin=protoc-gen-grpc=%PLUGIN%

 

Above command will compile the protocol buffers file into C# generated code.

 

Project structure before compiling protocol buffers

 

 

 

 

 

 

 

 

 

 

 

 

Project structure after compiling protocol buffers file

 

 

Creating server application

In existing solution, we will add a new console project ‘JavraGrpc.Server’ where we will define a server for JavraGrpc service. We will need to add reference of ‘JavraGrpc’ project in this new project.

Firstly, we will add a new CS file ‘JavraSearch’ which will extend a partial class JavraService.JavraServiceBase (this class is auto generated by gRPC compiler and is located in JavraserviceGrpc.cs). In this class, we will override a Search method (this method is the service function we defined in JavraService.proto). Here, we can perform all the data manipulations.  For simplicity, we will convert the request object sent by client to JSON and then return it as result.

public class JavraSearch : JavraService.JavraServiceBase
  {
    public override Task<SearchResponse> Search(SearchRequest request, ServerCallContext context)
    {
      var result = JsonConvert.SerializeObject(request);
      return Task.FromResult(new SearchResponse
      {
        Result = result
      });
    }
  }

 

Then, we will define a server in Main method as below.

 

class Program
{
    static void Main(string[] args)
    {
        const int servicePort = 50081;
        var server = new Grpc.Core.Server
        {
            Services = { JavraService.BindService(new JavraSearch()) },
            Ports = { new ServerPort("localhost", servicePort, ServerCredentials.Insecure) }
        };

        server.Start();

        Console.WriteLine("Server started");

        Console.WriteLine("Press any key to exit");

        Console.ReadKey();
    }
}

When defining a server, we will need to bind a service which we just created earlier as ‘JavraSearch’ and specify a port. Then we will need to start the server and then server is ready to operate.

 

Creating a client application

Similarly, we will create a new project ‘JavraGrpc.Client’ to create a client application. In Main method, we will perform following steps to create a channel and client and then request to server.

 

static void Main(string[] args)
{
    Console.WriteLine("Program started");

    //define channel
    var channel = new Channel("127.0.0.1:50081", ChannelCredentials.Insecure);

    //define client. It is also known as stub in other languages like python
    var client = new JavraService.JavraServiceClient(channel);

    //create request object
    var searchRequest = new SearchRequest
    {
        Query = "This is a sample query..",
        PageNumber = 1,
        ServiceType = SearchRequest.Types.ServiceType.Web
    };

    //send service request
    var response = client.Search(searchRequest);

    Console.WriteLine($"Response= {response}");

    //shutdown channel
    channel.ShutdownAsync().Wait();

    Console.WriteLine("Press any key to exit");

    Console.ReadKey();
}

We create a Channel that connects to a specific host. Here, ‘127.0.0.1’ refers to the ‘localhost’ and ‘50081’ refers to the server port we defined in JavraGrpc.Server project.

Then we can define a Client or Stub for JavraService by passing channel we defined a step ago.

Once a client is created, we can create a request object and then send service request to the server ‘JavraGrpc.Server’.

Based on how we implemented Search service earlier on ‘JavraGrpc.Server’ project, the server will convert the request into JSON string and then respond it to the client. Then we will shut down a channel cleanly.

 

Making calls to server from client

We will need to run both ‘JavraGrpc.Server’ and ‘JavraGrpc.Client’ project in order to make calls to server from client. We can perform it by starting project in two visual studio windows, making each project as startup project in each window. Or we can open one of the projects in command prompt and start project with following command.

dotnet run –f netcoreapp2.1

Response retrieved from server being displayed in client application

We will get result in JSON format as shown in above figure as we have defined while creating server application.

Conclusion

In this article, we have managed to create a simple gRPC server in C# .NET Core and made RPC call from client based on protocol buffers definition.

References

Leave a Reply

Your email address will not be published. Required fields are marked *