You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"
        "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\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);
        
        // 设置正确的
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 11:50:22