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

基于ASIO的TLS客户端关闭连接时SSL流崩溃的原因与解决

问题

我有一个基于ASIO的TLS客户端,简化代码如下(注:所有套接字操作均由互斥锁保护,所有ASIO异步回调在同一独立线程执行):

std::mutex socket_op_lock;
ssl::stream<tcp::socket> ssl_socket;

Connect() {
    ssl_socket.next_layer().async_connect(on_connect);
}

on_connect(const asio::error_code& error) {
    std::lock_guard<std::mutex> socket_op_lock;
    if (error) {
        CloseSocket();
    }
    else {
        ssl_socket.async_handshake(on_handshake);
    }
}

on_handshake(const asio::error_code& error) {
    std::lock_guard<std::mutex> socket_op_lock;
    if (error) {
        CloseSocket();
    }
    else {
        Receive();
    }
}

Receive() {
    // called internal, already locked
    async_read(ssl_socket, ..., on_read);
}

on_read() {
    std::lock_guard<std::mutex> socket_op_lock;
    if (error) {
        CloseSocket();
    }
    else {
        Receive();
    }
}

Disconnect() {
    std::lock_guard<std::mutex> socket_op_lock;
    CloseSocket();
}

CloseSocket() {
    // called internal, already locked
    ssl_socket.next_layer().shutdown();
    ssl_socket.next_layer().close();
}

问题出现在连接关闭阶段:因担心ssl::stream的shutdown会阻塞,我直接关闭底层TCP连接,但在Android/iOS上偶现崩溃,堆栈信息如下:

0  MyApp  asio::detail::kqueue_reactor::start_op(int, int, asio::detail::kqueue_reactor::descriptor_state*&, asio::detail::reactor_op*, bool, bool) + 108
1  MyApp  asio::detail::kqueue_reactor::start_op(int, int, asio::detail::kqueue_reactor::descriptor_state*&, asio::detail::reactor_op*, bool, bool) + 100
2  MyApp  void asio::detail::reactive_socket_service_base::async_receive<asio::mutable_buffers_1, asio::ssl::detail::io_op<asio::basic_stream_socket<asio::ip::tcp>, asio::ssl::detail::handshake_op, std::__1::__bind<void (MyApp::MyTcpClient::*)(std::__1::error_code const&), std::__1::shared_ptr<MyApp::MyTcpClient>, std::__1::placeholders::__ph<1> const&> > >(asio::detail::reactive_socket_service_base::base_implementation_type&, asio::mutable_buffers_1 const&, int, asio::ssl::detail::io_op<asio::basic_stream_socket<asio::ip::tcp>, asio::ssl::detail::handshake_op, std::__1::__bind<void (MyApp::MyTcpClient::*)(std::__1::error_code const&), std::__1::shared_ptr<MyApp::MyTcpClient>, std::__1::placeholders::__ph<1> const&> >&) + 384
3  MyApp  asio::ssl::detail::io_op<asio::basic_stream_socket<asio::ip::tcp>, asio::ssl::detail::handshake_op, std::__1::__bind<void (MyApp::MyTcpClient::*)(std::__1::error_code const&), std::__1::shared_ptr<MyApp::MyTcpClient>, std::__1::placeholders::__ph<1> const&> >::operator()(std::__1::error_code, unsigned long, int) + 900
4  MyApp  asio::detail::reactive_socket_send_op<asio::const_buffers_1, asio::detail::write_op<asio::basic_stream_socket<asio::ip::tcp>, asio::mutable_buffer, asio::mutable_buffer const*, asio::detail::transfer_all_t, asio::ssl::detail::io_op<asio::basic_stream_socket<asio::ip::tcp>, asio::ssl::detail::handshake_op, std::__1::__bind<void (MyApp::MyTcpClient::*)(std::__1::error_code const&), std::__1::shared_ptr<MyApp::MyTcpClient>, std::__1::placeholders::__ph<1> const&> > > >::do_complete(void*, asio::detail::scheduler_operation*, std::__1::error_code const&, unsigned long) + 228
5  MyApp  asio::detail::scheduler::do_run_one(asio::detail::conditionally_enabled_mutex::scoped_lock&, asio::detail::scheduler_thread_info&, std::__1::error_code const&) + 692
6  MyApp  asio::detail::scheduler::run(std::__1::error_code&) + 188
7  MyApp  MyApp::NetworkIoThread::CustomLoop() + 44
8  MyApp  MyApp::OMMsgThread::Run() + 96
9  libsystem_pthread.dylib  __pthread_start + 136

我猜测未显式调用ssl::stream的shutdown,导致关闭TCP后仍有SSL流操作触发崩溃,于是添加SSL层关闭流程,修改后的代码如下:

Disconnect() {
    std::lock_guard<std::mutex> socket_op_lock;
    ssl_socket.async_shutdown(on_shutdown);
}

on_shutdown() {
    std::lock_guard<std::mutex> socket_op_lock;
    CloseSocket();
}

修改后iOS平台崩溃次数增多,堆栈信息如下:

0  MyApp  CRYPTO_memcmp + 76
1  MyApp  aes_gcm_tls_cipher + 624
2  MyApp  aes_gcm_cipher + 96
3  MyApp  EVP_Cipher + 64
4  MyApp  tls1_enc + 2748
5  MyApp  ssl3_get_record + 3220
6  MyApp  ssl3_read_bytes + 744
7  MyApp  ssl3_read_internal + 128
8  MyApp  ssl3_read + 52
9  MyApp  ssl_read_internal + 340
10 MyApp  SSL_read + 96
11 MyApp  asio::ssl::detail::engine::perform(int (asio::ssl::detail::engine::*)(void*, unsigned long), void*, unsigned long, std::__1::error_code&, unsigned long*) + 96
12 MyApp  asio::ssl::detail::io_op<asio::basic_stream_socket<asio::ip::tcp>, asio::ssl::detail::read_op<asio::mutable_buffers_1>, asio::detail::read_dynbuf_op<asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp> >, asio::basic_streambuf_ref<std::__1::allocator<char> >, asio::detail::transfer_at_least_t, std::__1::__bind<void (MyApp::MyTcpClient::*)(std::__1::error_code const&, unsigned long), std::__1::shared_ptr<MyApp::MyTcpClient>, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&> > >::operator()(std::__1::error_code, unsigned long, int) + 92
13 MyApp  asio::detail::reactive_socket_recv_op<asio::mutable_buffers_1, asio::ssl::detail::io_op<asio::basic_stream_socket<asio::ip::tcp>, asio::ssl::detail::read_op<asio::mutable_buffers_1>, asio::detail::read_dynbuf_op<asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp> >, asio::basic_streambuf_ref<std::__1::allocator<char> >, asio::detail::transfer_at_least_t, std::__1::__bind<void (MyApp::MyTcpClient::*)(std::__1::error_code const&, unsigned long), std::__1::shared_ptr<MyApp::MyTcpClient>, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&> > >::do_complete(void*, asio::detail::scheduler_operation*, std::__1::error_code const&, unsigned long) + 196
14 MyApp  asio::detail::scheduler::do_run_one(asio::detail::conditionally_enabled_mutex::scoped_lock&, asio::detail::scheduler_thread_info&, std::__1::error_code const&) + 692
15 MyApp  asio::detail::scheduler::run(std::__1::error_code&) + 188
16 MyApp  MyApp::NetworkIoThread::CustomLoop() + 44
17 MyApp  MyApp::OMMsgThread::Run() + 96
18 libsystem_pthread.dylib  __pthread_start + 148

但奇怪的是Android平台无异常。请问该问题的原因是什么,如何解决?

原因分析
  1. 直接关闭底层TCP的问题:直接关闭TCP套接字时,ASIO的SSL流可能存在未完成的异步操作(如握手、读操作),这些操作后续会访问已关闭的套接字或失效的SSL上下文,触发崩溃。iOS的kqueue reactor对文件描述符状态变更更敏感,因此更容易出现这类问题。

  2. 添加async_shutdown后的问题:崩溃增多的核心原因是异步SSL shutdown与正在进行的异步读操作发生竞态。调用async_shutdown时,当前可能仍有async_read在执行,SSL引擎在shutdown过程中会尝试处理剩余SSL记录,而读操作也在访问SSL引擎内部状态,导致线程安全冲突。虽然代码用了互斥锁,但ASIO的SSL操作内部可能存在未被覆盖的共享状态访问。此外,iOS上的OpenSSL版本或ASIO实现对这类并发操作的兼容性更差,而Android的实现可能做了额外保护。

解决方案

步骤1:取消所有未完成的异步操作

发起shutdown前,必须先取消所有挂起的异步读/写操作,避免与shutdown操作冲突。在持有锁的情况下调用ssl_socket.cancel():

Disconnect() {
    std::lock_guard<std::mutex> socket_op_lock;
    // 取消所有未完成的异步操作
    ssl_socket.cancel();
    ssl_socket.async_shutdown(on_shutdown);
}

步骤2:正确处理shutdown错误

async_shutdown可能返回正常错误(如对方已关闭连接),无需额外处理,直接关闭套接字即可:

on_shutdown(const asio::error_code& error) {
    std::lock_guard<std::mutex> socket_op_lock;
    // 忽略shutdown的正常错误,比如对方提前关闭连接
    CloseSocket();
}

步骤3:完善CloseSocket逻辑

关闭底层套接字前,先清理SSL引擎状态,同时处理可能的错误:

CloseSocket() {
    // called internal, already locked
    asio::error_code ec;
    // 先关闭SSL引擎的读写
    ssl_socket.shutdown(ec);
    // 关闭底层TCP套接字
    ssl_socket.next_layer().shutdown(tcp::socket::shutdown_both, ec);
    ssl_socket.next_layer().close(ec);
}

步骤4:标记连接状态,避免重复操作

添加is_disconnecting标志,防止重复发起shutdown或其他操作:

std::mutex socket_op_lock;
ssl::stream<tcp::socket> ssl_socket;
bool is_disconnecting = false;

Disconnect() {
    std::lock_guard<std::mutex> socket_op_lock;
    if (is_disconnecting) return;
    is_disconnecting = true;
    
    ssl_socket.cancel();
    ssl_socket.async_shutdown(on_shutdown);
}

// 在所有回调中先检查状态,再执行后续操作
on_read(const asio::error_code& error, std::size_t bytes_transferred) {
    std::lock_guard<std::mutex> socket_op_lock;
    if (is_disconnecting) return;
    
    if (error) {
        CloseSocket();
    } else {
        Receive();
    }
}

关键说明

  • 取消异步操作是核心:必须确保shutdown前没有其他异步操作访问SSL流,否则会引发状态竞争。
  • 忽略shutdown错误:SSL shutdown失败是常见情况,无需视为异常,直接关闭底层套接字即可。
  • 状态标记防止重复操作:避免多次调用Disconnect导致的重复shutdown或cancel操作。

内容的提问来源于stack exchange,提问作者0luan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 00:05:55