std::optional<boost::asio::ip::tcp::socket> initializing using socket.emplace()

Viewed 443

I have the following example code, that implements a asynchronous server using boost::asio

I am trying to understand the code. The questions I have regarding the code are as follows

In function async_accept() in class server, the first statement is

socket.emplace(io_context)

From what I understand, this line call the socket constructor, create a socket. This socket is then passed to the session, usign std::move().

When the async_accept() is called again, in the last line of the funtion, a new socket is created. Is my understanding correct? Am I missing something?

#include <iostream>
#include <optional>
#include <boost/asio.hpp>

class session : public std::enable_shared_from_this<session>
{
public:

    session(boost::asio::ip::tcp::socket&& socket)
    : socket(std::move(socket))
    {
    }

    void start()
    {
        boost::asio::async_read_until(socket, streambuf, '\n', [self = shared_from_this()] (boost::system::error_code error, std::size_t bytes_transferred)
        {
            std::cout << std::istream(&self->streambuf).rdbuf();
        });
    }

private:

    boost::asio::ip::tcp::socket socket;
    boost::asio::streambuf streambuf;
};

class server
{
public:

    server(boost::asio::io_context& io_context, std::uint16_t port)
    : io_context(io_context)
    , acceptor  (io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
    {
    }

    void async_accept()
    {
        socket.emplace(io_context);

        acceptor.async_accept(*socket, [&] (boost::system::error_code error)
        {
            std::make_shared<session>(std::move(*socket))->start();
            async_accept();
        });
    }

private:

    boost::asio::io_context& io_context;
    boost::asio::ip::tcp::acceptor acceptor;
    std::optional<boost::asio::ip::tcp::socket> socket;
};

int main()
{
    boost::asio::io_context io_context;
    server srv(io_context, 15001);
    srv.async_accept();
    io_context.run();
    return 0;
}
1 Answers

Your understanding of optional is correct.

 acceptor.async_accept(*socket, [&] (boost::system::error_code error)
        {
            std::make_shared<session>(std::move(*socket))->start();
            async_accept();
        });

Waits for connections and if a connection is made the code inside the bracers is executed which creates a session object which has a socket as member and then "server::async_accept();" is called recursively so we can accept another connection.
BTW: I do not see a reason for the use of the optional.
I whould suggest to remove the member variable

std::optional<boost::asio::ip::tcp::socket> socket;

and replace Server::async_accept() with

  void
  async_accept ()
  {
    auto socket = std::make_shared<boost::asio::ip::tcp::socket> (io_context);
    acceptor.async_accept (*socket, [&, socket] (boost::system::error_code error) {
      std::make_shared<session> (std::move (*socket))->start ();
      async_accept ();
    });
  }

Full and tested example:

#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <optional>

class session : public std::enable_shared_from_this<session>
{
public:
  session (boost::asio::ip::tcp::socket &&socket) : socket (std::move (socket)) {}

  void
  start ()
  {
    boost::asio::async_read_until (socket, streambuf, '\n', [self = shared_from_this ()] (boost::system::error_code error, std::size_t bytes_transferred) { std::cout << std::istream (&self->streambuf).rdbuf (); });
  }

private:
  boost::asio::ip::tcp::socket socket;
  boost::asio::streambuf streambuf;
};

class server
{
public:
  server (boost::asio::io_context &io_context, std::uint16_t port) : io_context (io_context), acceptor (io_context, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4 (), port)) {}

  void
  async_accept ()
  {
    auto socket = std::make_shared<boost::asio::ip::tcp::socket> (io_context);
    acceptor.async_accept (*socket, [&, socket] (boost::system::error_code error) {
      std::make_shared<session> (std::move (*socket))->start ();
      async_accept ();
    });
  }

private:
  boost::asio::io_context &io_context;
  boost::asio::ip::tcp::acceptor acceptor;
};

int
main ()
{
  boost::asio::io_context io_context;
  server srv (io_context, 15001);
  srv.async_accept ();
  io_context.run ();
  return 0;
}

You can test it by running "localhost:15001" inside your browser. Check the server output for me it prints:

GET / HTTP/1.1
Host: localhost:15001
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

Edit: to fix a bug reported in the comments by liv2hak

Related