listener::on_accept function is not called upon end of do_accept function in Boost library

Viewed 81

I'm a newbie in using BOOST library. I'm currently trying to send packets to Web-socket using boost library and struggling.

I've referred to the template from this boost website (https://www.boost.org/doc/libs/1_68_0/libs/beast/example/websocket/server/async/websocket_server_async.cpp).

Firstly, I call listener constructor by running the thread function below.

DWORD WINAPI RunWebServerThread(LPVOID lpParameter)
{
    int threads(1);
    net::io_context ioc{ threads };

    queue<PQN_DATA_PACKET *> *pconnectionQ = (queue<PQN_DATA_PACKET *> *)lpParameter;    
    auto const address = net::ip::make_address(LOCAL_IP);
    // Create and launch a listening port
    std::make_shared<listener>(ioc, tcp::endpoint{ address, SERVER_PORT_NUMBER }, pconnectionQ)->run();
    // Run the I/O service on the requested number of threads
    std::vector<std::thread> v;
    v.reserve(threads - 1);
    for (auto i = threads - 1; i > 0; --i)
        v.emplace_back(
            [&ioc]
            {
                ioc.run();
            });
    ioc.run();    
}

When the make_shared function is run, the following functions run from the boost library.

// Start accepting incoming connections
void listener::run()
{
    do_accept();
}

void listener::do_accept()
{
    cout << "DO_ACCEPT LISTENER\n";
    // The new connection gets its own strand
    acceptor_.async_accept(
    net::make_strand(ioc_),
    beast::bind_front_handler(
        &listener::on_accept,
        shared_from_this()));

    cout << "DO_ACCEPT LISTENER END\n";
}

void listener::on_accept(beast::error_code ec, tcp::socket socket)
{
    cout << "ON_ACCEPT LISTENER\n";
    if (ec)
    {
        fail(ec, "accept");
    }
    else
    {
        // Create the session and run it
        std::make_shared<session>(std::move(socket), pconnectionQ, ioc_)->run();
    }

    // Accept another connection
    do_accept();
}

The problem is that on_accept function doesn't seem to be called upon bind_front_handler function from do_accept function.

The console is supposed to show the following.

DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER

However the console is just showing the following.

DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END

As you can see on_accept() function is never called, and it is very hard for me to start debug this since boost library is too complicated.

Can anyone suggest me what to do here?

Thank you in advance!

1 Answers

It seems you have multiple variables names ioc.

Specifically, there's one that is a local variable in RunWebServerThread. And then there's the one that you use in the other listener members: ioc_.

The ioc from the thread function can, by definition not be transmitted outside, so we know it's definitely separate instances.

There's a lot of legacy-code smell/noise (Win32 API thread cruft mixed with std::thread e.g.) as well. I'd simplify that.

Note also that using std::cout << "\n" doesn't guarantee messages being flushed to standard output. Either add std::flush or use std::endl (which includes a flush).

Here's a restructuring of the code imagining that you want to share ioc/queues with one or more listeners for the same server:

Live On Coliru

#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <iostream>
#include <memory>
#include <queue>

namespace net   = boost::asio;
namespace beast = boost::beast;
using net::ip::tcp;
using beast::error_code;

static void fail(error_code ec, std::string_view msg) {
    std::cerr << msg << ": " << ec.message() << std::endl;
    throw std::runtime_error("fail");
}

struct PQN_DATA_PACKET {
};

struct session : std::enable_shared_from_this<session> {
    session(tcp::socket&& socket) : _socket(std::move(socket)) {}

    void run()
    {
        std::cout << "TODO session for " << _socket.remote_endpoint()
                  << std::endl;
    }

  private:
    tcp::socket _socket;
};

using Queue = std::queue<PQN_DATA_PACKET*>;

struct listener : std::enable_shared_from_this<listener> {
    net::io_context& _ioc;
    tcp::acceptor    _acceptor{_ioc};
    Queue&           _connectionQ;

    listener(net::io_context& ioc, tcp::endpoint ep, Queue& connectionQ)
        : _ioc(ioc)
        , _acceptor(ioc, ep)
        , _connectionQ(connectionQ)
    {
    }

    // Start accepting incoming connections
    void run() { do_accept(); }

    void do_accept()
    {
        std::cout << "DO_ACCEPT LISTENER" << std::endl;
        // The new connection gets its own strand
        _acceptor.async_accept( //
            net::make_strand(_ioc),
            beast::bind_front_handler(&listener::on_accept,
                                      shared_from_this()));

        std::cout << "DO_ACCEPT LISTENER END" << std::endl;
    }

    void on_accept(beast::error_code ec, tcp::socket socket)
    {
        std::cout << "ON_ACCEPT LISTENER" << std::endl;
        if (ec) {
            fail(ec, "accept");
        } else {
            // Create the session and run it
            std::make_shared<session>(std::move(socket) /*, _connectionQ*/)
                ->run();
        }

        // Accept another connection
        do_accept();
    }
};

struct server {
    net::io_context           _ioc;
    Queue                     _connectionQ;

    void run(tcp::endpoint ep, unsigned nthreads = 1) {
        std::make_shared<listener>(_ioc, ep, _connectionQ) //
            ->run();

        // Run the I/O service on the requested number of threads
        std::vector<std::thread> v(nthreads);

        for (auto& th : v)
            th = std::thread([this] { _ioc.run(); });

        for (auto& th : v)
            if (th.joinable())
                th.join();
    }
};

int main() {
    static uint16_t constexpr SERVER_PORT_NUMBER = 9797;

    server srv;
    srv.run({{}, SERVER_PORT_NUMBER}, 1);
}

Printing

DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45930
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45932
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45934
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45936
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45938
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45940
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45942
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45944
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45946
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END
ON_ACCEPT LISTENER
TODO session for 127.0.0.1:45948
DO_ACCEPT LISTENER
DO_ACCEPT LISTENER END

More

I'd suggest using boost::asio::thread_pool over rolling your own (keep in mind Should the exception thrown by boost::asio::io_service::run() be caught? e.g.)

Keep in mind that passing a reference ioc around is a code smell in the presence of executors (you should probably be using _socket.get_executor() or creating the strand more locally?

Related