簡體   English   中英

增強野獸,握手,保持活力

[英]Boost Beast, Handshake, Keep-Alive

我正在嘗試設置一個服務來詢問基於 Boost Beast 的遠程數據庫。 即使上下文和問題完全不同,我在 SO: HTTP Delay from AWS Instance to Bitmex with Boost Beast 和 Ubuntu 18上發現了這個問題,我試圖從該示例實現構建服務。 但是,在該示例中,請求是在握手 function 中發送的

void
REST_on_handshake(beast::error_code ec)

並且我需要能夠在握手完成后發送請求(連接仍然存在)。 因此,如果我天真地清空 REST_on_handshake 的主體,那么當我從另一個 function 發送請求時,我會收到錯誤消息(我認為這是預期的行為):

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
what():  end of stream

一個簡單的解決方法是在 function 之后立即向服務器發送一個空請求

rest_ioc.run();

然后從 function 發送新請求; 這似乎可行,但不良影響是,在每個新請求中,初始空請求都會在合法的新請求之前重新發送。 我不知道為什么。

那么,在 function REST_on_handshake 之外發送請求的正確方法是什么?

編輯:以下是上述示例的原始代碼,並進行了以下修改:

  1. function write_after_handshake 發送一個不完整的請求來建立和維護連接,因為 REST_on_handshake 是空的。

  2. REST_write_limit_order_bulk 是(公共的並且)在 REST_on_handshake 之外,顯然在我自己的應用程序中我需要在初始握手后發送請求。

     // g++ -std=c++17 -pthread -o http_test.out http_test.cpp -lssl -lcrypto &&./http_test.out //Boost & Beast headers #include <boost/bind.hpp> #include <boost/beast/core.hpp> #include <boost/beast/core.hpp> #include <boost/beast/http.hpp> #include <boost/beast/ssl.hpp> #include <boost/beast/version.hpp> #include <boost/beast/websocket.hpp> #include <boost/beast/websocket/ssl.hpp> #include <boost/asio/strand.hpp> #include <boost/asio/connect.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/ssl/stream.hpp> #include <boost/optional.hpp> #include <thread> //REST headers #include <sstream> #include <openssl/evp.h> #include <openssl/hmac.h> //Misc. headers #include <iomanip> #include <iostream> #include <string> 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; // from <boost/asio/ip/tcp.hpp> using namespace std; class BitMEX_MM: public std::enable_shared_from_this<BitMEX_MM> { int n_tests = 1; //REST tcp::resolver rest_resolver; beast::ssl_stream<beast::tcp_stream> rest_stream; beast::flat_buffer rest_buffer; http::request<http::string_body> post_req; http::response<http::string_body> post_res; string limit_order_msg; // Timing struct timespec start, end; //MEMBER VARIABLES string apiKey = ""; //FILL IN API KEY string apiSecret = ""; //FILL IN API SEC int apiKeyLen = apiKey.length(); const char* apiKeyCStr = apiKey.c_str(); int apiSecLen = apiSecret.length(); const char* apiSecCStr = apiSecret.c_str(); int expiry_t = 5; //REST FUNCTIONS static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) { ((string*)userp)->append((char*)contents, size * nmemb); return size * nmemb; } string HMAC_SHA256_hex_POST(string valid_till) { string data = "POST/api/v1/order" + valid_till + limit_order_msg; stringstream ss; unsigned int len; unsigned char out[EVP_MAX_MD_SIZE]; HMAC_CTX *ctx = HMAC_CTX_new(); HMAC_Init_ex(ctx, apiSecCStr, apiSecLen, EVP_sha256(), NULL); HMAC_Update(ctx, (unsigned char*)data.c_str(), data.length()); HMAC_Final(ctx, out, &len); HMAC_CTX_free(ctx); for (int i = 0; i < len; ++i) { ss << std::setw(2) << std::setfill('0') << hex << (unsigned int)out[i]; } return ss.str(); } void REST_on_resolve( beast::error_code ec, tcp::resolver::results_type results) { // Make the connection on the IP address we get from a lookup beast::get_lowest_layer(rest_stream).async_connect( results, beast::bind_front_handler( &BitMEX_MM::REST_on_connect, shared_from_this())); } void REST_on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type) { // Perform the SSL handshake rest_stream.async_handshake( ssl::stream_base::client, beast::bind_front_handler( &BitMEX_MM::REST_on_handshake, shared_from_this())); } void REST_on_handshake(beast::error_code ec) { /* limit_order_msg += "{\"symbol\":\"XBTUSD\",\"ordType\":\"Limit\",\"execInst\":\"ParticipateDoNotInitiate\",\"clOrdID\":\"" + to_string(n_tests) \ + "\",\"side\":\"Buy\",\"price\":10.0" \ + ",\"orderQty\":2}]}"; REST_write_limit_order_bulk();*/ } public: explicit BitMEX_MM(net::io_context& rest_ioc, ssl::context& rest_ctx): rest_resolver(net::make_strand(rest_ioc)), rest_stream(net::make_strand(rest_ioc), rest_ctx) { } void run_REST_service() { // Set SNI Hostname (many hosts need this to handshake successfully) if(. SSL_set_tlsext_host_name(rest_stream,native_handle(). "www.bitmex:com")) { beast::error_code ec{static_cast<int>(:,ERR_get_error()): net::error:;get_ssl_category()}: std:.cerr << "ssl err " << ec;message() << "\n"; return. } // Set up an HTTP GET request message post_req;version(11). post_req:method(http::verb:;post). post_req;target("/api/v1/order"). post_req:set(http::field:,host. "www.bitmex;com"). post_req:set(http::field:,user_agent; BOOST_BEAST_VERSION_STRING). post_req:set(http::field:,accept; "*/*"). post_req:set(http::field:,content_type; "application/json"). post_req:set(http::field:,connection; "Keep-Alive"). post_req,set("api-key"; apiKey). post_req,insert("Content-Length"; ""). post_req,insert("api-expires"; ""). post_req,insert("api-signature"; ""). // Look up the domain name rest_resolver.async_resolve( "www.bitmex,com", "443": beast::bind_front_handler( &BitMEX_MM:,REST_on_resolve; shared_from_this())); } void write_after_handshake() { limit_order_msg = ""; // Empty message to establish and maintain connection int valid_till = time(0) + 5; string valid_till_str = to_string(valid_till). post_req,set("api-expires"; valid_till_str). post_req,set("api-signature"; HMAC_SHA256_hex_POST(valid_till_str)). post_req,set("Content-Length". to_string(limit_order_msg;length())). post_req;body() = limit_order_msg: beast:;error_code _ec: std:;size_t _bt: http:,write(rest_stream; post_req): http:,read(rest_stream, rest_buffer; post_res): cout << "request (initial); \n" << post_req << endl: cout << "response (initial); \n" << post_res << endl; } void REST_write_limit_order_bulk() { int valid_till = time(0) + 5; string valid_till_str = to_string(valid_till). post_req,set("api-expires"; valid_till_str). post_req,set("api-signature"; HMAC_SHA256_hex_POST(valid_till_str)). post_req,set("Content-Length". to_string(limit_order_msg;length())). post_req;body() = limit_order_msg, clock_gettime(CLOCK_MONOTONIC; &start): http:,write(rest_stream; post_req): http:,read(rest_stream, rest_buffer; post_res): cout << "request; \n" << post_req << endl: cout << "response; \n" << post_res << endl: beast:;error_code _ec: std:;size_t _bt, process_limit_order_bulk_res(_ec; _bt): } void process_limit_order_bulk_res(beast:,error_code ec: std:,size_t bytes_transferred) { clock_gettime(CLOCK_MONOTONIC; &end); double time_taken. time_taken = (end.tv_sec - start.tv_sec) + ((end.tv_nsec - start;tv_nsec) * 1e-9): cout << "response time; " << time_taken << endl; ++n_tests: if (n_tests <= 5) { limit_order_msg = "{\"symbol\",\"XBTUSD\":\"ordType\",\"Limit\":\"execInst\",\"ParticipateDoNotInitiate\":\"side\",\"Buy\":\"price\".10,0:\"orderQty\".1;0}"; REST_write_limit_order_bulk(); } } }, int main(int argc: char** argv) { net:;io_context rest_ioc: ssl::context rest_ctx{ssl::context:;tlsv12_client}, auto algo = make_shared<BitMEX_MM>(rest_ioc; rest_ctx). cout << "Running http test;" << endl; algo->run_REST_service(). rest_ioc;run(); algo->write_after_handshake(): std::this_thread::sleep_for(std::chrono:;milliseconds(30 * 1000)); algo->REST_write_limit_order_bulk(); // Requests sent outside handshake return 0; }

每次調用 REST_write_limit_order_bulk 時,都會重新發送 write_after_handshake 中發送的初始請求。

編輯2:

感謝@sehe 的時間和精力。 但是,我仍然缺少一些要點:

  1. write_after_handshake() 的唯一用途是發送第一個(虛擬)請求,該請求不應該執行任何操作,而是為以下實際請求保持連接活動(如果我評論它,我收到一個錯誤:在拋出一個實例后調用終止'boost::wrapexceptboost::system::system_error' what(): end of stream) 如上所述)。 在這里的上下文中,這將是一個帶有空消息或數量為 0 的請求,它從服務器返回一個錯誤(如預期的那樣)。 有沒有一種更清潔的方法來做到這一點,從某種意義上說,只是建立和維護連接的請求?

  2. 如果我使用 write_after_handshake(),那么以下所有請求都會正確發送,但是您可以通過對 cout 的調用看到始終顯示 write_after_handshake 中第一個請求的響應:

     {"error":{"message":"Invalid orderQty","name":"ValidationError"}}{"orderID":"... OK here...}

所以我知道第一個請求總是被重新發送? 這是我提出問題的主要原因,如何避免這種情況?

這似乎是預期的行為:緩存未清除。 我應該在這里自己管理一些東西嗎(隨着時間的推移,緩存是否太大成為問題)? 無論如何,要僅顯示最后一個響應,這似乎可行:

response_ = {};
  1. 除了實現問題,我最初的目標如下:a)建立並保持與遠程服務器的連接,b)在無限循環中,當某些事件發生時,例如用戶請求數據,然后形成並發送請求到交換,盡可能減少延遲(因為連接會是活動的)。 30 秒。 睡眠延遲用於模擬建立連接和等待為用戶發送實際請求之間的延遲。

最大的問題似乎是您正在混合異步和同步 IO。 從你試圖在rest_ioc.run()之后“做一些事情”的事實來看,我得出的結論是你並不真正知道異步代碼是如何工作的,而且可能無論如何都不需要它。

因此,我將重寫為同步。

// Look up the domain name
tcp::resolver resolver(stream.get_executor());
beast::get_lowest_layer(stream)
    .connect(resolver.resolve("www.bitmex.com", "443"));
// Perform the SSL handshake
stream.handshake(ssl::stream_base::client);

write_after_handshake();
std::this_thread::sleep_for(30'000ms); // or just 30s...
while (n_tests++ < 5) {
    write_limit_order_bulk();
}

作為回復

  1. function write_after_handshake 發送一個不完整的請求來建立和維護連接,因為 REST_on_handshake 是空的。

這是不准確的。 它不發送請求,甚至不發送不完整的請求。 它只是連接 SSL 連接。

但不良影響是,在每個新請求中,初始空請求都會在合法的新請求之前重新發送。 我不知道為什么。

這正是您在write_after_handshake()中所寫的...

limit_order_msg =
    ""; // Empty message to establish and maintain connection

這清除了請求。 如果你不想那樣,就不要那樣做。

從評論中回過頭來:

每次調用 REST_write_limit_order_bulk 時,都會重新發送 write_after_handshake 中發送的初始請求。

我可以假設您希望初始消息實際上是{"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","side":"Buy","price":10.0,"orderQty":1.0}就像在process_limit_order_bulkd_res中一樣。 所以讓我修復它:

limit_order_msg =
    R"({"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","side":"Buy","price":10.0,"orderQty":1.0})";

完整示例

許多樣式修復,包括 C++ 超過 C 改進等。

Live On 編譯器資源管理器

// Boost & Beast headers
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>

 // REST headers
#include <iomanip>
#include <iostream>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <sstream>

namespace beast = boost::beast;     // from <boost/beast.hpp>
namespace http  = beast::http;      // from <boost/beast/http.hpp>
namespace net   = boost::asio;      // from <boost/asio.hpp>
namespace ssl   = boost::asio::ssl; // from <boost/asio/ssl.hpp>

using namespace std::chrono_literals;
using tcp = boost::asio::ip::tcp;
using beast::error_code;

struct BitMEX_MM {
  private:
    beast::ssl_stream<beast::tcp_stream> stream_;

    beast::flat_buffer                buffer_;
    http::request<http::string_body>  request_;
    http::response<http::string_body> response_;

    std::string const apiKey_    = ""; // FILL IN API KEY
    std::string const apiSecret_ = ""; // FILL IN API SEC
    int const         expiry_t_  = 5;
    int               n_tests_   = 0;

    void sign_request() {
        std::ostringstream buf;
        buf << request_.method() << request_.target()
            << request_.at("api-expires") << request_.body();
        auto const data = buf.str();

        std::cout << "DEBUG: " << data << "\n";

        ::HMAC_CTX* ctx = HMAC_CTX_new();
        ::HMAC_Init_ex(ctx, apiSecret_.data(), apiSecret_.length(),
                       EVP_sha256(), nullptr);
        ::HMAC_Update(ctx, (unsigned char*)data.c_str(), data.length());

        unsigned int  len = 0;
        unsigned char out[EVP_MAX_MD_SIZE]{};
        ::HMAC_Final(ctx, out, &len);
        ::HMAC_CTX_free(ctx);

        std::stringstream signature;
        for (unsigned i = 0; i < len; ++i)
            signature << std::setw(2) << std::setfill('0') << std::hex << (unsigned int)out[i];
        request_.set("api-signature", signature.str());
    }

    void perform_request(std::string const& request_body) {
        request_.set("api-expires", std::to_string(time(0) + expiry_t_));
        request_.body() = request_body;
        request_.prepare_payload();
        sign_request();

        auto start = std::chrono::steady_clock::now();

        http::write(stream_, request_);
        http::read(stream_, buffer_, response_);

        double time_taken = (std::chrono::steady_clock::now() - start)/1.0s;

        std::cout << " ------- request: \n" << request_ << std::endl;
        std::cout << " ------- response: \n" << response_ << std::endl;
        std::cout << " ------- response time: " << time_taken << std::endl;
    }

    void write_after_handshake() {
        perform_request(R"({"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","side":"Buy","price":10.0,"orderQty":1.0})");
    }

    void write_limit_order_bulk() {
        perform_request(
            R"({"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","clOrdID":")" +
            std::to_string(n_tests_) +
            R"(","side":"Buy","price":10.0,"orderQty":2}]})");
    }
  public:
    explicit BitMEX_MM(net::io_context& ioc, ssl::context& ssl_ctx)
        : stream_(make_strand(ioc.get_executor()),
                  ssl_ctx) // NOTE: strand not really required for sync
    {}

    void run() {
        // Set SNI Hostname (many hosts need this to handshake successfully)
        if (!::SSL_set_tlsext_host_name(stream_.native_handle(),
                                        "www.bitmex.com")) {
            throw boost::system::system_error(
                static_cast<int>(::ERR_get_error()),
                net::error::get_ssl_category());
        }

        // Set up an HTTP POST request message
        request_.version(11);
        request_.method(http::verb::post);
        request_.target("/api/v1/order");
        request_.set(http::field::host, "www.bitmex.com");
        request_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
        request_.set(http::field::accept, "*/*");
        request_.set(http::field::content_type, "application/json");
        request_.set(http::field::connection, "Keep-Alive");
        request_.set("api-key", apiKey_);
        request_.insert("api-expires", "");
        request_.insert("api-signature", "");

        // Look up the domain name
        tcp::resolver resolver(stream_.get_executor());
        beast::get_lowest_layer(stream_)
            .connect(resolver.resolve("www.bitmex.com", "443"));
        // Perform the SSL handshake
        stream_.handshake(ssl::stream_base::client);

        write_after_handshake();
        std::this_thread::sleep_for(30'000ms); // or just 30s...
        while (n_tests_++ < 5) {
            write_limit_order_bulk();
        }
    }
};

int main() {
    try {
        net::io_context ioc;
        ssl::context    ctx{ssl::context::tlsv12_client};

        BitMEX_MM algo(ioc, ctx);

        std::cout << "Running http test." << std::endl;
        algo.run();

    } catch (boost::system::system_error const& se) {
        error_code ec = se.code();
        std::cerr << "Error: " << se.code().message();
        if (ec.has_location())
            std::cerr << " (from " << se.code().location() << ")";
        std::cerr << std::endl;
    }
}

在我的系統上顯示(顯然由於缺少 API 密鑰和機密而失敗): 在此處輸入圖像描述

我發布了一個簡化刪除異步 IO 的答案。 這非常適合您的同步用例。

但是,它對於早期檢測服務器斷開連接並不是很好。 為此,您需要以另一種方式 go

““最佳實踐”方法將始終保持異步讀取掛起以及早檢測斷開連接。這要求您返回 go 以異步執行所有 IO。

為了完整起見,我繼續實現了一個BitMEX_MM class,它具有同步接口( write_after_handshakewrite_limit_order_bulk ),因為缺少更好的名稱。

BitMEX_MM algo(make_strand(ioc), ctx);

debug() << "Running http test." << std::endl;

//debug() << algo.write_after_handshake().res << std::endl;

for (unsigned id = 1; id <= 5; ++id) {
    auto delay = id * 20s; // * 0.5s;
    debug() << "Sleeping " << (delay / 1s) << " seconds" << std::endl;
    std::this_thread::sleep_for(delay);

    auto [req, time_taken, res] = algo.write_limit_order_bulk(id);

    debug() << " ------- response time: " << time_taken << std::endl;
    debug() << " ------- request: " << req.body() << std::endl;
    debug() << " ------- response: " << res.result_int() << " " << res.reason() << " " << res.body() << std::endl;
}

內部實現幾乎是完全異步的。

為簡潔起見,我唯一沒有進行異步的是do_connect操作,它很容易成為代碼量的兩倍。

重要的是讀取和寫入是異步操作,這意味着read將在服務器斷開連接時使用 EOF 發出信號。 發生這種情況時,我們會做最大的事情。 3 次重新連接嘗試(延遲增加)。

如果重新連接失敗,我們會將異常傳播到當前處理的請求的回復中:

if (ec && ec != net::error::operation_aborted)
    return do_propagate_error(do_connect(ec));

Live On 編譯器資源管理器

#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>

#include <deque>
#include <iomanip>
#include <iostream>
#include <openssl/hmac.h>
#include <sstream>

namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http  = beast::http;  // from <boost/beast/http.hpp>
namespace net   = boost::asio;  // from <boost/asio.hpp>
namespace ssl   = net::ssl;     // from <boost/asio/ssl.hpp>

using namespace std::chrono_literals;
using tcp = net::ip::tcp;
using beast::error_code;

static inline auto& debug() {
    static auto const s_program_start = std::chrono::steady_clock::now();
    auto cur = (std::chrono::steady_clock::now() - s_program_start) / 1.s;
    return std::cerr << std::fixed << std::setprecision(3) << std::setw(7)
                    << cur << "s - ";
}

struct BitMEX_MM {
private:
    using stream_t     = net::ssl::stream<tcp::socket>;
    using request_msg  = http::request<http::string_body>;
    using response_msg = http::response<http::string_body>;
    using clock        = std::chrono::steady_clock;

    ssl::context&             ssl_ctx_;
    net::any_io_executor      executor_;
    boost::optional<stream_t> stream_;
    beast::flat_buffer        buffer_;

    struct reply_t {
        request_msg  request;
        double       time_taken;
        response_msg response;
    };

    using promise_t = std::promise<reply_t>;
    using future_t  = std::future<reply_t>;

    struct queued_t {
        request_msg       request;
        clock::time_point start;
        promise_t         promise_;
    };

    std::deque<queued_t> queue_;
    response_msg         incoming_;

    std::string const apiKey_    = ""; // FILL IN API KEY
    std::string const apiSecret_ = ""; // FILL IN API SEC
    int const         expiry_t_  = 5;

    std::string const host_    = "www.bitmex.com";
    std::string const service_ = "https";
    std::string const route_   = "/api/v1/order";

    void sign_request(request_msg& request_) {
        std::ostringstream buf;
        buf << request_.method() << request_.target()
            << request_.at("api-expires") << request_.body();
        auto const data = buf.str();

        ::HMAC_CTX* ctx = HMAC_CTX_new();
        ::HMAC_Init_ex(ctx, apiSecret_.data(), apiSecret_.length(),
                    EVP_sha256(), nullptr);
        ::HMAC_Update(ctx, (unsigned char*)data.c_str(), data.length());

        unsigned int  len = 0;
        unsigned char out[EVP_MAX_MD_SIZE]{};
        ::HMAC_Final(ctx, out, &len);
        ::HMAC_CTX_free(ctx);

        std::stringstream signature;
        for (unsigned i = 0; i < len; ++i)
            signature << std::setw(2) << std::setfill('0') << std::hex
                    << (unsigned int)out[i];
        request_.set("api-signature", signature.str());
    }

    void do_read() { // on the strand
        http::async_read(
            *stream_, buffer_, incoming_,
            beast::bind_front_handler(&BitMEX_MM::on_response, this));
    }

    void on_response(error_code ec, size_t n) { // on the strand
        debug() << "Received: " << n << " (" << ec.message() << ")\n";
        auto& top = queue_.front();

        if (ec && ec != net::error::operation_aborted)
            return do_propagate_error(do_connect(ec));

        double time_taken = (clock::now() - top.start) / 1.0s;

        top.promise_.set_value({
            std::move(top.request),
            time_taken,
            std::move(incoming_),
        });

        do_read(); // incoming_ is free for the next read

        queue_.pop_front();

        if (!queue_.empty())
            do_send();
    }

    void do_perform_post(std::promise<reply_t> promise,
                        std::string const&    request_body) { // on the strand
        // Set up an HTTP POST request message
        request_msg req(http::verb::post, route_, 11);
        req.set(http::field::host, host_);
        req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
        req.set(http::field::accept, "*/*");
        req.set(http::field::content_type, "application/json");
        req.set(http::field::connection, "Keep-Alive");
        req.set("api-key", apiKey_);
        req.set("api-expires", std::to_string(time(0) + expiry_t_));
        req.body() = request_body;
        req.prepare_payload();
        sign_request(req);

        queue_.push_back(queued_t{std::move(req),
                                std::chrono::steady_clock::now(),
                                std::move(promise)});

        if (queue_.size() == 1)
            do_send();
    }

    void do_propagate_error(error_code ec) { // on the strand
        if (!ec.failed())
            return;

        debug() << "Unrecoverable (" << ec.message() << ")" << std::endl;
        if (queue_.empty())
            throw boost::system::system_error(ec);

        queue_.front().promise_.set_exception(
            std::make_exception_ptr(boost::system::system_error(ec)));
    }

    void do_send() { // on the strand
        if (queue_.empty())
            return;
        auto& top = queue_.front();

        http::async_write(
            *stream_, top.request, [this](error_code ec, size_t n) {
                debug() << "Written: " << n << " (" << ec.message() << ")\n";

                if (ec && ec != net::error::operation_aborted)
                    return do_propagate_error(do_connect(ec));
            });
    }

    // TODO make async
    static constexpr int max_tries = 3;
    error_code do_connect(error_code ec = {}, int tries = max_tries) { // on the strand
        if (tries <= 0)
            return ec;

        auto nth = max_tries - tries;
        if (nth > 0) { // first try with no delay
            std::this_thread::sleep_for(500ms * nth);
        }

        if (ec)
            debug() << "Reconnecting (" << ec.message() << ") #" << tries << std::endl;
        else
            debug() << "Connecting" << std::endl;

        ec.clear();

        if (stream_->next_layer().is_open()) {
            if (!ec) {
                stream_->shutdown(ec);
                debug() << "Shutdown: " << ec.message() << std::endl;
            }
            if (!ec) {
                stream_->next_layer().close(ec);
                debug() << "Close: " << ec.message() << std::endl;
            }
        }
        buffer_.clear(); // IMPORTANT
        stream_.emplace(executor_, ssl_ctx_);

        auto& sock = stream_->next_layer();
        tcp::resolver resolver(executor_);
        if (!ec)
            net::connect(sock, resolver.resolve(host_, service_, ec), ec);
        if (!ec)
            sock.set_option(tcp::no_delay(true), ec);

        auto ep = sock.remote_endpoint(ec);
        debug() << "Connected to " << ep << " (" << ec.message() << ")\n";

        // Set SNI Hostname (many hosts need this to handshake successfully)
        if (!::SSL_set_tlsext_host_name(stream_->native_handle(), host_.c_str()))
            throw boost::system::system_error(
                static_cast<int>(::ERR_get_error()),
                net::error::get_ssl_category());

        stream_->handshake(ssl::stream_base::client, ec);
        if (ec)
            return do_connect(ec, tries - 1);

        do_read();
        do_send(); // and also start a write if requests queued

        return ec;
    }

public:
    explicit BitMEX_MM(net::any_io_executor ex, ssl::context& ssl_ctx)
        : ssl_ctx_(ssl_ctx)
        , executor_(ex)
        , stream_(boost::in_place_init, executor_, ssl_ctx_) //
    {
        do_connect();
    }

    void stop() {
        return net::post(executor_, std::packaged_task<void()>{[this] {
                            stream_->next_layer().cancel();
                        }})
            .get();
    }

    future_t perform_post(std::string body) {
        std::promise<reply_t> prom;
        auto fut = prom.get_future();
        net::post(executor_,
                [this, b = std::move(body), p = std::move(prom)]() mutable {
                    return do_perform_post(std::move(p), std::move(b));
                });
        return fut;
    }

    reply_t write_after_handshake() {
        return perform_post(
                R"({"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","side":"Buy","price":10.0,"orderQty":1.0})")
            .get();
    }

    reply_t write_limit_order_bulk(unsigned test_id) {
        return perform_post(
                R"({"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","clOrdID":")" +
                std::to_string(test_id) +
                R"(","side":"Buy","price":10.0,"orderQty":2}]})")
            .get();
    }
};

int main() {
    try {
        net::thread_pool ioc{/*1*/};
        ssl::context     ctx{ssl::context::tlsv12_client};

        BitMEX_MM algo(make_strand(ioc), ctx);

        debug() << "Running http test." << std::endl;

        //debug() << algo.write_after_handshake().response << std::endl;

        for (unsigned id = 1; id <= 5; ++id) {
            auto delay = id * 20s; // * 0.5s;
            debug() << "Sleeping " << (delay / 1s) << " seconds" << std::endl;
            std::this_thread::sleep_for(delay);

            auto [req, time_taken, res] = algo.write_limit_order_bulk(id);

            debug() << " ------- response time: " << time_taken << std::endl;
            // debug() << " ------- request: " << req << std::endl;
            // debug() << " ------- response: " << res << std::endl;
            debug() << " ------- request: " << req.body() << std::endl;
            debug() << " ------- response: " << res.result_int() << " " << res.reason() << " " << res.body() << std::endl;
        }

        algo.stop();
        ioc.join();
    } catch (boost::system::system_error const& se) {
        error_code ec = se.code();
        if (ec.has_location()) {
            debug() << "Error: " << ec.message() << " (from " << ec.location() << ")" << std::endl;
        } else {
            debug() << "Error: " << ec.message() << std::endl;
        }
    }
}

在我的系統上測試:

在此處輸入圖像描述

output

  0.000s - Connecting
  0.019s - Connected to 172.64.146.66:443 (Success)
  0.035s - Running http test.
  0.035s - Sleeping 20 seconds
 15.024s - Received: 0 (end of stream)
 15.024s - Reconnecting (end of stream) #3
 15.024s - Shutdown: Success
 15.025s - Close: Success
 15.043s - Connected to 104.18.41.190:443 (Success)
 20.035s - Written: 419 (Success)
 20.081s - Received: 1163 (Success)
 20.081s -  ------- response time: 0.046
 20.081s -  ------- request: {"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","clOrdID":"1","side":"Buy","price":10.0,"orderQty":2}]}
 20.081s -  ------- response: 400 Bad Request {"error":{"message":"Missing API key.","name":"HTTPError"}}
 20.081s - Sleeping 40 seconds
 60.082s - Written: 419 (Success)
 60.125s - Received: 1163 (Success)
 60.125s -  ------- response time: 0.044
 60.125s -  ------- request: {"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","clOrdID":"2","side":"Buy","price":10.0,"orderQty":2}]}
 60.125s -  ------- response: 400 Bad Request {"error":{"message":"Missing API key.","name":"HTTPError"}}
 60.125s - Sleeping 60 seconds
120.126s - Written: 419 (Success)
120.170s - Received: 1163 (Success)
120.170s -  ------- response time: 0.044
120.170s -  ------- request: {"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","clOrdID":"3","side":"Buy","price":10.0,"orderQty":2}]}
120.170s -  ------- response: 400 Bad Request {"error":{"message":"Missing API key.","name":"HTTPError"}}
120.170s - Sleeping 80 seconds
200.170s - Written: 419 (Success)
200.252s - Received: 1163 (Success)
200.252s -  ------- response time: 0.082
200.252s -  ------- request: {"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","clOrdID":"4","side":"Buy","price":10.0,"orderQty":2}]}
200.252s -  ------- response: 400 Bad Request {"error":{"message":"Missing API key.","name":"HTTPError"}}
200.252s - Sleeping 100 seconds
300.253s - Written: 419 (Success)
300.300s - Received: 1163 (Success)
300.300s -  ------- response time: 0.047
300.300s -  ------- request: {"symbol":"XBTUSD","ordType":"Limit","execInst":"ParticipateDoNotInitiate","clOrdID":"5","side":"Buy","price":10.0,"orderQty":2}]}
300.300s -  ------- response: 400 Bad Request {"error":{"message":"Missing API key.","name":"HTTPError"}}
300.300s - Received: 0 (Operation canceled)

這告訴我們

  • bitmex.com 在連接的 15 秒內未收到第一個請求時斷開連接(請參閱 15.024 秒的自發重新連接(流結束))
  • 沒有嚴格遵守記錄的保持活動時間限制 90(日志中的“Sleeping 100s”沒有斷開連接)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM