在 websocket 计时器打勾后提升 asio 短读取错误
Boost asio short read error after websocket timer tick
我正在尝试使用 websocketpp 连接到安全的 websocket,但在计时器第二次滴答后我收到了这个奇怪的错误:
[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]
我的代码是:
#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();
}
令人惊讶的是,如果我使用 NodeJS 做同样的事情,它可以正常使用:
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');
});
我在 C++ 版本中做错了什么?
环境:Windows10,MSVC 14,Websocketpp 0.8.1,Boost 1.69
根据文档 [1],客户端必须发送 ping(手册称之为心跳):
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.
您正在使用 WSClient::set_timer()(在 set_open 方法中)在您的 C++ 实现中发送 ping 消息。但是,WSClient::set_timer() 只调用您的 ping 函数一次(您可以使用 printf 检查或阅读该方法的文档)。因此,您只发送一个 ping 消息。因此,您的连接会在一段时间后从服务器断开。
相比之下,您在 NodeJS 实现中使用“setIntervall()”来设置周期性计时器。因此,此计时器会定期调用,服务器会定期接收您的 ping 消息。
我做了以下修复你的 C++ 代码 [答案末尾复制粘贴的完整代码]:
1.) 添加处理程序以读取传入消息以进行调试:
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.) 以非阻塞方式启动websocket:
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.) 执行 ping:
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);
}
请注意以下事项:
- 您必须从第一条消息中提取 ping 间隔。 (我没有)
- ping 消息应发送最后一次 ping 的序列号(请参阅相关文档)。我没有,它仍然有效,但不要指望它。
- client.run() 是一个阻塞调用,有一些问题。
你可以在下面找到我的代码
#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();
}
我正在尝试使用 websocketpp 连接到安全的 websocket,但在计时器第二次滴答后我收到了这个奇怪的错误:
[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]
我的代码是:
#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();
}
令人惊讶的是,如果我使用 NodeJS 做同样的事情,它可以正常使用:
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');
});
我在 C++ 版本中做错了什么?
环境:Windows10,MSVC 14,Websocketpp 0.8.1,Boost 1.69
根据文档 [1],客户端必须发送 ping(手册称之为心跳):
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.
您正在使用 WSClient::set_timer()(在 set_open 方法中)在您的 C++ 实现中发送 ping 消息。但是,WSClient::set_timer() 只调用您的 ping 函数一次(您可以使用 printf 检查或阅读该方法的文档)。因此,您只发送一个 ping 消息。因此,您的连接会在一段时间后从服务器断开。
相比之下,您在 NodeJS 实现中使用“setIntervall()”来设置周期性计时器。因此,此计时器会定期调用,服务器会定期接收您的 ping 消息。
我做了以下修复你的 C++ 代码 [答案末尾复制粘贴的完整代码]:
1.) 添加处理程序以读取传入消息以进行调试:
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.) 以非阻塞方式启动websocket:
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.) 执行 ping:
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);
}
请注意以下事项:
- 您必须从第一条消息中提取 ping 间隔。 (我没有)
- ping 消息应发送最后一次 ping 的序列号(请参阅相关文档)。我没有,它仍然有效,但不要指望它。
- client.run() 是一个阻塞调用,有一些问题。
你可以在下面找到我的代码
#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();
}