Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I am experimenting with the Boost beast::websocket websocket_client_async.cpp example, in conjunction with websocket_server_async.cpp .

As given, the client example just makes a connection, sends a string to the server (which simply echoes back), prints the reply, closes, and exists.

I'm trying to modify the client to keep the session alive, so that I can repeatedly send/receive strings. So, whereas the example code's on_handshake function immediately sends the string via ws_.async_write(...) , I separate that out into its own write(...) function.

Here is my modified session class:

using tcp = boost::asio::ip::tcp;
namespace websocket = boost::beast::websocket;
void fail(boost::system::error_code ec, char const* what)
    std::cerr << what << ": " << ec.message() << "\n";
// Sends a WebSocket message and prints the response
class session : public std::enable_shared_from_this<session>
    tcp::resolver resolver_;
    websocket::stream<tcp::socket> ws_;
    std::atomic<bool> io_in_progress_;
    boost::beast::multi_buffer buffer_;
    std::string host_;
public:
    // Resolver and socket require an io_context
    explicit session(boost::asio::io_context& ioc) : resolver_(ioc), ws_(ioc) {
        io_in_progress_ = false;
    bool io_in_progress() const {
        return io_in_progress_;
    // +---------------------+
    // | The "open" sequence |
    // +---------------------+
    void open(char const* host, char const* port)
        host_ = host;
        // Look up the domain name
        resolver_.async_resolve(host, port,
            std::bind( &session::on_resolve, shared_from_this(),
                std::placeholders::_1, std::placeholders::_2 )
    void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results)
        if (ec)
            return fail(ec, "resolve");
        boost::asio::async_connect(
            ws_.next_layer(), results.begin(), results.end(),
            std::bind( &session::on_connect, shared_from_this(),
                std::placeholders::_1 )
    void on_connect(boost::system::error_code ec)
        if (ec)
            return fail(ec, "connect");
        ws_.async_handshake(host_, "/",
            std::bind( &session::on_handshake, shared_from_this(),
                std::placeholders::_1 )
    void on_handshake(boost::system::error_code ec)
        if (ec)
            return fail(ec, "handshake");
        else {
            std::cout << "Successful handshake with server.\n";
    // +---------------------------+
    // | The "write/read" sequence |
    // +---------------------------+
    void write(const std::string &text)
        io_in_progress_ = true;
        ws_.async_write(boost::asio::buffer(text),
            std::bind( &session::on_write, shared_from_this(),
                std::placeholders::_1, std::placeholders::_2 )
    void on_write(boost::system::error_code ec, std::size_t bytes_transferred)
        boost::ignore_unused(bytes_transferred);
        if (ec)
            return fail(ec, "write");
        ws_.async_read(buffer_,
            std::bind( &session::on_read, shared_from_this(),
                std::placeholders::_1, std::placeholders::_2 )
    void on_read(boost::system::error_code ec, std::size_t bytes_transferred)
        io_in_progress_ = false; // end of write/read sequence
        boost::ignore_unused(bytes_transferred);
        if (ec)
            return fail(ec, "read");
        std::cout << boost::beast::buffers(buffer_.data()) << std::endl;
    // +----------------------+
    // | The "close" sequence |
    // +----------------------+
    void close()
        io_in_progress_ = true;
        ws_.async_close(websocket::close_code::normal,
            std::bind( &session::on_close, shared_from_this(),
                std::placeholders::_1)
    void on_close(boost::system::error_code ec)
        io_in_progress_ = false; // end of close sequence
        if (ec)
            return fail(ec, "close");
        std::cout << "Socket closed successfully.\n";

The problem is that, while the connection works fine and I can send a string, the on_read callback is never hit (unless I do an ugly hack described below).

My main looks like this:

void wait_for_io(std::shared_ptr<session> psession, boost::asio::io_context &ioc)
    // Continually try to run the ioc until the callbacks are finally
    // triggered (as indicated by the session::io_in_progress_ flag)
    while (psession->io_in_progress()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        ioc.run();
int main(int argc, char** argv)
    // Check command line arguments.
    if (argc != 3) {
        std::cerr << "usage info goes here...\n";
        return EXIT_FAILURE;
    const char *host = argv[1], *port = argv[2];
    boost::asio::io_context ioc;
    std::shared_ptr<session> p = std::make_shared<session>(ioc);
    p->open(host, port);
    ioc.run(); // This works. Connection is established and all callbacks are executed.
    p->write("Hello world"); // String is sent & received by server,
                             // even before calling ioc.run()
                             // However, session::on_read callback is never
                             // reached.
    ioc.run();               // This seems to be ignored and returns immediately, so
    wait_for_io(p, ioc);     // <-- so this hack is necessary
    p->close();              // session::on_close is never reached
    ioc.run();               // Again, this seems to be ignored and returns immediately, so
    wait_for_io(p, ioc);     // <-- this is necessary
    return EXIT_SUCCESS;

If I do this:

p->write("Hello world");
while(1) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));

I can confirm that the string is sent & received1 by the server, and that the session::on_read callback is not reached.

Same thing occurs with p->close().

But, if I add my weird wait_for_io() function, everything works. I'm positive this a terrible hack, but I can't figure out what's going on.

1 Note: I can confirm that the message does reach the server, as I modified the server example to print any received strings to the console. This was the only modified I made. The echo-to-client functionality was not changed.

Just an observation, this code has undefined behavior. You are calling boost::asio::buffer(text) in write. But text can go out of scope while the asynchronous operation is still running. It is the caller's responsibility to ensure that the lifetime of memory buffers passed to stream algorithms remain valid at least until the corresponding completion handler is called. – Vinnie Falco Apr 27, 2018 at 0:02 @VinnieFalco Oops... good point. That was just a stupid transcription error I made while formatting my code for SO.SE. – Blair Fonville Apr 28, 2018 at 0:17 Thanks Vinnie. I do understand that, and tested it that way to begin with, but had some difficulty ending the pending call cleanly, when I wanted to close the socket. But, to be honest I don't see how my code above doesn't adhere to to what you wrote in your first sentence. When I call p->write(...); just before ioc.run(); there IS pending work. And the read should then follow. – Blair Fonville Apr 26, 2018 at 23:34 To clarify, when I call p->write(...), the function itself creates work - which is why I don't understand how ioc.run() fails to do anything. – Blair Fonville Apr 26, 2018 at 23:59 If you want to close the websocket connection, just call async_close. Make sure you do that from the same implicit or explicit strand which is used to make the other calls. Pending calls to async_read and async_write will eventually completion with websocket::error::closed and boost::asio::operation_aborted respectively when the async_close operation has completed. I'm not sure exactly why io_context::run is returning right away, but it can only be because there is no pending work. – Vinnie Falco Apr 27, 2018 at 0:04 Yes, calling async_close does work - I'm not sure what the issue was that I had before; it's simple to do. Maybe I forgot to join the thread or something stupid. But it's a bit beside the point. My question is really regarding why the code above doesn't work. For now, I'm just testing my understanding of the library to be sure I have a firm grasp writing my operational code. I'd like to clear up this mystery before continuing. I'm beginning to think it's a bug in the library. – Blair Fonville Apr 27, 2018 at 22:13 I would be very surprised if there was a defect in the latest release of Beast. The code almost has 100% coverage via tests, and the tests are quite exhaustive. The example clients and servers work as written. If they stop working, it is certainly because of any changes that were made. I would start over using the original code and verify that it does work as expected, and then apply your changes incrementally to find out what causes it to malfunction. – Vinnie Falco Apr 27, 2018 at 23:26

The reason the calls to io_context::run() are not working after the first call (shown here):

boost::asio::io_context ioc;
std::shared_ptr<session> p = std::make_shared<session>(ioc);
p->open(host, port);
ioc.run(); // This works. Connection is established and all callbacks are executed.

is because the function io_context::restart() must be called prior to any subsequent calls of io_context::run.

From the documentation:

io_context::restart

Restart the io_context in preparation for a subsequent run() invocation.

This function must be called prior to any second or later set of invocations of the run(), run_one(), poll() or poll_one() functions when a previous invocation of these functions returned due to the io_context being stopped or running out of work. After a call to restart(), the io_context object's stopped() function will return false.

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.