[英]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 之外發送請求的正確方法是什么?
編輯:以下是上述示例的原始代碼,並進行了以下修改:
function write_after_handshake 發送一個不完整的請求來建立和維護連接,因為 REST_on_handshake 是空的。
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 的時間和精力。 但是,我仍然缺少一些要點:
write_after_handshake() 的唯一用途是發送第一個(虛擬)請求,該請求不應該執行任何操作,而是為以下實際請求保持連接活動(如果我評論它,我收到一個錯誤:在拋出一個實例后調用終止'boost::wrapexceptboost::system::system_error' what(): end of stream) 如上所述)。 在這里的上下文中,這將是一個帶有空消息或數量為 0 的請求,它從服務器返回一個錯誤(如預期的那樣)。 有沒有一種更清潔的方法來做到這一點,從某種意義上說,只是建立和維護連接的請求?
如果我使用 write_after_handshake(),那么以下所有請求都會正確發送,但是您可以通過對 cout 的調用看到始終顯示 write_after_handshake 中第一個請求的響應:
{"error":{"message":"Invalid orderQty","name":"ValidationError"}}{"orderID":"... OK here...}
所以我知道第一個請求總是被重新發送?
這是我提出問題的主要原因,如何避免這種情況?
這似乎是預期的行為:緩存未清除。 我應該在這里自己管理一些東西嗎(隨着時間的推移,緩存是否太大成為問題)? 無論如何,要僅顯示最后一個響應,這似乎可行:
response_ = {};
最大的問題似乎是您正在混合異步和同步 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();
}
作為回復
- 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 改進等。
// 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;
}
}
我發布了一個簡化刪除異步 IO 的答案。 這非常適合您的同步用例。
但是,它對於早期檢測服務器斷開連接並不是很好。 為此,您需要以另一種方式 go :
““最佳實踐”方法將始終保持異步讀取掛起以及早檢測斷開連接。這要求您返回 go 以異步執行所有 IO。
為了完整起見,我繼續實現了一個BitMEX_MM
class,它具有同步接口( write_after_handshake
和write_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));
#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)
這告訴我們
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.