在C++中使用Boost.Asio处理多线程WebSocket客户端中的EXC_BAD_ACCESS

问题描述 投票:0回答:1

我正在使用 Boost.Beast 和 Boost.Asio 用 C++ 开发多线程 WebSocket 客户端。我在WebSocket握手过程(do_handshake方法)中偶尔会遇到分段错误(EXC_BAD_ACCESS)。该应用程序旨在处理来自服务器的实时数据流。

这是我的 WebSocketClient 类的简化版本:

class WebSocketClient
{
private:
    OrderBook &orderBook_;
    string coin_;

    boost::asio::io_context ioc_;
    tcp::resolver resolver_;
    ssl::context ssl_context_;
    websocket::stream<boost::asio::ssl::stream<tcp::socket>> ws_;

public:
    WebSocketClient(OrderBook &orderBook, const string &coin)
        : orderBook_(orderBook),
          coin_(coin),
          resolver_(ioc_),
          ssl_context_(ssl::context::tlsv12_client),
          ws_(ioc_, ssl_context_)
    {
        ssl_context_.set_default_verify_paths();
        ws_.next_layer().set_verify_mode(ssl::verify_peer);
        ws_.next_layer().set_verify_callback(ssl::rfc2818_verification("stream.binance.com"));
    }

    void connectAndSubscribe()
    {
        try
        {
            cout << "&ws_ = " << &ws_ << endl;
            // Resolve the host name and connect to the server
            auto const results = resolver_.resolve("stream.binance.com", "9443");
            auto ep = boost::asio::connect(ws_.next_layer().next_layer(), results);
            string host_ = "stream.binance.com:" + to_string(ep.port());

            ws_.next_layer().handshake(ssl::stream_base::client);
            // Set a decorator to change the User-Agent of the handshake
            ws_.set_option(websocket::stream_base::decorator(
                [](websocket::request_type &req)
                {
                    req.set(boost::beast::http::field::user_agent,
                            string(BOOST_BEAST_VERSION_STRING) + " websocket-client");
                }));

            string path = "/ws/" + coin_ + "@depth5@100ms";
            ws_.handshake(host_, path);
            cout << "Handshake successful." << endl;

            string msg = "{\"method\": \"SUBSCRIBE\", \"params\": [\"" + coin_ + "@depth5@100ms\"], \"id\": 1}";
            send(msg);
            cout << "Subscription request done!" << endl;
        }
        catch (const std::exception &e)
        {
            cout << "Error in connectAndSubscribe: " << e.what() << endl;
            throw; // Rethrow to be caught by the caller
        }
    }

该类在多线程上下文中使用,其中每个线程创建一个 WebSocketClient 实例来处理其连接。崩溃偶尔发生,堆栈跟踪指向 do_handshake 方法。

我已经查看了对象的生命周期,像 ssl_context_、resolver_ 和 ws_ 这样的对象都是 WebSocketClient 类的成员。我确保 OrderBook 对象的寿命比 WebSocketClient 实例的寿命长。

lldb终端:

* thread #5, stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x000000010003a28b adabtc`void boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor> >, true>::do_handshake<void (*)(boost::beast::http::message<true, boost::beast::http::empty_body, boost::beast::http::basic_fields<std::__1::allocator<char> > >&)>(boost::beast::http::message<false, boost::beast::http::basic_string_body<char, std::__1::char_traits<char>, std::__1::allocator<char> >, boost::beast::http::basic_fields<std::__1::allocator<char> > >*, boost::core::basic_string_view<char>, boost::core::basic_string_view<char>, void (* const&)(boost::beast::http::message<true, boost::beast::http::empty_body, boost::beast::http::basic_fields<std::__1::allocator<char> > >&), boost::system::error_code&) + 843
adabtc`boost::beast::websocket::stream<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor> >, true>::do_handshake<void (*)(boost::beast::http::message<true, boost::beast::http::empty_body, boost::beast::http::basic_fields<std::__1::allocator<char> > >&)>:
->  0x10003a28b <+843>: vmovaps 0x40(%rax), %ymm0
    0x10003a290 <+848>: leaq   0x2210(%rsp), %rax
    0x10003a298 <+856>: vmovups %ymm0, (%rax)
    0x10003a29c <+860>: movq   %r13, %r12
Target 0: (adabtc) stopped.
c++ websocket segmentation-fault boost-asio
1个回答
0
投票

这不是 C++ 例外。这是一般保护故障。你的工作不是处理它们,而是避免它们。显然你有 UB,很可能是数据竞争。解决这个问题,或者发布一个独立的示例,以便我们进行审查。这是我从你的代码开始的 - 这就是我现在的 20 分钟时间:

住在Coliru

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

namespace net       = boost::asio;
namespace ssl       = net::ssl;
namespace http      = boost::beast::http;
namespace websocket = boost::beast::websocket;
using net::ip::tcp;
struct OrderBook {
};

struct WebSocketClient {
    using Socket = tcp::socket;
    using Stream = ssl::stream<Socket>;
    using WS     = websocket::stream<Stream>;

    net::io_context ioc_;
    OrderBook&      orderBook_;
    std::string     coin_;
    tcp::resolver   resolver_{ioc_};
    ssl::context    ssl_context_{ssl::context::tlsv12_client};
    WS              ws_{ioc_, ssl_context_};

    WebSocketClient(OrderBook& orderBook, std::string coin) : orderBook_(orderBook), coin_(std::move(coin)) {
        ssl_context_.set_default_verify_paths();
        ws_.next_layer().set_verify_mode(ssl::verify_peer);
        ws_.next_layer().set_verify_callback(ssl::rfc2818_verification("stream.binance.com"));
    }

    void send(std::string msg) {
        ws_.write(net::buffer(msg));
    }

    void connectAndSubscribe() {
        // std::cout << "&ws_ = " << &ws_ << std::endl;

        // Resolve the host name and connect to the server
        auto results = resolver_.resolve("stream.binance.com", "9443");
        auto ep      = connect(ws_.next_layer().next_layer(), results);
        auto host_   = "stream.binance.com:" + std::to_string(ep.port());

        ws_.next_layer().handshake(Stream::client);
        // Set a decorator to change the User-Agent of the handshake
        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");
        }));

        std::string path = "/ws/" + coin_ + "@depth5@100ms";
        ws_.handshake(host_, path);
        std::cout << "Handshake successful." << std::endl;

        // std::string msg = R"({"method": "SUBSCRIBE", "params": [")" + coin_ + R"(@depth5@100ms"], "id": 1})";
        std::string msg = serialize(boost::json::value{
            {"method", "SUBSCRIBE"},
            {"params", {coin_ + "@depth5@100ms"}},
            {"id", 1},
        });
        // msg = R"({"method": "SUBSCRIBE", "params": [")" + coin_ + R"(@depth5@100ms"], "id": 1})";

        send(msg);
        std::cout << "Subscription request done!" << std::endl;
    }
};

int main() {
    OrderBook ob;
    WebSocketClient x(ob, "USD");
    x.connectAndSubscribe();
}

我的机器上打印出预期的内容

Handshake successful.
Subscription request done!

该应用程序旨在处理来自服务器的实时数据流

我非常怀疑它,因为我希望使用线程感知和异步 IO。

请扩展最小的独立代码以重现您的错误。或者简单地展示一下订单簿上的阅读是如何组织的以及线程同步是如何完成的。

© www.soinside.com 2019 - 2024. All rights reserved.