5 minute read

Some projects aren’t enough using a single programming language. When in comes to the project that uses multiple languages, communicating between different languages become a problem. There are (of course) many ways to implement IPC communication but Google’s protobuf seems to be a reasonable choice unleess I have a special purpose in mind.

Basic workflow (How does it work?)

Protobuf is a language-neutral serialization solution. Language-neutral means that you need to define messages first no matter which language you use. It looks like we are doing another work but it has a certain advantages. Be patient.

Basically, you define message .proto file.

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}

And turn this .proto file into your language API using protoc

Person john = Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .build();
output = new FileOutputStream(args[0]);
john.writeTo(output);

TA-DA. Serialized Person instance has been written to output stream! It looks simple enough. Let’s take a closer look how we can apply Protobuf into the project.

Install protobuf

Protobuf requires to be installed prior to use it. You can go to https://developers.google.com/protocol-buffers/docs/downloads to find an installation package. If you are planning to use C++, you will need to build from the source. Check C++ installation page in protobuf GitHub page.

It turns out I didn’t need to install protobuf separately. gRPC installs protobuf since it’s based on protobuf serialization.

Install gRPC

Wait, what is gRPC? My original plan was to serialize messages via protobuf and send the serialized data into socket communication which will work as IPC communication. But why reinvent wheels? gRPC was designed to call a method on a server from the client directly which its purpose exactly matches with what IPC does.

You can follow instructions on how to install gRPC in https://grpc.io/docs/languages/cpp/quickstart/

Write .proto for gRPC ready.

I have introduced simple Person message definition above. Let’s expand it to IPC communication ready. Here is .proto message definition.

syntax = "proto3";

package protobuf_example;

service WhoIsIt {
    rpc SendMe (PersonRequest) returns (Person) {}
}

message Person {
  string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}

message PersonRequest {
    string name = 1;
}

In this definition, I have defined WhoIsIt service which takes control of server and client side communication via RPC(Remote Procedure Call). SendMe method receieves PersonRequest message and responses with Person message. The structures of messages are defined as we have written in .proto file.

Generate .cc and .h file (And .py file)

Once we have written the .proto file, we are ready to write language-dependent files. We assume that our message definition .proto file is msg.proto.

protoc --cpp_out ./cxx --grpc_out ./cxx --plugin=protoc-gen-grpc="/home/user/.local/bin/grpc_cpp_plugin" msg.proto

You will need to change proto-gen-grpc path along with your installation path. Once you run this command, you will have 4 files. msg.grpc.pb.cc, msg.grpc.pb.h, msg.pb.cc, msg.pb.h As you already might have noticed that protoc generates msg.pb.cc and msg.pb.h which defines how protobuf generates message serialization. And the protoc-gen-grpc plugin generates msg.grpc.pb.cc and msg.grpc.pb.h which contains communication services.

What if I want to use python language for IPC between C++ and python? You will need to generate python file as well.

protoc --python_out ./python --grpc_out ./python --plugin=protoc-gen-grpc="/home/user/.local/bin/grpc_python_plugin" msg.proto

And you will see msg_pb2_grpc.py, msg_pb2.py files has been generated. Now we are ready to write our own IPC communication program!

Write main.cpp for IPC server

I want to write an application that receieves name of the person request and send reply with email information in C++ and send request message from python language. Let start with C++ first.

#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>

#include <iostream>
#include <memory>
#include <string>

#include "msg.grpc.pb.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;

using protobuf_example::Person;
using protobuf_example::PersonRequest;
using protobuf_example::WhoIsIt;

class PersonServiceImpl final : public WhoIsIt::Service {
  Status SendMe(ServerContext* context, const PersonRequest* request,
                Person* person) override {
    person->set_name(request->name());
    person->set_email("lim.jeikei@gmail.com");
    return Status::OK;
  }
};

int main(int argc, char** argv) {
  std::string server_address("0.0.0.0:50051");
  grpc::EnableDefaultHealthCheckService(true);
  grpc::reflection::InitProtoReflectionServerBuilderPlugin();

  PersonServiceImpl service;

  ServerBuilder builder;
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;

  server->Wait();

  return 0;
}

I have made a PersonServiceImpl class that inherits WhoIsIt::Service which was generated by protoc. Let take a look in python code now.

import grpc

from protobuf.python.msg_pb2 import Person
from protobuf.python.msg_pb2_grpc import WhoIsItStub

if __name__ == "__main__":
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = WhoIsItStub(channel)
        response = stub.SendMe(Person(name="Jongkuk Lim"))

    print(response)

You will find that python prints message like below. Once you run binary file built from C++ and run python script.

name: "Jongkuk Lim"
email: "lim.jeikei@gmail.com"

You can find demo repository in https://github.com/JeiKeiLim/protobuf-ipc-example

Comments