I am using boost::beast as well as boost::asio to implement a websocket client with SSL support. My WebsocketClient class has the following members:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<tcp::resolver> resolver;
std::optional<websocket::stream<beast::ssl_stream<beast::tcp_stream>>> ws;
std::promise<void> promise_;
The io_context and ssl_context are constructed and passed by reference from main. The resolver and ws members are initialized via:
resolver.emplace(boost::asio::make_strand(io_context));
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
After calling a WebsocketClient method "run" which triggers a sequence which calls connect, handshake, etc. we enter an async loop. Also of note, we set the value of promise before entering the "on_read" loop. This promise is returned to the caller of run in main which allows the application to progress once the initial WebsocketClient::run call is made.
void WebsocketClient::on_read(
beast::error_code ec,
std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec) {
fail(ec, "read");
reconnect(ec);
} else {
// business logic
ws_->async_read(
buffer_,
beast::bind_front_handler(
&WebsocketClient::on_read,
this)
);
}
}
The WebsocketClient works well for several minutes until I reach an exception:
read: application data after close notify
Upon reaching this error, the WebsocketClient proceeds to:
void WebsocketClient::reconnect(beast::error_code ec) {
ws.emplace(boost::asio::make_strand(io_context), ssl_context);
promise_ = std::promise<void>();
resolver_.emplace(boost::asio::make_strand(io_context));
on_disconnect_cb(ec);
}
The function "on_disconnect_cb" is passed in during WebsocketClient initialization as a lambda function. This function simply calls the WebsocketClient::run function again in attempt to reconnect.
To summarize my questions:
Why am I receiving this "read: application data after close notify" error?
Why does the application fail to reconnect after calling WebsocketClient::reconnect?
In the process of debugging I have concluded that the application makes no additional progress after calling:
resolver->async_resolve(
host,
port,
beast::bind_front_handler(
&WebsocketClient::on_resolve,
this)
);
in the WebsocketClient::run function. This fails only in the case of a reconnect and run works as expected on first call. Therefore I expect the issue to be related to incorrectly resetting some of the components of the WebsocketClient.
Edit:
As requested in the comments, I'd like to provide a sketch of the "main" routine. This is indeed a sketch but should cover how the websocket is being used during the program lifecycle:
class App {
public:
App(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context) : {
ws.emplace(boost::asio::io_context& io_context,
boost::asio::ssl::context& ssl_context,
[this]() {
// on_read callback for ws
logic();
},
[this]() {
// on_disconnect callback for ws
on_disconnect();
}
);
}
void run() {
// call underlying websocket connect
}
void logic() {
// do stuff with inbound message
}
void on_disconnect() {
// destroy existing websocket
// If *this contains a value, destroy that value as if by value().T::~T()
ws.reset();
// Initialize new WebsocketClient
ws.emplace(io_context,
ssl_context,
[this](){
logic();
},
[this](){
on_disconnect();
};
);
// call App::run again to restart
run();
}
private:
boost::asio::io_context& io_context;
boost::asio::ssl::context& ssl_context;
std::optional<WebsocketClient> ws;
};
int main() {
// spinup io_context and detach thread that polls
App app(io_context, ssl_contex);
// triggers WebsocketClient::connect
app.run();
}