读取:版本号错误(SSL 例程,ssl3_get_record)

read: wrong version number (SSL routines, ssl3_get_record)

binance-http.hpp

#pragma once

#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/json/src.hpp>
#include <boost/json.hpp>
#include <iostream>
#include <string>
#include <exception>


namespace beast = boost::beast;         
namespace http  = beast::http;          
namespace net   = boost::asio;          
namespace ssl   = boost::asio::ssl;     
using tcp       = boost::asio::ip::tcp; 

using executor = net::any_io_executor; 
using namespace boost::json;



boost::url make_url(boost::url_view base_api, boost::url_view method) {
    assert(!method.is_path_absolute());
    assert(base_api.data()[base_api.size() - 1] == '/');

    boost::urls::error_code ec;
    boost::url url;
    resolve(base_api, method, url, ec);
    if (ec)
        throw boost::system::system_error(ec);

    return url;
}
void fail_http(beast::error_code ec, char const* what);


namespace binapi{


    enum operation {synchronous,asynchronous};

    namespace rest{

        class httpClient : public std::enable_shared_from_this<httpClient> 
        {

        public:    
            tcp::resolver resolver_;
            beast::ssl_stream<beast::tcp_stream> stream_;
            beast::flat_buffer buffer_; // (Must persist between reads)
            http::request<http::string_body>  req_;
            http::response<http::string_body> res_;
            std::string API_KEY = "v6uhUtse5Ae1Gyz72eMSbUMGw7VUDdd5AnqobMOW1Llzi4snnfP4YCyY9V74PFJ4";
            std::string secret_key = "FW8j4YobD26PVP6QLu0sv4Dv7OzrtfgQKzn8FoIMwGzMW9Y0VmX1DatbLIfXoCHV";
            std::string BASE_URL = "https://testnet.binance.vision/api/v3/";
            net::io_context ioc;
            value json;
            

        public:

            httpClient(executor ex, ssl::context& ctx);

            ssl::context ctxx{ssl::context::tlsv12_client};

            void http_call(boost::url url, http::verb action, operation o);

            void on_resolve(beast::error_code ec, tcp::resolver::results_type results);

            void on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type);

            void on_handshake(beast::error_code ec);

            void on_write(beast::error_code ec, std::size_t bytes_transferred);

            void on_read(beast::error_code ec, std::size_t bytes_transferred);

            void on_shutdown(beast::error_code ec);

            void server_time(operation oper);

            void avg_price(std::string symbol, operation oper);


        };
    }
}


namespace binapi
{
    
    namespace rest
    {

        // Report a failure
        void fail_http(beast::error_code ec, char const* what)
        {
            std::cerr << what << ": " << ec.message() << "\n";
        }

        httpClient::httpClient(executor ex, ssl::context& ctx)
            : resolver_(ex)
            , stream_(ex, ctx) {}


        // Start the asynchronous operation
        void httpClient::http_call(boost::url url, http::verb action, operation o) 
        {

            std::string const host(url.host());
            std::string const service = url.has_port() //
                ? url.port()
                : (url.scheme_id() == boost::urls::scheme::https) //
                    ? "https"
                    : "http";
            url.remove_origin(); // becomes req_.target()

            // Set SNI Hostname (many hosts need this to handshake successfully)
            if(! SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()))
            {
                beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
                std::cerr << ec.message() << "\n";
                return;
            }

            // Set up an HTTP GET/POST/DELETE/PUT request message
            // req_.version(version);
            req_.method(action);
            req_.target(url.c_str());
            req_.set(http::field::host, host);
            req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
            req_.set("X-MBX-APIKEY", API_KEY);
            //req_.body() = serialize(json::object {{"symbol", "btcusdt"}});
            req_.prepare_payload(); // make HTTP 1.1 compliant

            if(o==asynchronous){
                resolver_.async_resolve(host, service,beast::bind_front_handler(&httpClient::on_resolve,shared_from_this()));
            }
            else
            {
                auto const results = resolver_.resolve(host, service);
                beast::get_lowest_layer(stream_).connect(results);

                // Perform the SSL handshake
                stream_.handshake(ssl::stream_base::client);
                http::write(stream_, req_);

                // Receive the HTTP response    
                this->buffer_.clear();
                this->res_.clear();
                http::read(stream_, buffer_, res_);

            }
        }

        void httpClient::on_resolve(beast::error_code ec, tcp::resolver::results_type results)
        {
            if(ec)
                return fail_http(ec, "resolve");            

            // Set a timeout on the operation
            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));

            // Make the connection on the IP address we get from a lookup
            beast::get_lowest_layer(stream_).async_connect(results,beast::bind_front_handler(&httpClient::on_connect,shared_from_this()));
        }

        void httpClient::on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
        {
            if(ec)
                return fail_http(ec, "connect");

            // Perform the SSL handshake
            stream_.async_handshake(ssl::stream_base::client,beast::bind_front_handler(&httpClient::on_handshake,shared_from_this()));
        }

        void httpClient::on_handshake(beast::error_code ec)
        {
            if(ec)
                return fail_http(ec, "handshake");

            // Set a timeout on the operation
            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));

            // Send the HTTP request to the remote host
            std::cout << "Sending " << req_ << std::endl;
            
            http::async_write(stream_, req_, beast::bind_front_handler(&httpClient::on_write, shared_from_this()));
            
        }

        void httpClient::on_write(beast::error_code ec, std::size_t bytes_transferred)
        {
            boost::ignore_unused(bytes_transferred);

            if(ec)
                return fail_http(ec, "write"); 

            // Receive the HTTP response
            this->buffer_.clear();
            this->res_.clear();
            http::async_read(stream_, buffer_, res_, beast::bind_front_handler(&httpClient::on_read,shared_from_this()));


        }

        void httpClient::on_read(beast::error_code ec, std::size_t bytes_transferred)
        {
            boost::ignore_unused(bytes_transferred);

            if(ec)
                return fail_http(ec, "read"); 

            // Set a timeout on the operation
            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));

            // Gracefully close the stream
            stream_.async_shutdown(beast::bind_front_handler(&httpClient::on_shutdown,shared_from_this()));
        }

        void httpClient::on_shutdown(beast::error_code ec)
        {
            if(ec == net::error::eof)
            {
                ec = {};
            }
            if(ec)
                return fail_http(ec, "shutdown");

        }

        void httpClient::avg_price(std::string symbol,operation oper)
        {
            this->server_time(operation::synchronous);
            static boost::url_view const base_api{"https://testnet.binance.vision/api/v3/"};
            boost::url method{"avgPrice"};
            method.params().emplace_back("symbol",symbol);
            this->http_call(make_url(base_api,method),http::verb::get, oper);
        }
    }
}

这是我的 main.cpp 样子 :

#include <iostream>
#include <ctime>
#include "boost/url/src.hpp" // can only be included in one source file
#include "binance-ws.hpp"
#include "binance-http.hpp" 

using namespace binapi;

int main()
{
    net::io_context ioc;
    // The SSL context is required, and holds certificates
    ssl::context ctx{ssl::context::tlsv12_client};

    // Verify the remote server's certificate
    ctx.set_verify_mode(ssl::verify_peer);
    ctx.set_default_verify_paths();

    auto httpclients = std::make_shared<rest::httpClient>(ioc.get_executor(),ctx);

    httpclients->avg_price("BTCUSDT",operation::asynchronous);


    ioc.run();
}

错误:read: wrong version number (SSL routines, ssl3_get_record) 我认为它来自 async_read(),所以我清除了所有 buffer_res_ 但仍然不知道为什么它会再次发生。

我还发现,如果我删除这个:this->server_time() avg_price() 函数然后它工作正常。 我不知道这里到底发生了什么,请帮助并提前谢谢!

我们无法真正看到,因为代码不是 self-contained。不过,我确实注意到,使用 给出的代码,我无法重现该问题。事实上它运行 ubsan/asan 干净。

主要区别在于 this->server_time 被跳过(因为您没有提供它的实现)。所以关于 运行 两个连续请求的问题。

确实,只是重复调用:

this->http_call(make_url(base_api, method), http::verb::get, oper);
this->http_call(make_url(base_api, method), http::verb::get, oper);

确实会导致问题。

然后,当您阅读 http_call 时,问题似乎很明显。

你永远不会关闭流,但是,你第二次使用相同的流,并对其执行 SNI+握手。那是行不通的。

一个简单的 hack 是重置流。但是,如果您计划在每次调用时都进行完全重新连接,为什么甚至 (a) 具有异步模式 (b) 具有 httpClient class。您可能希望在调用之间保持连接打开,除非发生错误。