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