7.2.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.2.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.

 Math Protobuf File
└─  math.proto
 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}

7.2.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}

7.2.2.3. Math Server Files#

 C++
   └─  math_server.cpp

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

7.2.2.5. Math Client Files#

 C++
   └─  math_client.cpp