7.3.2. Protobuf: Math#

In C++ eCAL also supports the protobuf serialization format for the client/server API.

That means that for the request and response, an interface can be defined as a protobuf message in order to make it easier to handle requests.

7.3.2.1. Protobuf file#

We use the special protobuf service definitions in order to implement it in our server/client applications. The “message” format is already known by you from the publisher/subscriber examples. The “service” format and the rpc xyz (type) returns (type) is now added. You will see, which effect this addition has on our example.

 1syntax = "proto3";
 2
 3option cc_generic_services = true;
 4
 5///////////////////////////////////////////////////////
 6// Math Service
 7///////////////////////////////////////////////////////
 8message SFloatTuple
 9{
10  double inp1 = 1;
11  double inp2 = 2;
12}
13
14message SFloat
15{
16  double out  = 1;
17}
18
19service MathService
20{
21  rpc Add      (SFloatTuple) returns (SFloat);
22  rpc Multiply (SFloatTuple) returns (SFloat);
23  rpc Divide   (SFloatTuple) returns (SFloat);
24}
 Math Protobuf File
└─  math.proto

7.3.2.2. Math Server#

In the server we need to implement the service interface defined by the protobuf file. For that we derive from the generated class MathService and implement the methods.

For the data exchange the server reads the input messages from the protobuf message and writes directly to the output message.

  1#include <ecal/ecal.h>
  2#include <ecal/msg/protobuf/server.h>
  3
  4#include <iostream>
  5#include <chrono>
  6#include <thread>
  7#include <math.h>
  8#include <cfloat>
  9#include <cmath>
 10
 11#include "math.pb.h"
 12
 13/*
 14  Here we derive our service implementation from the generated protobuf service class.abort
 15  We override the methods Add, Multiplay and Divide which we defined in the protobuf file.
 16*/
 17class MathServiceImpl : public MathService
 18{
 19public:
 20  void Add(::google::protobuf::RpcController* /* controller_ */, const ::SFloatTuple* request_, ::SFloat* response_, ::google::protobuf::Closure* /* done_ */) override
 21  {
 22    std::cout << "Received request for MathService in C++: Add" << "\n";
 23    std::cout << "Input1 : " << request_->inp1() << "\n";
 24    std::cout << "Input2 : " << request_->inp2() << "\n";
 25    std::cout << "\n";
 26
 27    /*
 28      The request is a pointer to the protobuf message SFloatTuple.
 29      We can access the input values inp1 and inp2 using the generated getter methods.
 30      The response is a pointer to the protobuf message SFloat.
 31      We set the output value using the generated setter method set_out.
 32
 33      This is very convenient, because we can simply work with the protobuf messages without additional function calls.
 34    */
 35    response_->set_out(request_->inp1() + request_->inp2());
 36  }
 37
 38  void Multiply(::google::protobuf::RpcController* /* controller_ */, const ::SFloatTuple* request_, ::SFloat* response_, ::google::protobuf::Closure* /* done_ */) override
 39  {
 40    std::cout << "Received request for MathService in C++: Multiply" << "\n";
 41    std::cout << "Input1 : " << request_->inp1() << "\n";
 42    std::cout << "Input2 : " << request_->inp2() << "\n";
 43    std::cout << "\n";
 44
 45    response_->set_out(request_->inp1() * request_->inp2());
 46  }
 47
 48  void Divide(::google::protobuf::RpcController* /* controller_ */, const ::SFloatTuple* request_, ::SFloat* response_, ::google::protobuf::Closure* /* done_ */) override
 49  {
 50    std::cout << "Received request for MathService in C++: Divide" << "\n";
 51    std::cout << "Input1 : " << request_->inp1() << "\n";
 52    std::cout << "Input2 : " << request_->inp2() << "\n";
 53    std::cout << "\n";
 54
 55    if(std::fabs(request_->inp2()) > DBL_EPSILON) response_->set_out(request_->inp1() / request_->inp2());
 56    else                                          response_->set_out(0.0);
 57  }
 58};
 59
 60/*
 61  We define a callback function that will be called when the an event happens on the server, like connected or disconnected.
 62*/
 63void OnServerEvent(const eCAL::SServiceId& /*service_id_*/, const struct eCAL::SServerEventCallbackData& data_)
 64{
 65  switch (data_.type)
 66  {
 67  case eCAL::eServerEvent::connected:
 68    std::cout << "-----------------------------------" << std::endl;
 69    std::cout << "Server connected                   " << std::endl;
 70    std::cout << "-----------------------------------" << std::endl;
 71    break;
 72  case eCAL::eServerEvent::disconnected:
 73    std::cout << "-----------------------------------" << std::endl;
 74    std::cout << "Server disconnected                " << std::endl;
 75    std::cout << "-----------------------------------" << std::endl;
 76    break;
 77  default:
 78    std::cout << "Unknown server event." << std::endl;
 79    break;
 80  }
 81}
 82
 83int main()
 84{
 85  /*
 86    As always: initialize the eCAL API and give your process a name.
 87  */
 88  eCAL::Initialize("math server");
 89
 90  /*
 91    Here we need to create a shared pointer to our service implementation class.
 92  */
 93  std::shared_ptr<MathService> math_service = std::make_shared<MathServiceImpl>();
 94  
 95  /*
 96    Now we create the math server and give as parameter the service shared pointer.
 97    Additionally we want to listen to server events, so we pass the callback function OnServerEvent.
 98  */
 99  eCAL::protobuf::CServiceServer<MathService> math_server(math_service, OnServerEvent);
100
101  while(eCAL::Ok())
102  {
103    std::this_thread::sleep_for(std::chrono::milliseconds(500));
104  }
105
106  /*
107    As always, when we are done, finalize the eCAL API.
108  */
109  eCAL::Finalize();
110
111  return(0);
112}
 C++
   └─  math_server.cpp

7.3.2.3. Math Client#

The client handling is similiar to the binary example. The main difference is that our request is now a protobuf message which will be the input for the server. In the response we can read out the results and print them to the console.

  1#include <ecal/ecal.h>
  2#include <ecal/msg/protobuf/client.h>
  3
  4#include <iostream>
  5#include <chrono>
  6#include <thread>
  7
  8#include "math.pb.h"
  9
 10/*
 11  We define a callback function that will be called when the an event happens on the client, like connected or disconnected.
 12*/
 13void OnClientState(const eCAL::SServiceId& service_id_, const eCAL::SClientEventCallbackData& data_)
 14{
 15  switch (data_.type)
 16  {
 17  case eCAL::eClientEvent::connected:
 18    std::cout << "---------------------------------" << std::endl;
 19    std::cout << "Client connected to service      " << std::endl;
 20    std::cout << "---------------------------------" << std::endl;
 21    break;
 22  case eCAL::eClientEvent::disconnected:
 23    std::cout << "---------------------------------" << std::endl;
 24    std::cout << "Client disconnected from service " << std::endl;
 25    std::cout << "---------------------------------" << std::endl;
 26    break;
 27  default:
 28    std::cout << "Unknown client event." << std::endl;
 29    break;
 30  }
 31
 32  std::cout << "Server Hostname      : " << service_id_.service_id.host_name << std::endl;
 33  std::cout << "Server Name          : " << service_id_.service_name << std::endl;
 34  std::cout << "Server PID           : " << service_id_.service_id.process_id << std::endl;
 35  std::cout << "---------------------------------" << std::endl << std::endl;
 36}
 37
 38void printResponseInformation(const eCAL::protobuf::SMsgServiceResponse<SFloat>& service_response_)
 39{
 40  const std::string& method_name = service_response_.service_method_information.method_name;
 41  const std::string& host_name   = service_response_.server_id.service_id.host_name;
 42  const int32_t&     process_id  = service_response_.server_id.service_id.process_id;
 43  std::string        state;
 44
 45  switch(service_response_.call_state)
 46  {
 47    case eCAL::eCallState::executed:
 48      state = "EXECUTED";
 49      break;
 50    case eCAL::eCallState::timeouted:
 51      state = "TIMEOUTED";
 52      break;
 53    case eCAL::eCallState::failed:
 54      state = "FAILED";
 55      break;
 56    default:
 57      state = "UNKNOWN";
 58      break;
 59  }
 60  std::cout << "State    :" << state << "\n";
 61  std::cout << "Method   :" << method_name << "\n";
 62  std::cout << "Response :" << service_response_.response.out() << "\n";
 63  std::cout << "Host     :" << host_name << "\n";
 64  std::cout << "PID      :" << process_id << "\n";
 65  std::cout << "\n";
 66}
 67
 68/*
 69  Callback for math service responses (for the callback variants).
 70*/
 71void OnMathCallbackResponse(const eCAL::protobuf::SMsgServiceResponse<SFloat>& service_response_)
 72{
 73  std::cout << "Callback: Received response MathService: " << "\n";
 74  printResponseInformation(service_response_);
 75}
 76
 77/*
 78  Function for math service responses (for the blocking variants).
 79*/
 80void onMathBlockingResponse(const eCAL::protobuf::SMsgServiceResponse<SFloat>& service_response_)
 81{
 82  std::cout << "Blocking: Received response MathService: " << "\n";
 83  printResponseInformation(service_response_);
 84}
 85
 86int main()
 87{
 88  /*
 89    As always: initialize the eCAL API and give your process a name.
 90  */
 91  eCAL::Initialize("math client");
 92
 93  /*
 94    Create a math service client using the protobuf service type MathService.
 95  */
 96  const eCAL::protobuf::CServiceClient<MathService> math_client(OnClientState);
 97
 98  /*
 99    Wait until the client is connected to at least one service.
100  */
101  while (eCAL::Ok() && !math_client.IsConnected())
102  {
103    std::this_thread::sleep_for(std::chrono::milliseconds(500));
104    std::cout << "Waiting for the service .." << std::endl;
105  }
106
107  /*
108    Now we prepare the request message based on protobuf defined message SFloatTuple.
109  */
110  SFloatTuple math_request;
111  int counter = 0;
112
113  /*
114    Create a vector of method names that we want to call later alternatively.
115  */
116  std::vector<std::string> method_names = { "Add", "Multiply", "Divide" };
117
118  while (eCAL::Ok())
119  {
120    if (math_client.IsConnected())
121    {
122      math_request.set_inp1(counter);
123      math_request.set_inp2(counter + 1);
124
125      /*
126        As in the binary examples, we now iterate over the client instances and call the service methods.
127        If you either way want to call all instances, you can use the following high level methods:
128
129        math_client.CallWithResponse<SFloatTuple, SFloat>("Divide", math_request, 1000);
130        math_client.CallWithCallback<SFloatTuple, SFloat>("Divide", math_request, OnMathCallbackResponse, 1000);
131
132        But using the iterative approach you have more control over the instances you want to call.
133      */
134      auto instances = math_client.GetClientInstances();
135      for (auto& instance : instances)
136      {
137        /*
138          Service call: Callback
139        */
140        if (!instance.CallWithCallback<SFloatTuple, SFloat>(method_names[counter % 3], math_request, OnMathCallbackResponse))
141        {
142          std::cout << "MathService::Divide call on an instance failed." << std::endl;
143        }
144
145        /*
146          Service call: Blocking
147        */
148        auto divide_response = instance.CallWithResponse<SFloatTuple, SFloat>(method_names[counter % 3], math_request);
149        if (divide_response.first)
150        {
151          onMathBlockingResponse(divide_response.second);
152        }
153        else
154        {
155          std::cout << "Blocking: MathService::Divide call failed: " << divide_response.second.error_msg << std::endl;
156        }
157      }
158    }
159    else
160    {
161      std::cout << "Waiting for the service .." << std::endl;
162    }
163
164    counter++;
165    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
166  }
167
168  /*
169    Cleanup: finalize the eCAL API.
170  */
171  eCAL::Finalize();
172
173  return 0;
174}
 C++
   └─  math_client.cpp