如何在Rust中从tokio::net::TcpStream读取LEB128格式数据
基于Tokio读取带LEB128前缀字符串的正确实现
问题根源
你之前的实现把Tokio异步TCP流转成阻塞的std流处理,完全违背了Tokio的异步调度模型。当调用阻塞的leb128::read::unsigned时,会占用Tokio的工作线程,导致整个调度器被卡住——这就是监听器无法接受新连接、出现竞态问题的核心原因。
两种可行解决方案
方案一:手动实现异步LEB128读取
直接基于Tokio的AsyncRead trait实现LEB128无符号整数读取,全程异步,无需转换为std流:
use tokio::io::{AsyncRead, AsyncReadExt}; async fn read_leb128_unsigned<R: AsyncRead + Unpin>(reader: &mut R) -> std::io::Result<u64> { let mut result = 0u64; let mut shift = 0; loop { // 异步读取单个字节 let byte = reader.read_u8().await?; result |= ((byte & 0x7F) as u64) << shift; // 最高位为0表示读取结束 if (byte & 0x80) == 0 { break; } shift += 7; // 防止溢出 if shift >= 64 { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, "LEB128长度超出64位范围", )); } } Ok(result) }
使用示例:
// socket为tokio::net::TcpStream实例 let file_name_len = read_leb128_unsigned(&mut socket).await?; // 分配对应长度的缓冲区,读取完整字符串字节 let mut buf = vec![0u8; file_name_len as usize]; socket.read_exact(&mut buf).await?; // 转换为字符串 let file_name = String::from_utf8(buf)?;
方案二:使用异步LEB128库
直接使用支持Tokio异步读取的async-leb128 crate,简化代码:
- 在
Cargo.toml添加依赖:
async-leb128 = "0.1"
- 读取逻辑:
use async_leb128::AsyncReadLeb128Ext; use tokio::io::AsyncReadExt; // 异步读取LEB128前缀长度 let file_name_len = socket.read_unsigned_leb128().await?; // 读取完整字符串内容 let mut buf = vec![0u8; file_name_len as usize]; socket.read_exact(&mut buf).await?; let file_name = String::from_utf8(buf)?;
核心注意点
- 绝对不要在Tokio异步代码中调用阻塞IO操作,这会破坏调度器的工作机制,引发死锁或性能问题。所有操作必须使用Tokio提供的异步方法(带
.await)。 - 读取字符串时必须用
read_exact,确保读取到指定长度的完整字节,避免因TCP流的分段特性导致数据不完整。
内容的提问来源于stack exchange,提问作者Aaras
相关产品推荐
相关产品推荐

