/src/libs/protobuf_comm/server.cpp

https://gitlab.com/F34140r/rockin-refbox · C++ · 505 lines · 308 code · 60 blank · 137 comment · 37 complexity · b7e51887bf0cf580257d0ed61300f305 MD5 · raw file

  1. /***************************************************************************
  2. * server.cpp - Protobuf stream protocol - server
  3. *
  4. * Created: Thu Jan 31 14:57:16 2013
  5. * Copyright 2013 Tim Niemueller [www.niemueller.de]
  6. ****************************************************************************/
  7. /* Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * - Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * - Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in
  15. * the documentation and/or other materials provided with the
  16. * distribution.
  17. * - Neither the name of the authors nor the names of its contributors
  18. * may be used to endorse or promote products derived from this
  19. * software without specific prior written permission.
  20. *
  21. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  24. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  25. * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  26. * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  27. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  28. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  29. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  30. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  31. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  32. * OF THE POSSIBILITY OF SUCH DAMAGE.
  33. */
  34. #include <protobuf_comm/server.h>
  35. #include <cstdlib>
  36. using namespace boost::asio;
  37. using namespace boost::system;
  38. namespace protobuf_comm {
  39. #if 0 /* just to make Emacs auto-indent happy */
  40. }
  41. #endif
  42. /** @class ProtobufStreamServer::Session <protobuf_comm/server.h>
  43. * Internal class representing a client session.
  44. * This class represents a connection to a particular client. It handles
  45. * connection management, reading from, and writing to the client.
  46. * @author Tim Niemueller
  47. */
  48. /** Constructor.
  49. * @param id ID of the client, used to address messages from within your
  50. * application.
  51. * @param parent Parent stream server notified about events.
  52. * @param io_service ASIO I/O service to use for communication
  53. */
  54. ProtobufStreamServer::Session::Session(ClientID id, ProtobufStreamServer *parent,
  55. boost::asio::io_service& io_service)
  56. : id_(id), parent_(parent), socket_(io_service)
  57. {
  58. in_data_size_ = 1024;
  59. in_data_ = malloc(in_data_size_);
  60. outbound_active_ = false;
  61. }
  62. /** Destructor. */
  63. ProtobufStreamServer::Session::~Session()
  64. {
  65. boost::system::error_code err;
  66. if (socket_.is_open()) {
  67. socket_.shutdown(ip::tcp::socket::shutdown_both, err);
  68. socket_.close();
  69. }
  70. free(in_data_);
  71. }
  72. /** Do processing required to start a session.
  73. */
  74. void
  75. ProtobufStreamServer::Session::start_session()
  76. {
  77. remote_endpoint_ = socket_.remote_endpoint();
  78. }
  79. /** Start reading a message on this session.
  80. * This sets up a read handler to read incoming messages. It also notifies
  81. * the parent server of the initiated connection.
  82. */
  83. void
  84. ProtobufStreamServer::Session::start_read()
  85. {
  86. boost::asio::async_read(socket_,
  87. boost::asio::buffer(&in_frame_header_, sizeof(frame_header_t)),
  88. boost::bind(&ProtobufStreamServer::Session::handle_read_header,
  89. shared_from_this(), boost::asio::placeholders::error));
  90. }
  91. /** Send a message.
  92. * @param component_id ID of the component to address
  93. * @param msg_type numeric message type
  94. * @param m Message to send
  95. */
  96. void
  97. ProtobufStreamServer::Session::send(uint16_t component_id, uint16_t msg_type,
  98. google::protobuf::Message &m)
  99. {
  100. QueueEntry *entry = new QueueEntry();
  101. parent_->message_register().serialize(component_id, msg_type, m,
  102. entry->frame_header, entry->message_header,
  103. entry->serialized_message);
  104. entry->buffers[0] = boost::asio::buffer(&entry->frame_header, sizeof(frame_header_t));
  105. entry->buffers[1] = boost::asio::buffer(&entry->message_header, sizeof(message_header_t));
  106. entry->buffers[2] = boost::asio::buffer(entry->serialized_message);
  107. std::lock_guard<std::mutex> lock(outbound_mutex_);
  108. if (outbound_active_) {
  109. outbound_queue_.push(entry);
  110. } else {
  111. outbound_active_ = true;
  112. boost::asio::async_write(socket_, entry->buffers,
  113. boost::bind(&ProtobufStreamServer::Session::handle_write,
  114. shared_from_this(),
  115. boost::asio::placeholders::error,
  116. boost::asio::placeholders::bytes_transferred,
  117. entry));
  118. }
  119. }
  120. /** Disconnect from client. */
  121. void
  122. ProtobufStreamServer::Session::disconnect()
  123. {
  124. boost::system::error_code err;
  125. if (socket_.is_open()) {
  126. socket_.shutdown(ip::tcp::socket::shutdown_both, err);
  127. socket_.close();
  128. }
  129. }
  130. /** Write completion handler. */
  131. void
  132. ProtobufStreamServer::Session::handle_write(const boost::system::error_code& error,
  133. size_t /*bytes_transferred*/,
  134. QueueEntry *entry)
  135. {
  136. delete entry;
  137. if (! error) {
  138. std::lock_guard<std::mutex> lock(outbound_mutex_);
  139. if (! outbound_queue_.empty()) {
  140. QueueEntry *entry = outbound_queue_.front();
  141. outbound_queue_.pop();
  142. boost::asio::async_write(socket_, entry->buffers,
  143. boost::bind(&ProtobufStreamServer::Session::handle_write,
  144. shared_from_this(),
  145. boost::asio::placeholders::error,
  146. boost::asio::placeholders::bytes_transferred,
  147. entry));
  148. } else {
  149. outbound_active_ = false;
  150. }
  151. } else {
  152. parent_->disconnected(shared_from_this(), error);
  153. }
  154. }
  155. /** Incoming data handler for header.
  156. * This method is called if an error occurs while waiting for data (e.g. if
  157. * the remote peer closes the connection), or if new data is available. This
  158. * callback expectes header information to be received.
  159. * @param error error code
  160. */
  161. void
  162. ProtobufStreamServer::Session::handle_read_header(const boost::system::error_code& error)
  163. {
  164. if (! error) {
  165. size_t to_read = ntohl(in_frame_header_.payload_size);
  166. if (to_read > in_data_size_) {
  167. void *new_data = realloc(in_data_, to_read);
  168. if (new_data) {
  169. in_data_size_ = to_read;
  170. in_data_ = new_data;
  171. } else {
  172. parent_->disconnected(shared_from_this(),
  173. errc::make_error_code(errc::not_enough_memory));
  174. }
  175. }
  176. // setup new read
  177. boost::asio::async_read(socket_,
  178. boost::asio::buffer(in_data_, to_read),
  179. boost::bind(&ProtobufStreamServer::Session::handle_read_message,
  180. shared_from_this(), boost::asio::placeholders::error));
  181. } else {
  182. parent_->disconnected(shared_from_this(), error);
  183. }
  184. }
  185. /** Incoming data handler for message content.
  186. * This method is called if an error occurs while waiting for data (e.g. if
  187. * the remote peer closes the connection), or if new data is available. This
  188. * callback expectes message to be received that conforms to a previously
  189. * received header.
  190. * @param error error code
  191. */
  192. void
  193. ProtobufStreamServer::Session::handle_read_message(const boost::system::error_code& error)
  194. {
  195. if (! error) {
  196. message_header_t *message_header = static_cast<message_header_t *>(in_data_);
  197. uint16_t comp_id = ntohs(message_header->component_id);
  198. uint16_t msg_type = ntohs(message_header->msg_type);
  199. try {
  200. std::shared_ptr<google::protobuf::Message> m =
  201. parent_->message_register().deserialize(in_frame_header_, *message_header,
  202. (char *)in_data_ + sizeof(message_header_t));
  203. parent_->sig_rcvd_(id_, comp_id, msg_type, m);
  204. } catch (std::runtime_error &e) {
  205. // ignored, most likely unknown message tpye
  206. parent_->sig_recv_failed_(id_, comp_id, msg_type, e.what());
  207. }
  208. start_read();
  209. } else {
  210. parent_->disconnected(shared_from_this(), error);
  211. }
  212. }
  213. /** @class ProtobufStreamServer <protobuf_comm/server.h>
  214. * Stream server for protobuf message transmission.
  215. * The server opens a TCP socket (IPv4) and waits for incoming connections.
  216. * Each incoming connection is given a unique client ID. Signals are
  217. * provided that can be used to react to connections and incoming data.
  218. * @author Tim Niemueller
  219. */
  220. /** Constructor.
  221. * @param port port to listen on
  222. */
  223. ProtobufStreamServer::ProtobufStreamServer(unsigned short port)
  224. : io_service_(),
  225. acceptor_(io_service_, ip::tcp::endpoint(ip::tcp::v6(), port))
  226. {
  227. message_register_ = new MessageRegister();
  228. own_message_register_ = true;
  229. next_cid_ = 1;
  230. acceptor_.set_option(socket_base::reuse_address(true));
  231. start_accept();
  232. asio_thread_ = std::thread(&ProtobufStreamServer::run_asio, this);
  233. }
  234. /** Constructor.
  235. * @param port port to listen on
  236. * @param proto_path file paths to search for proto files. All message types
  237. * within these files will automatically be registered and available for dynamic
  238. * message creation.
  239. */
  240. ProtobufStreamServer::ProtobufStreamServer(unsigned short port,
  241. std::vector<std::string> &proto_path)
  242. : io_service_(),
  243. acceptor_(io_service_, ip::tcp::endpoint(ip::tcp::v6(), port))
  244. {
  245. message_register_ = new MessageRegister(proto_path);
  246. own_message_register_ = true;
  247. next_cid_ = 1;
  248. acceptor_.set_option(socket_base::reuse_address(true));
  249. start_accept();
  250. asio_thread_ = std::thread(&ProtobufStreamServer::run_asio, this);
  251. }
  252. /** Constructor.
  253. * @param port port to listen on
  254. * @param mr message register to use to (de)serialize messages
  255. */
  256. ProtobufStreamServer::ProtobufStreamServer(unsigned short port,
  257. MessageRegister *mr)
  258. : io_service_(),
  259. acceptor_(io_service_, ip::tcp::endpoint(ip::tcp::v6(), port)),
  260. message_register_(mr), own_message_register_(false)
  261. {
  262. next_cid_ = 1;
  263. acceptor_.set_option(socket_base::reuse_address(true));
  264. start_accept();
  265. asio_thread_ = std::thread(&ProtobufStreamServer::run_asio, this);
  266. }
  267. /** Destructor. */
  268. ProtobufStreamServer::~ProtobufStreamServer()
  269. {
  270. io_service_.stop();
  271. asio_thread_.join();
  272. if (own_message_register_) {
  273. delete message_register_;
  274. }
  275. }
  276. /** Send a message to the given client.
  277. * @param client ID of the client to addresss
  278. * @param component_id ID of the component to address
  279. * @param msg_type numeric message type
  280. * @param m message to send
  281. */
  282. void
  283. ProtobufStreamServer::send(ClientID client, uint16_t component_id, uint16_t msg_type,
  284. google::protobuf::Message &m)
  285. {
  286. if (sessions_.find(client) == sessions_.end()) {
  287. throw std::runtime_error("Client does not exist");
  288. }
  289. sessions_[client]->send(component_id, msg_type, m);
  290. }
  291. /** Send a message.
  292. * @param client ID of the client to addresss
  293. * @param component_id ID of the component to address
  294. * @param msg_type numeric message type
  295. * @param m Message to send
  296. */
  297. void
  298. ProtobufStreamServer::send(ClientID client, uint16_t component_id, uint16_t msg_type,
  299. std::shared_ptr<google::protobuf::Message> m)
  300. {
  301. send(client, component_id, msg_type, *m);
  302. }
  303. /** Send a message.
  304. * @param client ID of the client to addresss
  305. * @param m Message to send, the message must have an CompType enum type to
  306. * specify component ID and message type.
  307. */
  308. void
  309. ProtobufStreamServer::send(ClientID client, google::protobuf::Message &m)
  310. {
  311. const google::protobuf::Descriptor *desc = m.GetDescriptor();
  312. const google::protobuf::EnumDescriptor *enumdesc = desc->FindEnumTypeByName("CompType");
  313. if (! enumdesc) {
  314. throw std::logic_error("Message does not have CompType enum");
  315. }
  316. const google::protobuf::EnumValueDescriptor *compdesc =
  317. enumdesc->FindValueByName("COMP_ID");
  318. const google::protobuf::EnumValueDescriptor *msgtdesc =
  319. enumdesc->FindValueByName("MSG_TYPE");
  320. if (! compdesc || ! msgtdesc) {
  321. throw std::logic_error("Message CompType enum hs no COMP_ID or MSG_TYPE value");
  322. }
  323. int comp_id = compdesc->number();
  324. int msg_type = msgtdesc->number();
  325. if (comp_id < 0 || comp_id > std::numeric_limits<uint16_t>::max()) {
  326. throw std::logic_error("Message has invalid COMP_ID");
  327. }
  328. if (msg_type < 0 || msg_type > std::numeric_limits<uint16_t>::max()) {
  329. throw std::logic_error("Message has invalid MSG_TYPE");
  330. }
  331. send(client, comp_id, msg_type, m);
  332. }
  333. /** Send a message.
  334. * @param client ID of the client to addresss
  335. * @param m Message to send, the message must have an CompType enum type to
  336. * specify component ID and message type.
  337. */
  338. void
  339. ProtobufStreamServer::send(ClientID client, std::shared_ptr<google::protobuf::Message> m)
  340. {
  341. send(client, *m);
  342. }
  343. /** Send a message to all clients.
  344. * @param component_id ID of the component to address
  345. * @param msg_type numeric message type
  346. * @param m message to send
  347. */
  348. void
  349. ProtobufStreamServer::send_to_all(uint16_t component_id, uint16_t msg_type,
  350. google::protobuf::Message &m)
  351. {
  352. std::map<ClientID, boost::shared_ptr<Session>>::iterator s;
  353. for (s = sessions_.begin(); s != sessions_.end(); ++s) {
  354. send(s->first, component_id, msg_type, m);
  355. }
  356. }
  357. /** Send a message to all clients.
  358. * @param component_id ID of the component to address
  359. * @param msg_type numeric message type
  360. * @param m message to send
  361. */
  362. void
  363. ProtobufStreamServer::send_to_all(uint16_t component_id, uint16_t msg_type,
  364. std::shared_ptr<google::protobuf::Message> m)
  365. {
  366. std::map<ClientID, boost::shared_ptr<Session>>::iterator s;
  367. for (s = sessions_.begin(); s != sessions_.end(); ++s) {
  368. send(s->first, component_id, msg_type, m);
  369. }
  370. }
  371. /** Send a message to all clients.
  372. * @param m message to send
  373. */
  374. void
  375. ProtobufStreamServer::send_to_all(std::shared_ptr<google::protobuf::Message> m)
  376. {
  377. std::map<ClientID, boost::shared_ptr<Session>>::iterator s;
  378. for (s = sessions_.begin(); s != sessions_.end(); ++s) {
  379. send(s->first, m);
  380. }
  381. }
  382. /** Send a message to all clients.
  383. * @param m message to send
  384. */
  385. void
  386. ProtobufStreamServer::send_to_all(google::protobuf::Message &m)
  387. {
  388. std::map<ClientID, boost::shared_ptr<Session>>::iterator s;
  389. for (s = sessions_.begin(); s != sessions_.end(); ++s) {
  390. send(s->first, m);
  391. }
  392. }
  393. /** Disconnect specific client.
  394. * @param client client ID to disconnect from
  395. */
  396. void
  397. ProtobufStreamServer::disconnect(ClientID client)
  398. {
  399. if (sessions_.find(client) != sessions_.end()) {
  400. boost::shared_ptr<Session> session = sessions_[client];
  401. session->disconnect();
  402. }
  403. }
  404. /** Start accepting connections. */
  405. void
  406. ProtobufStreamServer::start_accept()
  407. {
  408. Session::Ptr new_session(new Session(next_cid_++, this, io_service_));
  409. acceptor_.async_accept(new_session->socket(),
  410. boost::bind(&ProtobufStreamServer::handle_accept, this,
  411. new_session, boost::asio::placeholders::error));
  412. }
  413. void
  414. ProtobufStreamServer::disconnected(boost::shared_ptr<Session> session,
  415. const boost::system::error_code &error)
  416. {
  417. sessions_.erase(session->id());
  418. sig_disconnected_(session->id(), error);
  419. }
  420. void
  421. ProtobufStreamServer::handle_accept(Session::Ptr new_session,
  422. const boost::system::error_code& error)
  423. {
  424. if (!error) {
  425. new_session->start_session();
  426. sessions_[new_session->id()] = new_session;
  427. sig_connected_(new_session->id(), new_session->remote_endpoint());
  428. new_session->start_read();
  429. }
  430. start_accept();
  431. }
  432. void
  433. ProtobufStreamServer::run_asio()
  434. {
  435. #if BOOST_ASIO_VERSION > 100409
  436. while (! io_service_.stopped()) {
  437. #endif
  438. usleep(0);
  439. io_service_.reset();
  440. io_service_.run();
  441. #if BOOST_ASIO_VERSION > 100409
  442. }
  443. #endif
  444. }
  445. } // end namespace protobuf_comm