Search code examples
c++unixtcpboost-asio

How to set up an acceptor to accept on unix domain and tcp sockets?


I am writing a server class, and I would like to be able to support any stream protocol.

This page gives some tips : https://beta.boost.org/doc/libs/1_83_0/doc/html/boost_asio/overview/networking/other_protocols.html

However, it's quite elusive on the acceptor part...

Do you have an example on how to set up an acceptor to work with a boost::asio::generic::stream_protocol::endpoint (that could be at least ip/TCP or unix domain) ?


Solution

  • You'd need to have multiple listeners, so e.g.

    template <typename Socket> struct Listener {
        using Proto    = typename Socket::protocol_type;
        using Acceptor = asio::basic_socket_acceptor<Proto>;
        using Endpoint = Acceptor::endpoint_type;
    
        Listener(asio::any_io_executor ex, Endpoint ep) : acc_(std::move(ex), std::move(ep)) { accept_loop(); }
    
      private:
        void accept_loop() {
            acc_.async_accept([](error_code ec, Socket s) {
                std::cout << "Accepted (" << ec.message() << ") from " << s.remote_endpoint() << std::endl;
                if (!ec)
                    std::make_shared<Session<Socket>>(std::move(s))->Start();
            });
        }
        Acceptor acc_;
    };
    

    As you can see the session class is also templated on the socket type so you need to implement it only once:

    template <typename Socket> struct Session /*: BaseSession*/ {
        Session(Socket s) : sock_(std::move(s)) {}
    
        void Start() {
            read_loop();
        }
    
      private:
        void read_loop() {
            async_read_until(sock_, asio::dynamic_buffer(message), "\n",
                             [this, self = shared_from_this()](error_code ec, size_t n) {
                                 std::cout << "Received (" << ec.message() << "): "              //
                                           << quoted(std::string_view(message).substr(0, n - 1)) //
                                           << std::endl;
    
                                if (!ec) {
                                    message.erase(0, n);
                                    read_loop();
                                }
                             });
        }
        std::string message;
        Socket sock_;
    };
    

    If you need common operations on all session instantiations, consider a base-class.

    Now a multi-server is as simple as:

    struct MultiServer {
        MultiServer(asio::any_io_executor ex, std::set<uint16_t> tcp_listen_ports, std::set<std::string> unix_domain_socket) {
            for (auto p : tcp_listen_ports)
                listeners_.push_back(std::make_shared<Listener<tcp::socket>>(ex, tcp::endpoint{{}, p}));
            for (auto& s : unix_domain_socket)
                listeners_.push_back(std::make_shared<Listener<uxd::socket>>(ex, s));
        }
    
      private:
        std::vector<std::shared_ptr<void> > listeners_;
    };
    
    int main() {
        asio::io_context ioc;
        MultiServer      multi(ioc.get_executor(), {8989, 7979}, {"socket1", "socket2"});
    
        ioc.run_for(10s); // limited for Coliru
    }
    

    This runs a server listening on 2 tcp ports and 2 unix domain sockets, and spawning the same session on all of them:

    enter image description here

    Full Listing

    Live On Coliru

    #include <boost/asio.hpp>
    #include <iomanip>
    #include <iostream>
    #include <set>
    namespace asio = boost::asio;
    using namespace std::chrono_literals;
    using boost::system::error_code;
    using tcp = asio::ip::tcp;
    using uxd = asio::local::stream_protocol;
    
    struct BaseSession : std::enable_shared_from_this<BaseSession> {
        // in case you need any "common" behavior
        // virtual ~BaseSession() = default;
    };
    
    template <typename Socket> struct Session : BaseSession {
        Session(Socket s) : sock_(std::move(s)) {}
    
        void Start() {
            read_loop();
        }
    
      private:
        void read_loop() {
            async_read_until(sock_, asio::dynamic_buffer(message), "\n",
                             [this, self = shared_from_this()](error_code ec, size_t n) {
                                 std::cout << "Received (" << ec.message() << "): "              //
                                           << quoted(std::string_view(message).substr(0, n - 1)) //
                                           << std::endl;
    
                                if (!ec) {
                                    message.erase(0, n);
                                    read_loop();
                                }
                             });
        }
        std::string message;
        Socket sock_;
    };
    
    template <typename Socket> struct Listener {
        using Proto    = typename Socket::protocol_type;
        using Acceptor = asio::basic_socket_acceptor<Proto>;
        using Endpoint = Acceptor::endpoint_type;
    
        Listener(asio::any_io_executor ex, Endpoint ep) : acc_(std::move(ex), std::move(ep)) { accept_loop(); }
    
      private:
        void accept_loop() {
            acc_.async_accept([](error_code ec, Socket s) {
                std::cout << "Accepted (" << ec.message() << ") from " << s.remote_endpoint() << std::endl;
                if (!ec)
                    std::make_shared<Session<Socket>>(std::move(s))->Start();
            });
        }
        Acceptor acc_;
    };
    
    struct MultiServer {
        MultiServer(asio::any_io_executor ex, std::set<uint16_t> tcp_listen_ports, std::set<std::string> unix_domain_socket) {
            for (auto p : tcp_listen_ports)
                listeners_.push_back(std::make_shared<Listener<tcp::socket>>(ex, tcp::endpoint{{}, p}));
            for (auto& s : unix_domain_socket)
                listeners_.push_back(std::make_shared<Listener<uxd::socket>>(ex, s));
        }
    
      private:
        std::vector<std::shared_ptr<void> > listeners_;
    };
    
    int main() {
        asio::io_context ioc;
        MultiServer      multi(ioc.get_executor(), {8989, 7979}, {"socket1", "socket2"});
    
        ioc.run_for(10s); // limited for Coliru
    }