Documentation > Development > ProtoRPC RPC System

ProtoRPC is an RPC library built on top of Google Protocol Buffers. It permits programs written in different languages to exchange messages using RPC calls. The original version of this document can be found here [Source].

Brief Overview

Users write a .proto file that defines an RPC service. A service is composed of a set of methods, each of which takes an input message and returns an output message. This .proto file is then used to generate interface classes, in C++ or Java. See the original Protocol Buffer documentation for details on this stuff.

This interface is written in an event-driven, asynchronous style. This means that a single threaded program can have multiple pending RPCs at any time. RPCs complete by calling callbacks. On the client side, protorpc will call a callback when the server’s results have been returned. On the server side, results are returned to the client when a callback is invoked.

On the server side, an abstract class with a set of abstract methods is generated. To implement a service, this class is implemented, and an instance is registered (see the example below). When the RPC is invoked, the abstract methods will be called. See the protocol buffer service documentation for details about these methods.

On the client side, a class with the interface methods is generated. This class is connected to a remote service (see the example below). Then, the abstract methods are invoked to start the RPC. When the RPC completes, a callback will be invoked.

Note: It is possible to use this interface in a blocking fashion, if that is easier to understand. However, some code is currently not completed to make this work.

Non-Blocking Operations

The heart of non-blocking operations is the EventLoop interface. This interface runs the event loop. While inside the run() or runOnce() methods, implementations of this interface will wait for events to occur. When they occur, they will call the appropriate callback. An instance of this interface can be shared by any number of classes. Typically, a single instance is created for the entire program.

The key to making this work, is that when you need to wait for an event, you create a callback, and pass that to the appropriate API. When the event occurs, the EventLoop will call the callback.

The concrete implementation of this interface is typically NIOEventLoop.

Client Example

This example uses the ca.evanjones.protorpc.Counter example defined in the source repository. The Java implementation of both the client and server are included in ca.evanjones.protorpc.CounterExample.

  1. Create an NIOEventLoop for waiting for network I/O and delivering callbacks:
    NIOEventLoop eventLoop = new NIOEventLoop();
  2. Connect to the remote server by creating a ProtoRpcChannel. This channel communicates with a single server, but can be shared by multiple stubs.
    ProtoRpcChannel channel = new ProtoRpcChannel(eventLoop, new InetSocketAddress(host, port));
  3. Create a stub, which wraps the ProtoRpcChannel with the friendly generated interface.
    CounterService stub = CounterService.newStub(channel);
  4. Finally, call the RPC. For each pending RPC, there is an instance of ProtoRpcController, which is used to track the status and report errors. For client calls, you must create the object to be sent, as well as the callback to be invoked when the call is completed. Finally, you actually call the method on the stub.
    ProtoRpcController rpc = new ProtoRpcController();
    GetRequest request = GetRequest.newBuilder().setName("foo").build();
    StoreResultCallback<Value> callback = new StoreResultCallback<Value>();
    stub.get(rpc, request, callback);
  5. Wait for the response. In most programs, you want to process the response in the callback. This lets you continue and do other things (eg. call multiple RPCs on multiple servers). However, occasionally it is useful to wait for the RPC to complete. Note: This is somewhat dangerous and should only be used in simple, single threaded clients:
    rpc.block();
  6. Check for and handle errors. If there is an error, the callback is invoked with a null parameter, and rpc.failed() returns true.
    assert !rpc.failed();

Server Example

  1. Create the event loop and the ProtoServer, which listens for protorpc connections. It can be reused for multiple server stubs.
    NIOEventLoop eventLoop = new NIOEventLoop();
    ProtoServer server = new ProtoServer(eventLoop);
  2. Create a server stub that will handle the RPC server calls, then register it with the server.
    CounterExample counter = new CounterExample();
    server.register(counter);
  3. Bind to a specific port, and run the event loop. Optionally, you can force the event loop to exit on the SIGINT signal, which is sent when Ctrl-C is pressed.
    server.bind(port);
    eventLoop.setExitOnSigInt(true);
    eventLoop.run();