Reputation: 1490
I'm trying to connect to a secure websocket using websocketpp but I'm getting this weird error after the second tick of the timer:
[2019-12-05 10:48:55] [info] asio async_read_at_least error: asio.ssl:335544539 (short read)
[2019-12-05 10:48:55] [error] handle_read_frame error: websocketpp.transport:11 (Generic TLS related error)
[2019-12-05 10:48:55] [info] asio async_write error: asio.ssl:336396495 (protocol is shutdown)
[2019-12-05 10:48:55] [fatal] handle_write_frame error: websocketpp.transport:2 (Underlying Transport Error)
[2019-12-05 10:48:55] [info] asio async_shutdown error: asio.ssl:335544539 (short read)
close handler: Underlying Transport Error
[2019-12-05 10:48:55] [disconnect] Disconnect close local:[1006,Underlying Transport Error] remote:[1000]
My code is:
#define _WEBSOCKETPP_CPP11_STL_
# ifdef _WIN32
# pragma warning(disable: 4503)
# pragma warning(disable: 4996)
# endif
# include <websocketpp/config/asio_client.hpp>
# include <websocketpp/client.hpp>
# include <websocketpp/frame.hpp>
#undef _WEBSOCKETPP_CPP11_STL_
#include <iostream>
using WSClient = websocketpp::client<websocketpp::config::asio_tls_client>;
int main()
{
WSClient client;
client.init_asio();
client.set_tls_init_handler([](auto)
{
auto result = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23_client);
result->set_verify_mode(boost::asio::ssl::verify_none);
return result;
});
client.set_open_handler([&client](auto hdl)
{
client.set_timer(40000, [hdl, &client](auto)
{
if (auto con = client.get_con_from_hdl(hdl)) {
con->send(std::string(R"({"op":1,"d":1})"), websocketpp::frame::opcode::text);
}
});
});
client.set_close_handler([&client](auto hdl)
{
auto con = client.get_con_from_hdl(hdl);
std::cout << "close handler: " << con->get_ec().message() << std::endl;
});
client.set_fail_handler([&client](auto hdl)
{
auto con = client.get_con_from_hdl(hdl);
std::cout << "fail handler: " << con->get_ec().message() << std::endl;
});
websocketpp::lib::error_code ec;
const auto websocketUrl = "wss://gateway.discord.gg/?encoding=json&v=6";
auto con = client.get_connection(websocketUrl, ec);
if (ec) {
std::cout << "Could not create WebSocket connection because " << ec.message() << std::endl;
return 0;
}
client.connect(con);
client.run();
}
Surprising thing is that if I do the same thing using NodeJS it works fine using:
const WebSocket = require('ws');
let websocketUrl = 'wss://gateway.discord.gg/?encoding=json&v=6'
const ws = new WebSocket(websocketUrl);
ws.on('open', function() {
setInterval(() => {
console.log('ping');
ws.send('{"op":1,"d":1}');
}, 40000);
});
ws.on('error', function(e, v){
console.log('error', e, v);
});
ws.on('unexpected-response', function(e, t, v){
console.log('unexpected-response', e, t);
});
ws.on('close', function() {
console.log('connection closed');
});
What am I doing wrong in the C++ version ?
Env: Windows 10, MSVC 14, Websocketpp 0.8.1, Boost 1.69
Upvotes: 1
Views: 1933
Reputation: 301
According to the documentation [1], the client must send pings (the manual calls them heartbeats):
Heartbeat:
Used to maintain an active gateway connection. Must be sent every heartbeat_interval milliseconds after the Opcode 10 Hello payload is received. The inner d key is the last sequence number—s—received by the client. If you have not yet received one, send null.
You are using WSClient::set_timer() (in the set_open method) to send the ping messages in your C++ implementation. However, WSClient::set_timer() calls your ping function only once (you can check that with a printf or read the documentation of the method). Thus you are sending only one single ping message. Your connection is therefore killed after some time from the server.
In contrast, you are using "setIntervall()" in your NodeJS implementation to set a periodically timer. This timer is thus called periodically and the server is periodically receiving your ping messages.
I did the following to repair your C++ code [full code for copy-paste at the end of the answer]:
1.) Add a handler to read incoming messages for debugging:
client.set_message_handler([&client](auto hdl, auto msg_ptr)
{
std::string message = msg_ptr->get_payload();
std::cout << "received message: " << message << std::endl;
});
2.) Start the websocket in a non-blocking way:
auto run_thread = std::thread{[&client](){client.run();}};
while(! client_is_open) { //this variable is defined elsewhere, see full code
sleep(1); //TODO: use an mutex instead
}
3.) Do the pings:
int heartbeat_interval = 41250; //in ms: TODO extract from message
while(true) {
std::this_thread::sleep_for(std::chrono::milliseconds(heartbeat_interval));
client.send(con, "{\"op\":1, \"d\":null}", websocketpp::frame::opcode::text);
}
Please note the following:
You can find my code below
#define _WEBSOCKETPP_CPP11_STL_
# ifdef _WIN32
# pragma warning(disable: 4503)
# pragma warning(disable: 4996)
# endif
# define _WEBSOCKETPP_CPP11_STL_
# include <websocketpp/config/asio_client.hpp>
# include <websocketpp/client.hpp>
# include <websocketpp/frame.hpp>
#undef _WEBSOCKETPP_CPP11_STL_
#include <iostream>
using WSClient = websocketpp::client<websocketpp::config::asio_tls_client>;;
int main() { WSClient client;
client.init_asio();
bool client_is_open = false;
client.set_tls_init_handler([](auto)
{
auto result = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23_client);
result->set_verify_mode(boost::asio::ssl::verify_none);
return result;
});
client.set_open_handler([&client,&client_is_open](auto hdl)
{
client_is_open=true;
});
client.set_close_handler([&client](auto hdl)
{
auto con = client.get_con_from_hdl(hdl);
std::cout << "close handler: " << con->get_ec().message() << std::endl;
});
client.set_fail_handler([&client](auto hdl)
{
auto con = client.get_con_from_hdl(hdl);
std::cout << "fail handler: " << con->get_ec().message() << std::endl;
});
client.set_message_handler([&client](auto hdl, auto msg_ptr)
{
std::string message = msg_ptr->get_payload();
std::cout << "received message: " << message << std::endl;
});
websocketpp::lib::error_code ec;
const auto websocketUrl = "wss://gateway.discord.gg/?encoding=json&v=6";
auto con = client.get_connection(websocketUrl, ec);
if (ec) {
std::cout << "Could not create WebSocket connection because " << ec.message() << std::endl;
return 0;
}
client.connect(con);
auto run_thread = std::thread{[&client](){client.run();}};
while(! client_is_open) {
sleep(1); //TODO: use an mutex instead
}
int heartbeat_interval = 41250; //in ms: TODO extract from message
while(true) {
std::this_thread::sleep_for(std::chrono::milliseconds(heartbeat_interval));
client.send(con, "{\"op\":1, \"d\":null}", websocketpp::frame::opcode::text);
}
run_thread.join();
}
Upvotes: 1