Using gRPC with Model RealTime

May 28, 2024

gRPC is a Remote Procedure Call (RPC) framework for building systems of distributed applications in heterogeneous environments. By implementing a gRPC server in your realtime application you can access it from other applications. Those applications can run on different machines in a network and don't need to be implemented in C++, since gRPC supports a large number of programming languages.

A model library for gRPC support in Model RealTime is available on GitHub. The Git repo contains:

Clone the repository and follow the instructions in the README file to build gRPC itself, the model library, and the sample applications.

Using RPCs to Communicate with a Model RealTime gRPC Server

gRPC uses a protocol buffer language for defining services with RPC methods. It also defines the messages that are transmitted when a client invokes those RPCs on a server. Messages may contain data and are serialized by the gRPC framework to allow the client and server to run on different machines, and be implemented in different programming languages.

The following forms of communication with a gRPC server is supported by the Model RealTime gRPC library:

  1. Asynchronous requests Here the client makes a request to the server without waiting for a reply. The message that is sent with the request may or may not contain data. There is no reply message so no data can be sent back from the server to the client.

    Asynchronous requests are implemented using unary RPCs. Below are two examples from the sample application .proto file:

// Asynchronous request with no data
rpc GoWest (google.protobuf.Empty) returns (google.protobuf.Empty) {}

// Asynchronous request with data
rpc AdjustStepCount (AdjustStepCountRequest) returns (google.protobuf.Empty) {}

message AdjustStepCountRequest {
  int32 adjustment = 1;
}
  1. Synchronous requests Here the client makes a request to the server and waits for a reply. The message that is sent with the request may or may not contain data. The reply message usually contains some data, but could be empty. This is the closest approximation of making a traditional function call where the caller is suspended until the call returns from the callee.

    Synchronous requests are also implemented using unary RPCs. The only difference from asynchronous requests is that there is a reply message. Below is an example from the sample application .proto file:

// Synchronous request with no data (but with reply data)
rpc StepCount (google.protobuf.Empty) returns (StepCountReply) {}

message StepCountReply {
  int32 count = 1;
}
  1. Requests initiated by the server In realtime applications, not all communication is initiated by a client making a request to a server. The server may itself at any time send out an event and clients may want to get notified when that happens. This is supported by means of so called server streaming RPCs. The client can call these to subscribe to get notified when the server sends out a certain event. Here is an example from the sample application .proto file:
// Subscribe to get notified when the server sends an outgoing event (with data)
rpc Subscribe_WrongWay (google.protobuf.Empty) returns (stream WrongWay) {}

message WrongWay {
  string message = 1;
}

Contrary to other requests, a subscription request remains active for a long time, and as long as it's active the server can "stream" messages back to the subscribing client. This mechanism is used for notifying the client that the server has sent the specific event that was subscribed for. The client application typically needs to use a separate thread for receiving these notifications, to avoid blocking the main thread.

When a client no longer wants to receive such notifications, it can unsubscribe by calling a regular unary RPC. When the server receives it, it will finalize the ongoing subscription request and stop notifying the client. Here is an example from the sample application .proto file:

// Unsubscribe to no longer get notified when the server sends an outgoing event
rpc Unsubscribe_WrongWay (google.protobuf.Empty) returns (google.protobuf.Empty) {}

The picture below illustrates how the sample application implements the different forms of communication described above.

For more information about the support for gRPC in Model RealTime, watch this video.