I'm using boost 1.75.0 and I'm trying to update my server so he can listen to two different ports at the same time
let's say IP 127.0.0.1 port 6500 and port 6600.
do I need to hold in the Server
two sockets?
this is my server
#include <boost/asio/io_service.hpp>
#include <boost/asio.hpp>
#include <queue>
#include "Session.h"
using boost::asio::ip::tcp;
class Server
{
public:
Server(boost::asio::io_service &io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service)
{
do_accept();
}
private:
void do_accept(){
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec) {
if (!ec) {
std::cout << "accept connection\n";
std::make_shared<Session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
this is my Session class
#include <boost/asio/io_service.hpp>
#include <boost/asio.hpp>
#include "message.h"
#include <fstream>
#include <boost/bind/bind.hpp>
namespace {
using boost::asio::ip::tcp;
auto constexpr log_active=true;
using boost::system::error_code;
using namespace std::chrono_literals;
using namespace boost::posix_time;
};
class Session
: public std::enable_shared_from_this<Session>
{
public:
Session(tcp::socket socket)
: socket_(std::move(socket)) ,
{
}
~Session()= default;
void start();
void do_write_alive();
private:
void do_read_header();
void do_read_body();
void do_write();
using Strand = boost::asio::strand<tcp::socket::executor_type>;
using Timer = boost::asio::steady_timer;
tcp::socket socket_{strand_};
Strand strand_{make_strand(socket_.get_executor())};
Timer recv_deadline_{strand_};
Timer send_deadline_{strand_};
enum { max_length = 1024 };
char data_[max_length];
};
I didn't include the implementation of the Session
class only the constructor because there not relevant.
You need two acceptors.
You can do without the socket_ instance, removing the duplication as well.
Here's a minimal refactor that runs 10 listeners, stored in a deque (for reference stability):
class Server {
public:
Server(boost::asio::io_service& svc, uint16_t base_port) {
for (uint16_t port = base_port; port < base_port + 10; ++port) {
auto& a = acceptors_.emplace_back(svc, tcp::endpoint{{}, port});
a.listen();
accept_loop(a);
}
}
void stop() {
for (auto& a: acceptors_)
a.cancel();
}
private:
static void accept_loop(tcp::acceptor& a) {
a.async_accept(a.get_executor(), [&a](error_code ec, tcp::socket&& s) {
if (!ec) {
std::cout << "accepted " << s.remote_endpoint() << " on :" << a.local_endpoint().port() << std::endl;
std::make_shared<Session>(std::move(s))->start();
accept_loop(a);
} else {
std::cout << "exit accept_loop " << ec.message() << std::endl;
}
});
}
std::deque<tcp::acceptor> acceptors_;
};
io_context
and executorsYou can modernize this some more by using the executor model:
#include <boost/asio.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/bind/bind.hpp>
#include <fstream>
#include <deque>
namespace {
using boost::asio::ip::tcp;
//auto constexpr log_active = true;
using boost::system::error_code;
using namespace std::chrono_literals;
//using namespace boost::posix_time;
} // namespace
class Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket) : socket_(std::move(socket)) {}
~Session() = default;
void start() {}
void do_write_alive() {}
private:
void do_read_header() {}
void do_read_body() {}
void do_write() {}
using Strand = boost::asio::strand<tcp::socket::executor_type>;
using Timer = boost::asio::steady_timer;
tcp::socket socket_{strand_};
Strand strand_{make_strand(socket_.get_executor())};
Timer recv_deadline_{strand_};
Timer send_deadline_{strand_};
enum { max_length = 1024 };
char data_[max_length];
};
#include <boost/asio.hpp>
#include <boost/asio/io_service.hpp>
#include <queue>
#include <iostream>
using boost::asio::ip::tcp;
class Server {
public:
template <typename Ex>
Server(Ex executor, uint16_t base_port) {
for (uint16_t port = base_port; port < base_port + 10; ++port) {
auto& a = acceptors_.emplace_back(executor, tcp::endpoint{{}, port});
a.listen();
accept_loop(a);
}
}
void stop() {
for (auto& a: acceptors_)
a.cancel();
}
private:
static void accept_loop(tcp::acceptor& a) {
a.async_accept(a.get_executor(), [&a](error_code ec, tcp::socket&& s) {
if (!ec) {
std::cout << "accepted " << s.remote_endpoint() << " on :" << a.local_endpoint().port() << std::endl;
std::make_shared<Session>(std::move(s))->start();
accept_loop(a);
} else {
std::cout << "exit accept_loop " << ec.message() << std::endl;
}
});
}
std::deque<tcp::acceptor> acceptors_;
};
int main() {
boost::asio::io_context context;
Server s(context.get_executor(), 7878);
context.run_for(10s);
s.stop();
context.run();
}
Note also that the accept-loop can now be canceled. With a sample client run of e.g.
(for p in {7878..7887}; do (sleep 1.$RANDOM; nc localhost $p <<<"HELLO")& done; wait)
Prints e.g.
accepted 127.0.0.1:37958 on :7880
accepted 127.0.0.1:45100 on :7885
accepted 127.0.0.1:42414 on :7883
accepted 127.0.0.1:49988 on :7886
accepted 127.0.0.1:44898 on :7878
accepted 127.0.0.1:43536 on :7879
accepted 127.0.0.1:35350 on :7882
accepted 127.0.0.1:40158 on :7887
accepted 127.0.0.1:53022 on :7884
accepted 127.0.0.1:39020 on :7881
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled
exit accept_loop Operation canceled