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) ?
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:
#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
}