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:)
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.
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();
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