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

使用Rust、Mio和TlsConnector无法建立TLS连接求助

WebSocket客户端TLS握手失败:"the handshake process was interrupted"问题排查与解决

问题场景

尝试用tokio_tungstenite和mio实现WebSocket客户端时,TLS握手阶段出现the handshake process was interrupted错误,报错发生在mh.handshake()调用处,相关代码如下:

let addr: Vec<_> = ws_url
    .to_socket_addrs()
    .map_err(|err| ClientError {
        message: err.to_string(),
    })
    .unwrap()
    .collect();

println!("{:?}", addr);

let connector = TlsConnector::new().unwrap();
let stream = TcpStream::connect(addr[0]).unwrap();
let mut stream = match connector.connect(ws_url.as_str(), stream) {
    Ok(stream) => Ok(stream),
    Err(err) => match err {
        native_tls::HandshakeError::Failure(err) => Err(ClientError::new(format!(
            "Handshake failed: {}",
            err.to_string()
        ))),
        native_tls::HandshakeError::WouldBlock(mh) => match mh.handshake() {
            Ok(stream) => Ok(stream),
            Err(err) => Err(ClientError::new(format!( // <-- the handshake process was interrupted
                "Handshake failed: {}",
                err.to_string()
            ))),
        },
    },
}?;

问题原因

核心问题是错误处理HandshakeError::WouldBlock的方式不符合非阻塞IO逻辑:

  1. mio的TcpStream默认是非阻塞模式,当TLS握手需要等待网络IO(比如等待服务器响应)时,connector.connect()会返回WouldBlock错误,同时返回一个MidHandshakeTlsStream对象。
  2. 直接调用mh.handshake()并不会等待IO就绪,而是立即尝试继续握手,此时流还没有可读/可写事件,导致握手过程因无法完成IO操作而中断。

解决方法

根据使用的异步 runtime 选择对应方案:

方案一:使用tokio生态的异步TLS实现(推荐)

既然用了tokio_tungstenite,应搭配tokio-native-tls或rustls-tokio这类适配tokio的TLS库,而非同步的native-tls,利用tokio的异步调度自动处理IO等待:

use tokio::net::TcpStream;
use tokio_native_tls::{TlsConnector, TlsStream};
use url::Url;

async fn connect_ws(ws_url: Url) -> Result<TlsStream<TcpStream>, ClientError> {
    let addr = ws_url
        .to_socket_addrs()
        .map_err(|e| ClientError::new(e.to_string()))?
        .next()
        .ok_or_else(|| ClientError::new("No valid address found".to_string()))?;

    let tcp_stream = TcpStream::connect(addr).await?;
    let connector = TlsConnector::new()?;
    let domain = ws_url.host_str().ok_or_else(|| ClientError::new("Invalid URL host".to_string()))?;
    
    let tls_stream = connector.connect(domain, tcp_stream).await?;
    Ok(tls_stream)
}

方案二:基于mio手动处理事件循环

如果必须用mio手动管理,需要通过mio的Poll监听流的可读/可写事件,当流就绪后再继续握手:

use mio::{Events, Interest, Poll, Token};
use native_tls::{HandshakeError, MidHandshakeTlsStream, TlsConnector};
use std::time::Duration;

// 初始化mio的Poll和Token
let poll = Poll::new()?;
let token = Token(0);

// 注册流到Poll,监听读写事件
poll.registry().register(
    &mut mh.get_mut(),
    token,
    Interest::READABLE | Interest::WRITABLE,
)?;

let mut events = Events::with_capacity(1);
loop {
    // 等待流就绪
    poll.poll(&mut events, Some(Duration::from_secs(10)))?;
    
    match mh.handshake() {
        Ok(stream) => break Ok(stream),
        Err(HandshakeError::WouldBlock(new_mh)) => {
            mh = new_mh;
            // 重新注册事件
            poll.registry().reregister(
                &mut mh.get_mut(),
                token,
                Interest::READABLE | Interest::WRITABLE,
            )?;
        }
        Err(HandshakeError::Failure(e)) => break Err(ClientError::new(format!("Handshake failed: {}", e))),
    }
}

关键注意点

  • 非阻塞IO场景下,不能忽略WouldBlock错误直接重试,必须等待对应的IO就绪事件后再继续操作。
  • tokio_tungstenite本身提供了connect_async封装方法,可直接传入URL自动处理TLS握手,无需手动管理TCP和TLS流,建议优先使用:
    use tokio_tungstenite::connect_async;
    
    let (ws_stream, response) = connect_async(ws_url).await?;
    

内容的提问来源于stack exchange,提问作者Adam Starlight

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:50:18