File indexing completed on 2024-09-22 04:57:52

0001 /*
0002     SPDX-FileCopyrightText: 2017, 2018 Ivan Čukić <ivan.cukic(at)kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #ifndef VOY_ENGINE_ASIO_TCP_SERVICE_H
0008 #define VOY_ENGINE_ASIO_TCP_SERVICE_H
0009 
0010 // Standard library
0011 #include <iostream>
0012 #include <functional>
0013 
0014 // Boost
0015 #include <boost/asio.hpp>
0016 
0017 // Solf
0018 #include "../../../utils.h"
0019 
0020 namespace voy::engine::asio::tcp {
0021 
0022 
0023 // Not a good idea to add a using declaration in a header,
0024 // but we can live with it for this small example
0025 using boost::asio::ip::tcp;
0026 
0027 template <typename MessageType>
0028 class with_client {
0029 private:
0030     MessageType m_value;
0031     boost::asio::ip::tcp::socket* m_socket;
0032 
0033 public:
0034     with_client(MessageType value, tcp::socket* socket)
0035         : m_value{std::move(value)}
0036         , m_socket{socket}
0037     {
0038     }
0039 
0040     void reply(const std::string& message) const
0041     {
0042         auto buf = std::make_shared<std::string>(message);
0043         boost::asio::async_write(
0044             *m_socket,
0045             boost::asio::buffer(*buf, buf->length()),
0046             [buf](auto, auto) {});
0047     }
0048 
0049     const MessageType& operator*() const
0050     {
0051         return m_value;
0052     }
0053 
0054     const MessageType* operator->() const
0055     {
0056         return &m_value;
0057     }
0058 };
0059 
0060 namespace detail {
0061 
0062 
0063     template <typename MessageType>
0064     auto make_with_client(MessageType&& value, tcp::socket* socket)
0065     {
0066         return voy::engine::asio::tcp::with_client<MessageType>{
0067             std::forward<MessageType>(value), socket};
0068     }
0069 
0070 
0071     template <typename EmitFunction>
0072     class session: public std::enable_shared_from_this<session<EmitFunction>> {
0073     public:
0074         session(tcp::socket&& socket, EmitFunction emit)
0075             : m_socket(std::move(socket))
0076             , m_emit(emit)
0077         {
0078         }
0079 
0080         void start()
0081         {
0082             do_read();
0083         }
0084 
0085     private:
0086         using shared_session = std::enable_shared_from_this<session<EmitFunction>>;
0087 
0088         void do_read()
0089         {
0090             // Getting a shared pointer to this instance
0091             // to capture it in the lambda
0092             auto self = shared_session::shared_from_this();
0093             boost::asio::async_read_until(
0094                 m_socket, m_data, '\n',
0095                 [this, self](const boost::system::error_code& error,
0096                              std::size_t size) {
0097                     if (!error) {
0098                         // Reading a line from the client and
0099                         // passing it to whoever listens to us
0100                         std::istream is(&m_data);
0101                         std::string line;
0102                         std::getline(is, line);
0103                         m_emit(make_with_client(std::move(line), &m_socket));
0104 
0105                         // Scheduling the next line to be read
0106                         do_read();
0107                     }
0108                 });
0109         }
0110 
0111         tcp::socket m_socket;
0112         boost::asio::streambuf m_data;
0113         EmitFunction m_emit;
0114     };
0115 
0116 
0117     template <typename Socket, typename EmitFunction>
0118     auto make_shared_session(Socket&& socket, EmitFunction&& emit)
0119     {
0120         return std::make_shared<session<EmitFunction>>(
0121                 std::forward<Socket>(socket),
0122                 std::forward<EmitFunction>(emit));
0123     }
0124 
0125 } // namespace detail
0126 
0127 /**
0128  * The service class handles client connections
0129  * and emits the messages sent by the clients
0130  */
0131 template <typename Cont>
0132 class service: utils::non_copyable {
0133 public:
0134     explicit service(boost::asio::io_service& service,
0135                      unsigned short port,
0136                      Cont& cont)
0137         : m_acceptor(service, tcp::endpoint(tcp::v4(), port))
0138         , m_socket(service)
0139         , m_emit{cont}
0140     {
0141         do_accept();
0142     }
0143 
0144 private:
0145     void do_accept()
0146     {
0147         m_acceptor.async_accept(
0148             m_socket, [this] (const boost::system::error_code& error) {
0149                 if (!error) {
0150                     // Creating a new session and start listing for
0151                     // client messages
0152                     detail::make_shared_session(
0153                             std::move(m_socket),
0154                             m_emit
0155                         )->start();
0156 
0157                 } else {
0158                     // If there was a connection error,
0159                     // just write it out
0160                     std::cerr << error.message() << std::endl;
0161 
0162                 }
0163 
0164                 // Listening to another client
0165                 do_accept();
0166             });
0167     }
0168 
0169     tcp::acceptor m_acceptor;
0170     tcp::socket m_socket;
0171     Cont& m_emit;
0172 };
0173 
0174 } // namespace voy::engine::asio::tcp
0175 
0176 #endif // include guard