使用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逻辑:
- mio的
TcpStream默认是非阻塞模式,当TLS握手需要等待网络IO(比如等待服务器响应)时,connector.connect()会返回WouldBlock错误,同时返回一个MidHandshakeTlsStream对象。 - 直接调用
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
相关产品推荐
相关产品推荐

