Boost SSL WebSocket连接Binance时握手被拒问题求助
问题描述
尝试用Boost实现与Binance的SSL WebSocket连接,握手时持续报错:
Error: The WebSocket handshake was declined by the remote peer
原本代码中load_root_certificates函数提示未定义,遂移除,看到有帖子称无需手动加载证书,系统会使用默认证书,不确定该说法是否正确。相关代码如下:
#include <boost/asio/connect.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/ssl/stream.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/json_parser.hpp> #include <boost/beast/core/ostream.hpp> #include <string> #include <utility> #include <boost/beast/core.hpp> #include <boost/beast/websocket.hpp> #include <boost/beast/ssl.hpp> #include <boost/beast/websocket/ssl.hpp> #include <iostream> #include "JSONParser.hpp" #include "data.hpp" #define ONEHOUR_ONEMONTH 672 #define ONEMIN_ONEWEEK 10080 #define ONESEC_ONEDAY 86400 std::string create_subscription_message() { boost::property_tree::ptree message; message.put("method", "SUBSCRIBE"); std::vector<std::string> streams = {"btcusdt@kline_1m"}; boost::property_tree::ptree params; for(auto& stream: streams) params.push_back(std::make_pair("", boost::property_tree::ptree(stream))); message.add_child("params", params); message.put("id", 1); std::stringstream ss; boost::property_tree::write_json(ss, message); return ss.str(); } namespace beast = boost::beast; // from <boost/beast.hpp> namespace http = beast::http; // from <boost/beast/http.hpp> namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp> namespace net = boost::asio; // from <boost/asio.hpp> namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp> using tcp = boost::asio::ip::tcp; int main() { try { cData candlesticks(ONEMIN_ONEWEEK); std::string s = "{\n \"e\": \"kline\",\n \"E\": 123456789,\n \"s\": \"BNBBTC\",\n \"k\": {\n \"t\": 123400000,\n \"T\": 123460000,\n \"s\": \"BNBBTC\",\n \"i\": \"1m\",\n \"f\": 100,\n \"L\": 200,\n \"o\": \"0.0010\",\n \"c\": \"0.0020\",\n \"h\": \"0.0025\",\n \"l\": \"0.0015\",\n \"v\": \"1000\",\n \"n\": 100,\n \"x\": false,\n \"q\": \"1.0000\",\n \"V\": \"500\",\n \"Q\": \"0.500\",\n \"B\": \"123456\"\n }\n}"; candlesticks.addCandlestick(s); candlesticks.printCandlestick(candlesticks.accessDataAtIndex(0)); // WebSocket endpoint std::string host = "wss://stream.binance.com"; std::string port = "443"; // Create the I/O context boost::asio::io_context ioc; // Creates SSL context and holds certificate ssl::context ctx{ssl::context::tlsv12_client}; tcp::resolver resolver(ioc); // Create the WebSocket stream websocket::stream<beast::ssl_stream<tcp::socket>> ws{ioc, ctx}; // Resolve the hostname auto endpoints = resolver.resolve(host, port); // Connect to the first endpoint in the list auto ep = net::connect(get_lowest_layer(ws), endpoints); if(! SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str())) throw beast::system_error( beast::error_code( static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()), "Failed to set SNI Hostname"); host += ':' + std::to_string(ep.port()); ws.next_layer().handshake(ssl::stream_base::client); 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-coro"); })); boost::beast::error_code ec; ws.handshake(host, "/ws/ethusdt@kline_5m", ec); if(ec) { std::cerr << "Error: " << ec.message() << std::endl; return EXIT_FAILURE; } //subscription message std::string subscription_message = create_subscription_message(); // Send the subscription message ws.write(boost::asio::buffer(subscription_message)); // Receive messages for (;;) { boost::beast::multi_buffer buffer; ws.read(buffer); std::cout << boost::beast::make_printable(buffer.data()) << std::endl; if (buffer.size() == 0) { break; } auto message = boost::beast::buffers_to_string(buffer.data()); if (message == "ping") { buffer.consume(buffer.size()); ws.write(boost::asio::buffer("pong")); } } } catch (std::exception const& e) { std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } }
问题解决
1. 握手失败的核心原因
代码存在两个关键错误:
- 主机名格式错误:初始化
host时使用了完整的wss://stream.binance.com,但DNS解析和SNI设置只需要纯主机名stream.binance.com,不能带协议前缀。 - 路径与订阅不匹配:握手时使用
/ws/ethusdt@kline_5m,但后续订阅的是btcusdt@kline_1m,且主机名错误已经先触发了握手拒绝。
2. 证书加载的正确方式
Boost Asio的SSL上下文不会自动加载系统默认根证书,必须手动配置。移除load_root_certificates后,SSL握手缺少可信证书链,大概率会导致验证失败,只是被WebSocket握手错误掩盖。可以通过两种方式解决:
- 从系统证书文件读取(如Linux的
/etc/ssl/certs/ca-certificates.crt) - 使用Boost Beast示例中的
load_root_certificates函数加载根证书链
修复后的完整代码
#include <boost/asio/connect.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/ssl/stream.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/json_parser.hpp> #include <boost/beast/core/ostream.hpp> #include <string> #include <utility> #include <boost/beast/core.hpp> #include <boost/beast/websocket.hpp> #include <boost/beast/ssl.hpp> #include <boost/beast/websocket/ssl.hpp> #include <iostream> #include "JSONParser.hpp" #include "data.hpp" #define ONEHOUR_ONEMONTH 672 #define ONEMIN_ONEWEEK 10080 #define ONESEC_ONEDAY 86400 std::string create_subscription_message() { boost::property_tree::ptree message; message.put("method", "SUBSCRIBE"); std::vector<std::string> streams = {"btcusdt@kline_1m"}; boost::property_tree::ptree params; for(auto& stream: streams) params.push_back(std::make_pair("", boost::property_tree::ptree(stream))); message.add_child("params", params); message.put("id", 1); std::stringstream ss; boost::property_tree::write_json(ss, message); return ss.str(); } namespace beast = boost::beast; namespace http = beast::http; namespace websocket = beast::websocket; namespace net = boost::asio; namespace ssl = boost::asio::ssl; using tcp = boost::asio::ip::tcp; // 加载根证书,可从Boost Beast示例获取完整证书链 void load_root_certificates(ssl::context& ctx) { std::string const cert = "-----BEGIN CERTIFICATE-----\n" "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" "-----END CERTIFICATE-----\n"; ctx.add_certificate_authority(boost::asio::buffer(cert.data(), cert.size())); } int main() { try { cData candlesticks(ONEMIN_ONEWEEK); std::string s = "{\n \"e\": \"kline\",\n \"E\": 123456789,\n \"s\": \"BNBBTC\",\n \"k\": {\n \"t\": 123400000,\n \"T\": 123460000,\n \"s\": \"BNBBTC\",\n \"i\": \"1m\",\n \"f\": 100,\n \"L\": 200,\n \"o\": \"0.0010\",\n \"c\": \"0.0020\",\n \"h\": \"0.0025\",\n \"l\": \"0.0015\",\n \"v\": \"1000\",\n \"n\": 100,\n \"x\": false,\n \"q\": \"1.0000\",\n \"V\": \"500\",\n \"Q\": \"0.500\",\n \"B\": \"123456\"\n }\n}"; candlesticks.addCandlestick(s); candlesticks.printCandlestick(candlesticks.accessDataAtIndex(0)); // 修正主机名,去掉协议前缀 std::string host = "stream.binance.com"; std::string port = "443"; boost::asio::io_context ioc; ssl::context ctx{ssl::context::tlsv12_client}; // 加载根证书 load_root_certificates(ctx); tcp::resolver resolver(ioc); websocket::stream<beast::ssl_stream<tcp::socket>> ws{ioc, ctx}; auto endpoints = resolver.resolve(host, port); auto ep = net::connect(get_lowest_layer(ws), endpoints); // 设置正确的
相关产品推荐
相关产品推荐

