1

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.

yaodav
  • 1,126
  • 12
  • 34

1 Answers1

5

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_;
};

Use io_context and executors

You can modernize this some more by using the executor model:

Live On Coliru

#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
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Will this also work for having multiple listeners listening on the same port, in order to scale the number of accepted connections performance? – benjist Sep 24 '22 at 06:05
  • @benjist yes. Provided that you [use the required flags and subject to some platform dependencies](https://stackoverflow.com/a/14388707/85371). In fact, you could equally well do it in separate processes, which avoids inadvertent sharing which could require locking that might lead to worse performance than using a single thread. – sehe Sep 24 '22 at 22:02