2

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!

Taki
  • 313
  • 4
  • 13

1 Answers1

2

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?

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you so much @sehe for your detailed response! Although I've modified like what you did, it still doesn't work for me. Perhaps, I need to use something else than CreateThread function to run it as a thread. I'm looking for the way right now. – Taki Nov 04 '21 at 02:17
  • I'd like to have a private chat with you via Skype or something on this matter if you are okay with it. – Taki Nov 04 '21 at 02:32
  • I prefer to keep the help on site. That way we guarantee that you get the best help the fastest and your question helps the most people. – sehe Nov 04 '21 at 03:43
  • I've just run your code from scratch, but it still has the same error as mine. It seems the problem relies on somewhere else.. Perhaps, it can be caused from how you compiled it? – Taki Nov 04 '21 at 06:32
  • Well. I doubt the compilation command is the problem as well. Can it really depend on the pc that you run the code on? What's your opinion @sehe? – Taki Nov 04 '21 at 06:49
  • I've run the debugger on your code, and it seems I'm failing here. – Taki Nov 04 '21 at 07:04
  • #if !defined(BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS) BOOST_SYSTEM_CONSTEXPR explicit operator bool() const BOOST_NOEXCEPT // true if error { return failed_; } Perhaps, it's actually related to compilation option. – Taki Nov 04 '21 at 07:05
  • Okay. you've actually typed './a.out& for a in {1..10}; do netcat 127.0.0.1 9797 <<< "Hello world"; done kill %1' to run. Am I right? – Taki Nov 04 '21 at 09:04
  • That runs the program (`a.out` because I didn't name it) *and* runs ten client sessions as well using `netcat`. Then it kills the program that ran in the background. – sehe Nov 04 '21 at 12:28
  • Regarding the error, there is no way for me to magically know where [that code](https://stackoverflow.com/questions/69821226/listeneron-accept-function-is-not-called-upon-end-of-do-accept-function-in-boo/69827047?noredirect=1#comment123444654_69827047) is from. Let alone what the error message could be. – sehe Nov 04 '21 at 12:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238883/discussion-between-taki-and-sehe). – Taki Nov 05 '21 at 01:33