Websocket++ TLS客户端报错:必需的tls_init_handler未设置
问题描述
我用Websocket++开发客户端程序,参考zaphoyd官网教程实现,但教程未覆盖TLS客户端内容,自行修改代码实现了TLS客户端(代码见下方)。尝试连接wss://echo.websocket.events这类服务器时,始终收到日志错误「[fatal] Required tls_init handler not present.」,连接失败。曾尝试将处理器移入connection_metadata类,但遇到函数签名错误。已安装OpenSSL 1.1.1f,使用CMake构建并配置了find_package(OpenSSL REQUIRED),请问问题出在哪?
#include <cstdlib> #include <iostream> #include <map> #include <string> #include <sstream> #include <websocketpp/config/asio_client.hpp> #include <boost/asio.hpp> #include <boost/asio/ssl.hpp> #include <boost/asio/ssl/context.hpp> #include <websocketpp/client.hpp> #include <websocketpp/common/thread.hpp> #include <websocketpp/common/memory.hpp> typedef websocketpp::client<websocketpp::config::asio_tls_client> client; class connection_metadata { private: int m_id; websocketpp::connection_hdl m_hdl; std::string m_status; std::string m_uri; std::string m_server; std::string m_error_reason; public: typedef websocketpp::lib::shared_ptr<connection_metadata> ptr; connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri): m_id(id), m_hdl(hdl), m_status("Connecting"), m_uri(uri), m_server("N/A") {} int get_id(){return m_id;} websocketpp::connection_hdl get_hdl(){ return m_hdl;} std::string get_status(){return m_status;} void on_open(client * c, websocketpp::connection_hdl hdl){ m_status = "Open"; client::connection_ptr con = c->get_con_from_hdl(hdl); m_server = con->get_response_header("Server"); } void on_fail(client * c, websocketpp::connection_hdl hdl){ m_status = "Failed"; client::connection_ptr con = c->get_con_from_hdl(hdl); m_server = con->get_response_header("Server"); m_error_reason = con->get_ec().message(); } void on_close(client * c, websocketpp::connection_hdl hdl){ m_status = "Closed"; client::connection_ptr con = c->get_con_from_hdl(hdl); std::stringstream s; s << "Close code: " << con->get_remote_close_code() << "(" << websocketpp::close::status::get_string(con->get_remote_close_code()) << "), Close reason: " << con->get_remote_close_reason(); m_error_reason = s.str(); } friend std::ostream &operator<< (std::ostream &out, connection_metadata const &data); }; std::ostream &operator<< (std::ostream &out, connection_metadata const &data){ out << "> URI: " << data.m_uri << "\n" << "> Status: " << data.m_status << "\n" << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); return out; } class websocket_endpoint { private: typedef std::map<int,connection_metadata::ptr> con_list; client m_endpoint; websocketpp::lib::shared_ptr<websocketpp::lib::thread> m_thread; con_list m_connection_list; int m_next_id; public: websocket_endpoint(): m_next_id(0) { m_endpoint.set_access_channels(websocketpp::log::alevel::all); m_endpoint.set_error_channels(websocketpp::log::elevel::all); m_endpoint.init_asio(); m_endpoint.start_perpetual(); m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); } ~websocket_endpoint() { m_endpoint.stop_perpetual(); for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { if (it->second->get_status() != "Open") { continue; } std::cout << "> Closing connection " << it->second->get_id() << std::endl; websocketpp::lib::error_code ec; m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); if (ec) { std::cout << "> Error closing connection " << it->second->get_id() << ": " << ec.message() << std::endl; } } m_thread->join(); } int connect (std::string const &uri) { websocketpp::lib::error_code ec; client::connection_ptr con = m_endpoint.get_connection(uri, ec); if(ec){ std::cout << "Connection initialization error: " << ec.message() << std::endl; return -1; } int new_id = m_next_id++; connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); m_connection_list[new_id] = metadata_ptr; con->set_open_handler(websocketpp::lib::bind( &connection_metadata::on_open, metadata_ptr, &m_endpoint, websocketpp::lib::placeholders::_1 )); con->set_fail_handler(websocketpp::lib::bind( &connection_metadata::on_fail, metadata_ptr, &m_endpoint, websocketpp::lib::placeholders::_1 )); con->set_close_handler(websocketpp::lib::bind( &connection_metadata::on_close, metadata_ptr, &m_endpoint, websocketpp::lib::placeholders::_1 )); con->set_tls_init_handler([](websocketpp::connection_hdl){ boost::asio::ssl::context context(boost::asio::ssl::context::tlsv1); return websocketpp::lib::make_shared<boost::asio::ssl::context>(std::move(context)); }); m_endpoint.connect(con); return new_id; } connection_metadata::ptr get_metadata(int id) const { con_list::const_iterator metadata_it = m_connection_list.find(id); if (metadata_it == m_connection_list.end()) { return connection_metadata::ptr(); } else { return metadata_it->second; } } void close(int id, websocketpp::close::status::value code, std::string reason) { websocketpp::lib::error_code ec; con_list::iterator metadata_it = m_connection_list.find(id); if (metadata_it == m_connection_list.end()) { std::cout << "> No connection found with id " << id << std::endl; return; } m_endpoint.close(metadata_it->second->get_hdl(), code, "", ec); std::cout << "Connection closed with reason: " << reason << std::endl; } }; int main(){ bool done = false; std::string input; websocket_endpoint endpoint; while(!done) { std::cout << "Enter Command: "; std::getline(std::cin, input); if(input == "quit"){ done = true; } else if(input == "help"){ std::cout << "\nCommand List:\n" << "help: Displays this help text\n" << "quit: exits the program\n" << "connect <URI>: creates a connection with the given URI\n" << "close <id> <code (optional)> <reason (optional)>: closes the connection with the given id with optionally specifiable exit_code and/or reason\n" << "show <id>: Gets the metadata of the connection with the given id\n" << std::endl; } else if (input.substr(0,7) == "connect") { int id = endpoint.connect(input.substr(8)); if (id != -1) { std::cout << "> Created connection with id " << id << std::endl; } } else if (input.substr(0,4) == "show") { int id = atoi(input.substr(5).c_str()); connection_metadata::ptr metadata = endpoint.get_metadata(id); if (metadata) { std::cout << *metadata << std::endl; } else { std::cout << "> Unknown connection id " << id << std::endl; } } else if(input.substr(0, 5) == "close"){ std::stringstream ss(input); std::string cmd; int id; int close_code = websocketpp::close::status::normal; std::string reason; ss >> cmd >> id >> close_code; std::getline(ss, reason); endpoint.close(id, close_code, reason); } else{ std::cout << "Unrecognized command" << std::endl; } } return 0; }
问题原因与修复方案
核心问题
- TLS处理器设置时机错误:你在调用
m_endpoint.get_connection(uri, ec)后才设置tls_init_handler,但Websocket++在创建连接对象时就会检查TLS处理器是否存在,此时处理器尚未配置,直接触发致命错误。 - TLS协议版本过时:代码中指定了
tlsv1版本,这是已被淘汰的协议,现代WSS服务器大多不再支持,会导致握手失败。
修复步骤
1. 调整TLS处理器设置时机(全局设置推荐)
在websocket_endpoint构造函数中设置全局TLS初始化处理器,确保创建连接对象前TLS配置已就绪:
websocket_endpoint(): m_next_id(0) { m_endpoint.set_access_channels(websocketpp::log::alevel::all); m_endpoint.set_error_channels(websocketpp::log::elevel::all); // 设置全局TLS初始化处理器 m_endpoint.set_tls_init_handler([](websocketpp::connection_hdl) { // 使用TLSv1.2版本,兼容绝大多数现代服务器 boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); try { // 启用证书验证,避免安全风险 ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::single_dh_use); } catch (std::exception& e) { std::cout << "TLS context setup error: " << e.what() << std::endl; } return websocketpp::lib::make_shared<boost::asio::ssl::context>(std::move(ctx)); }); m_endpoint.init_asio(); m_endpoint.start_perpetual(); m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); }
2. 删除局部TLS处理器设置
移除connect函数中这段代码:
con->set_tls_init_handler([](websocketpp::connection_hdl){ boost::asio::ssl::context context(boost::asio::ssl::context::tlsv1); return websocketpp::lib::make_shared<boost::asio::ssl::context>(std::move(context)); });
3. (可选)单个连接定制TLS配置
如果需要为不同连接设置不同TLS参数,需在get_connection后立刻设置处理器,且必须在调用m_endpoint.connect(con)前完成。也可以用bind绑定connection_metadata类成员函数,正确签名示例:
在connection_metadata中添加成员函数:
websocketpp::lib::shared_ptr<boost::asio::ssl::context> on_tls_init(websocketpp::connection_hdl) { boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12); // 自定义配置... return websocketpp::lib::make_shared<boost::asio::ssl::context>(std::move(ctx)); }
在connect函数中绑定:
con->set_tls_init_handler(websocketpp::lib::bind( &connection_metadata::on_tls_init, metadata_ptr, websocketpp::lib::placeholders::_1 ));
内容的提问来源于stack exchange,提问作者insipidintegrator
相关产品推荐
相关产品推荐

