$darkmode
tcp_transceiver.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2020 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
22 #pragma once
23 #ifndef CLOE_UTILITY_TCP_TRANSCEIVER_HPP_
24 #define CLOE_UTILITY_TCP_TRANSCEIVER_HPP_
25 
26 #include <chrono> // for duration<>
27 #include <memory> // for unique_ptr<>
28 #include <string> // for string, to_string
29 #include <thread> // for this_thread, sleep_for
30 #include <utility> // for move
31 
32 #include <boost/asio.hpp> // for iostream
33 
34 #include <cloe/core.hpp> // for Error, Logger
35 #include <cloe/core/abort.hpp> // for AbortFlag, abort_checkpoint
36 #include <cloe/utility/tcp_transceiver_config.hpp> // for TcpTransceiverConfiguration
37 
38 namespace cloe {
39 namespace utility {
40 
50 class TcpReadError : public Error {
51  public:
52  using Error::Error;
53  virtual ~TcpReadError() noexcept = default;
54 };
55 
62  public:
63  TcpTransceiver() = default;
64  TcpTransceiver(const std::string& host, uint16_t port) { tcp_connect(host, port); }
65  virtual ~TcpTransceiver() { TcpTransceiver::tcp_disconnect(); } // NOLINT
66 
71  void tcp_connect(const std::string& host, uint16_t port) {
72  tcp_stream_.clear();
73  tcp_stream_.connect(host, std::to_string(port));
74  if (!tcp_stream_) {
75  throw std::ios_base::failure(tcp_stream_.error().message());
76  }
77  tcp_connected_ = true;
78  tcp_host_ = host;
79  tcp_port_ = port;
80  }
81 
87  bool tcp_is_connected() const { return tcp_connected_; }
88 
94  bool tcp_is_ok() const { return static_cast<bool>(tcp_stream_); }
95 
101  void tcp_disconnect() {
102  tcp_stream_.close();
103  tcp_connected_ = false;
104  }
105 
106  uint16_t tcp_port() const { return tcp_port_; }
107  const std::string& tcp_host() const { return tcp_host_; }
108  std::string tcp_endpoint() const { return fmt::format("tcp://{}:{}", tcp_host_, tcp_port_); }
109 
110  protected:
114  std::streamsize tcp_available_data() const {
115  return tcp_stream_.rdbuf()->in_avail() + tcp_stream_.rdbuf()->available();
116  }
117 
118  template <typename M>
119  void tcp_send(M* msg, size_t sz) {
120  tcp_stream_.write(reinterpret_cast<const char*>(msg), sz);
121  tcp_stream_.flush();
122  }
123 
124  protected:
125  boost::asio::ip::tcp::iostream tcp_stream_;
126  bool tcp_connected_{false};
127  std::string tcp_host_{};
128  uint16_t tcp_port_{};
129 };
130 
150 template <typename T>
152  public:
153  TcpTransceiverFactory() = default;
154  TcpTransceiverFactory(int attempts, std::chrono::duration<float> delay) {
155  config_.retry_attempts = attempts;
156  config_.retry_delay = delay;
157  }
158  explicit TcpTransceiverFactory(const TcpTransceiverConfiguration& c) : config_(c) {}
159  explicit TcpTransceiverFactory(TcpTransceiverConfiguration&& c) : config_(std::move(c)) {}
160  virtual ~TcpTransceiverFactory() = default;
161 
162  int retry_attempts() const { return config_.retry_attempts; }
163  void set_retry_attempts(int attempts) { config_.retry_attempts = attempts; }
164 
165  std::chrono::duration<float> retry_delay() const { return config_.retry_delay; }
166  void set_retry_delay(std::chrono::duration<float> delay) { config_.retry_delay = delay; }
167 
171  std::unique_ptr<T> create_or_null(const std::string& host, uint16_t port) const {
172  try {
173  return this->create_or_throw(host, port);
174  } catch (std::ios_base::failure&) {
175  return std::unique_ptr<T>{nullptr};
176  }
177  }
178 
182  std::unique_ptr<T> create_or_throw(const std::string& host, uint16_t port) const {
183  // On a 32-bit machine, this will overflow in 48 days.
184  for (int32_t attempts = 0; true; attempts++) {
185  try {
186  if (attempts == 0) {
187  factory_logger()->info("{} connect tcp://{}:{}", instance_name(), host, port);
188  } else {
189  factory_logger()->info("{} connect tcp://{}:{} [attempt {}/{}]", instance_name(), host,
190  port, attempts + 1, config_.retry_attempts + 1);
191  }
192  return std::make_unique<T>(host, port);
193  } catch (std::ios_base::failure&) {
194  if (attempts == config_.retry_attempts) {
195  throw;
196  }
197  }
198  std::this_thread::sleep_for(config_.retry_delay);
199  }
200  }
201 
207  std::unique_ptr<T> create_or_throw(const std::string& host, uint16_t port, AbortFlag& sig) const {
208  // On a 32-bit machine, this will overflow in 48 days.
209  for (int32_t attempts = 0; true; attempts++) {
210  try {
211  if (attempts == 0) {
212  factory_logger()->info("{} connect tcp://{}:{}", instance_name(), host, port);
213  } else {
214  factory_logger()->info("{} connect tcp://{}:{} [attempt {}/{}]", instance_name(), host,
215  port, attempts + 1, config_.retry_attempts + 1);
216  }
217  return std::make_unique<T>(host, port);
218  } catch (std::ios_base::failure&) {
219  if (attempts == config_.retry_attempts) {
220  throw;
221  }
222  }
223  abort_checkpoint(sig);
224  std::this_thread::sleep_for(config_.retry_delay);
225  }
226  }
227 
228  friend void to_json(Json& j, const TcpTransceiverFactory<T>& f) { to_json(j, f.config_); }
229  friend void from_json(const Json& j, TcpTransceiverFactory<T>& f) { from_json(j, f.config_); }
230 
231  protected:
232  virtual Logger factory_logger() const = 0;
233  virtual const char* instance_name() const = 0;
234 
235  protected:
237 };
238 
244 template <typename F>
246  -> decltype(F{c}.create_or_throw(c.host, c.port)) {
247  return F{c}.create_or_throw(c.host, c.port);
248 }
249 
255 template <typename F>
257  -> decltype(F{c}.create_or_throw(c.host, c.port, sig)) {
258  return F{c}.create_or_throw(c.host, c.port, sig);
259 }
260 
266 template <typename F>
268  -> decltype(F{c}.create_or_null(c.host, c.port)) {
269  return F{c}.create_or_null(c.host, c.port);
270 }
271 
272 } // namespace utility
273 } // namespace cloe
274 
275 #endif // CLOE_UTILITY_TCP_TRANSCEIVER_HPP_
void abort_checkpoint(AbortFlag &sig)
Definition: abort.hpp:60
auto create_or_null_with(const TcpTransceiverFullConfiguration &c) -> decltype(F
Definition: tcp_transceiver.hpp:267
void tcp_disconnect()
Definition: tcp_transceiver.hpp:101
std::atomic_bool AbortFlag
Definition: abort.hpp:42
Definition: error.hpp:35
Definition: coordinator.hpp:36
Definition: tcp_transceiver_config.hpp:84
bool tcp_is_connected() const
Definition: tcp_transceiver.hpp:87
Definition: tcp_transceiver_config.hpp:40
auto create_or_throw_with(const TcpTransceiverFullConfiguration &c) -> decltype(F
Definition: tcp_transceiver.hpp:245
Definition: tcp_transceiver.hpp:50
std::streamsize tcp_available_data() const
Definition: tcp_transceiver.hpp:114
std::unique_ptr< T > create_or_throw(const std::string &host, uint16_t port) const
Definition: tcp_transceiver.hpp:182
Definition: tcp_transceiver.hpp:61
Definition: tcp_transceiver.hpp:151
std::unique_ptr< T > create_or_throw(const std::string &host, uint16_t port, AbortFlag &sig) const
Definition: tcp_transceiver.hpp:207
void tcp_connect(const std::string &host, uint16_t port)
Definition: tcp_transceiver.hpp:71
std::unique_ptr< T > create_or_null(const std::string &host, uint16_t port) const
Definition: tcp_transceiver.hpp:171
bool tcp_is_ok() const
Definition: tcp_transceiver.hpp:94