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