1

Complete noob here learning c++ through an IoT project using Websocket. So far, somewhat successfully modified this example beast async_client_ssl to handshake with a server.

My problem is ioc.run() runs out of work and exits after the initial callback. I was having the same issue as this post two years ago. Boost.best websocket ios.run() exits after sending a request and receiving one reply

The answers from the linked post above were pretty simple (1. and 2.), but I still have no clue how to implement it.

1. Without reading your code, understand that the run() method terminates if there is no pending work. For instance, your read method needs to queue up a new read.

2. Move async_read() to a separate function, let’s say do_read(), and call it at the end of on_read()as well as where it currently is.

The person who asked the question in the post also seemed puzzled, but after these two answers, there was no further explanation. So, is there anyone who can kindly help me out, perhaps with a simple code snippet?

In on_read() in the code from some other noob's previous post, I added the async_read() like below.

void on_read(boost::system::error_code ec, std::size_t bytes_transferred)
{
     io_in_pr34ogress_ = false; // end of write/read sequence
     boost::ignore_unused(bytes_transferred);

     if(ec)
        return fail(ec, "read");
     else
        std::cout << "on_read callback : " << boost::beast::buffers(buffer_.data()) << std::endl;
           
     // Clear the Buffer
     //~ buffer_ = {};
     buffer_.consume(buffer_.size());
     ws_.async_read(buffer_, std::bind(&session::on_read, shared_from_this(), 
                    std::placeholders::_1, std::placeholders::_2));
}

But no hope. ioc.run just terminates. So how to do the above 1. and 2. answers appropriately?

Thanks!

-----------------UPDATED on 10/25/2021-------------------

The answer from @sehe worked with the executor. I had to upgrade the boost version from 1.67 to above 1.7 (I used 1.74) to do so. This solved my issue but if someone has a working solution for 1.67 for the folks out there, please share the idea:)

mqmarathon
  • 47
  • 5
  • 2
    You need to show a self-contained example, becuase, by definition if `on_read` calls `async_read` then the service _does not run out of work_. Could it be that the connection is actively closed by the other end? – sehe Oct 17 '21 at 13:09
  • @sehe Thanks for the reply. I thought so, but I was unable to figure out what actively closes it. Here is my entire session class and main function. Please see the updated post above. Any idea why ioc is just returning? – mqmarathon Oct 17 '21 at 20:32

1 Answers1

2

Okay, the simplest thing is to add a work_guard. The more logical thing to do is to have a thread_pool as the execution context.

Slap a work guard on it:

boost::asio::io_context ioc;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
    work = make_work_guard(ioc.get_executor());

(or simply auto work = make_work_guard(...);).

If at some point you want the run to return, release the work guard:

work.reset();

A Thread Pool

The previous sort of skimped over the "obvious" fact that you'd need another thread to either run() the service or to reset() the work guard.

Instead, I'd suggest to leave the running of the thread to Asio in the first place:

boost::asio::thread_pool ioc(1);

This, like io_context is an execution context:

int main()
{
    // +-----------------------------+
    // | Get azure access token      |
    // +-----------------------------+
    static std::string const accessToken =
        "wss://sehe797979.webpubsub.azure.com/client/hubs/"
        "Hub?access_token=*************************************"
        "**********************************************************************"
        "**********************************************************************"
        "**********************************************************************"
        "************************************************************";
    // get_access_token();

    // +--------------------------+
    // | Websocket payload struct |
    // +--------------------------+
    struct Payload payload = {0, "", "text", "test", "joinGroup"};

    // +---------------------------------+
    // | Websocket connection parameters |
    // +---------------------------------+
    std::string protocol = "wss://";
    std::string host     = "sehe797979.webpubsub.azure.com";
    std::string port     = "443";
    std::string text     = json_payload(payload);

    auto endpointSubstringIndex = protocol.length() + host.length();

    // Endpoint
    std::string endpoint = accessToken.substr(endpointSubstringIndex);
    //std::cout << "Endpoint : " << endpoint << std::endl;
    
    // The io_context is required for all I/O
    boost::asio::thread_pool ioc(1);

    // The SSL context is required, and holds certificates
    ssl::context ctx{ssl::context::sslv23_client};

    // This holds the root certificate used for verification
    load_root_certificates(ctx);

    // Launch the asynchronous operation
    std::shared_ptr<session> ws_session =
        std::make_shared<session>(ioc.get_executor(), ctx);
    ws_session->open(host, port, endpoint);

    // Run the I/O service. The call will return when the socket is closed.
    // Change the payload type
    payload.type = "sendToGroup";

    // +--------------+
    // | Send Message |
    // +--------------+
    // Get the input and update the payload data
    while (getline(std::cin, payload.data)) {
        // Send the data over WSS
        ws_session->write(json_payload(payload));
    }

    ioc.join();
}

This requires minimal changes to the session constructor to take an executor instead of the io_context&:

template <typename Executor>
explicit session(Executor executor, ssl::context& ctx)
    : resolver_(executor)
    , ws_(executor, ctx)
{
}

Here's a fully self-contained compiling demo Live On Coliru

enter image description here

sehe
  • 374,641
  • 47
  • 450
  • 633
  • My live demo is not "working" as I have no idea about the payload json, I faked it [as you can see](http://coliru.stacked-crooked.com/a/16cc3291788069ad). However, it is *more than enough* to demonstrate connectivity and proof of concept with a simplistic aazure web pubsub endpoint. – sehe Oct 18 '21 at 01:35
  • Thank you so much for taking your time! I tried the thread pool. But it failed to compile. Please see the error message updated in the main post. It says there is no matching function. Looks like you used a different beast version? First of all, I really like the demo video! Wish it was longer so that I could learn more from it. I also liked your profile saying "I got the impression more than once that people think that "we, the experts" use some kind of magic fairy dust and promptly post the solutions without breaking a sweat, I thought it would be nice to show the reality." haha – mqmarathon Oct 19 '21 at 01:46
  • I wonder if it is possible to set a live tutorial on a different platform on zoom or slack for one time? I have other Boost questions too. Eager to learn but having difficulty learning by myself. If it's too much, please ignore it haha. But I would really appreciate it if you could guide me a little further with this problem. Thanks! – mqmarathon Oct 19 '21 at 01:46
  • Mmm. `thread_pool` is boost 1.66+, and 1.70 mentions [_I/O objects' constructors and functions that previously took an asio::io_context& now accept either an Executor or a reference to a concrete ExecutionContext_](https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/history.html#:~:text=I/O%20objects%27%20constructors%20and%20functions%20that%20previously%20took%20an%20asio%3A%3Aio_context%26%20now%20accept%20either%20an%20Executor%20or%20a%20reference%20to%20a%20concrete%20ExecutionContext). So... I patched up an install with Boost 1.69 + Boost JSON from 1.75 and repro'ed. – sehe Oct 19 '21 at 12:19
  • I don't see a way out other than not using the thread_pool - you can [approximate it manually with `thread_group`](https://pastebin.ubuntu.com/p/qbkkGRgtVb/). I would strongly suggest updating boost, because that approach is just a million times tricckier when you actually have more than 1 thread (requiring very conscious strand use, instead of "just" tying the IO objects to a strand executor) as well as getting the thread loop right. – sehe Oct 19 '21 at 12:21
  • By the way [you can ping (@sehe) me here, and we can even split of another chat room if you want.](https://chat.stackoverflow.com/transcript/message/53279242#53279242) – sehe Oct 19 '21 at 12:37
  • I am using 1.67. There is documentation here regarding a thread_pool in 1.67(https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/thread_pool.html) It looks like the resolver doesn't have an overloading method to accept this executor type. I don't know if I can just update Beast because, as far as I know, the latest available boost version for Rpi is 1.67. Will try your suggestion. Please let me know if you have any other thoughts on this. Thanks again! – mqmarathon Oct 19 '21 at 14:12
  • These libraries are header-only, just downloading the tarball should do – sehe Oct 19 '21 at 14:21
  • I downloaded a tarball of boost 1.74 and replaced 1.67 with it in my Raspberry pi 4. Like you said it's header-only. So a simple library swapping should have no problem. I compiled a simple HTTP client example successfully. I wonder why Debian has a default version as 1.67 and not making 'sudo apt-catche search libboost' show above 1.67 versions. I assume that's a version Debian guarantees that works with their version, Buster. – mqmarathon Oct 20 '21 at 04:15
  • Anyhow, I will use this example "websocket_client_async_ssl_system_executor.cpp" which wasn't an option for 1.67. [link](https://www.boost.org/doc/libs/1_74_0/libs/beast/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp) This executor type is what you think I should use right? I will work on it tomorrow and let you know. If you can quickly test yours with 1.74, that would be amazing! Not just this case, I want to learn more so please bear with me if I am bothering you everyday. I really appreciate your help again. Thank you. – mqmarathon Oct 20 '21 at 04:21
  • Forgot to mention that I tried to compile the previous code made with 1.67 (and with your above example with the executor) just to know how many changes happened between the two versions. That.... threw many errors haha. – mqmarathon Oct 20 '21 at 04:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238358/discussion-between-mqmarathon-and-sehe). – mqmarathon Oct 20 '21 at 12:22
  • 1
    Your "thread_pool" suggestion worked. I removed the additional question regarding a different error on run-time and restored the post back to the original. Thank you for the help again! - By the way, the error (additional question) was nothing to do with the threading. I put the read_async inside the on_handshake. Basically, it was a buffer issue. The problem is solved as well. – mqmarathon Oct 25 '21 at 14:34