#include "WebSocketClient.hpp"
WebSocketClient::WebSocketClient()
: io_context_(), ssl_context_(ssl::context::tlsv13_client), resolver_(net::make_strand(io_context_)), buffer_(),
heartbeat_manager_(io_context_, std::chrono::seconds(HEARTBEAT_INTERVAL),
[this](const std::vector<uint8_t>& message) {
SendHeartbeat(message);
})
{}
WebSocketClient::~WebSocketClient() = default;
void WebSocketClient::Connect(std::string host, std::string port, std::string uri)
{
ws_.reset();
if (io_context_.stopped()) {
io_context_.restart();
}
ws_.emplace(net::make_strand(io_context_), ssl_context_);
if (!SSL_set_tlsext_host_name(ws_->next_layer().native_handle(), host.c_str())) {
beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
on_error(std::runtime_error("SSL_set_tlsext_host_name failed: " + ec.message()));
return;
}
host_ = host;
port_ = port;
uri_ = uri;
resolver_.async_resolve(host, port, beast::bind_front_handler(&WebSocketClient::on_resolve, this));
ws_->binary(true);
}
void WebSocketClient::Disconnect()
{
try {
beast::error_code ec;
ws_->close(websocket::close_code::normal, ec);
if (ec) {
if (ec == net::error::operation_aborted || ec == net::error::not_connected || ec == net::error::eof) {
return;
}
on_error(std::runtime_error("Error during disconnect: " + ec.message()));
}
ws_.reset();
io_context_.stop();
buffer_.consume(buffer_.size());
} catch (const std::exception& e) {
on_error(e);
}
}
void WebSocketClient::Send(const std::vector<uint8_t>& binary_data)
{
{
std::lock_guard<std::mutex> lock(sendMutex_);
sendQueue_.push(binary_data);
}
{
std::lock_guard<std::mutex> lock(sendMutex_);
if (!isWriting_) {
isWriting_ = true;
net::post(io_context_, [this]() {
ProcessSendQueue();
});
}
}
}
void WebSocketClient::ProcessSendQueue()
{
std::shared_ptr<std::vector<uint8_t>> data;
{
std::unique_lock<std::mutex> lock(sendMutex_);
if (sendQueue_.empty()) {
isWriting_ = false;
return;
}
data = std::make_shared<std::vector<uint8_t>>(std::move(sendQueue_.front()));
sendQueue_.pop();
}
ws_->async_write(net::buffer(*data),
[this, data](const boost::system::error_code& ec, std::size_t bytes_transferred) {
on_write(ec, bytes_transferred);
});
}
void WebSocketClient::Run()
{
try {
if (io_context_.stopped()) {
io_context_.restart();
}
io_context_.run();
} catch (const std::exception& e) {
std::cerr << "Error in io_context: " << e.what() << std::endl;
on_error(e);
}
}
void WebSocketClient::StartHeartbeat(std::chrono::seconds interval)
{
heartbeat_manager_.SetInterval(interval);
heartbeat_manager_.Start();
}
void WebSocketClient::StopHeartbeat()
{
heartbeat_manager_.Stop();
}
void WebSocketClient::SendHeartbeat(const std::vector<uint8_t>& message)
{
ws_->async_write(net::buffer(message), [this](const boost::system::error_code& ec, std::size_t) {
if (ec) {
on_error(std::runtime_error("Failed to send heartbeat: " + ec.message()));
return;
}
});
}
void WebSocketClient::on_resolve(const boost::system::error_code& ec, tcp::resolver::results_type results)
{
if (ec) {
on_error(std::runtime_error("Resolve failed: " + ec.message()));
return;
}
beast::get_lowest_layer(*ws_).async_connect(results, beast::bind_front_handler(&WebSocketClient::on_connect, this));
}
void WebSocketClient::on_connect(const boost::system::error_code& ec, const tcp::endpoint& ep)
{
if (ec) {
on_error(std::runtime_error("Connect failed: " + ec.message()));
return;
}
host_ += ':' + std::to_string(ep.port());
ws_->next_layer().async_handshake(ssl::stream_base::client,
beast::bind_front_handler(&WebSocketClient::on_ssl_handshake, this));
}
void WebSocketClient::on_ssl_handshake(const boost::system::error_code& ec)
{
if (ec) {
on_error(std::runtime_error("SSL handshake failed: " + ec.message()));
return;
}
beast::get_lowest_layer(*ws_).expires_never();
ws_->set_option(websocket::stream_base::decorator([](websocket::request_type& req) {
req.set(http::field::user_agent, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-async-ssl");
}));
ws_->read_message_max(MAX_READ_BUFFER);
ws_->write_buffer_bytes(MAX_WRITE_BUFFER);
ws_->async_handshake(host_, uri_, beast::bind_front_handler(&WebSocketClient::on_handshake, this));
}
void WebSocketClient::on_handshake(const boost::system::error_code& ec)
{
if (ec) {
on_error(std::runtime_error("Handshake failed: " + ec.message()));
return;
}
do_read();
}
void WebSocketClient::do_read()
{
ws_->async_read(buffer_, beast::bind_front_handler(&WebSocketClient::on_read, this));
}
void WebSocketClient::on_read(const boost::system::error_code& ec, std::size_t)
{
if (ec) {
if (ec == net::error::operation_aborted || ec == net::error::eof) {
return;
}
if (ec != websocket::error::closed) {
on_error(std::runtime_error("Read failed: " + ec.message()));
} else {
on_close(ws_->reason());
}
return;
}
do_read();
OnMessage(beast::buffers_to_string(buffer_.data()));
buffer_.consume(buffer_.size());
}
void WebSocketClient::on_write(beast::error_code ec, std::size_t bytes_transferred)
{
if (ec) {
on_error(std::runtime_error("Write failed: " + ec.message()));
{
std::lock_guard<std::mutex> lock(sendMutex_);
isWriting_ = false;
}
return;
}
net::post(io_context_, [this]() {
ProcessSendQueue();
});
}
void WebSocketClient::on_close(const websocket::close_reason& cr)
{
OnClose(cr);
StopHeartbeat();
io_context_.stop();
}
void WebSocketClient::on_error(const std::exception& e)
{
OnError(e);
StopHeartbeat();
io_context_.stop();
}