简而言之,我需要从 C# 客户端发送的 TCP 连接中读取一个字符串。客户端使用BinaryWriter
,它以 leb128 格式的长度作为实际字符串的前缀。我tokio::net::TcpStream
在 Rust 中使用,并搜索了一个 crate 来帮助我从流中检索该长度前缀,但我找不到合适的东西。大多数解决方案都需要您正在读取的源来实现该io::Read
特征,但tokio::net::TcpStream
没有实现它。
我设法让它用这个丑陋的代码工作,但我从一开始就对它持怀疑态度。我最近发现它有时会导致某种竞争条件。我并不完全确定,但我认为它在 上被阻止了let file_name_len = leb128::read::unsigned(&mut socket)?;
,这不知何故导致我的 TCP 侦听器停止接受新连接,这更奇怪。
let mut socket = socket.into_std()?;
socket.set_nonblocking(false)?;
let file_name_len = leb128::read::unsigned(&mut socket)?;
let mut socket = tokio::net::TcpStream::from_std(socket)?;
有人知道正确的方法吗?
上面的代码是阻塞的:
set_nonblocking(false)
)。leb128::read::unsigned(&mut socket)?;
。这将阻塞整个 tokio 线程。
如果TCP 侦听器在单独的任务中运行并且您正在使用(默认)多线程 tokio 运行时,它不应该阻止 TCP 侦听器......当然,除非您有多个这样的 LEB 任务阻止每个 tokio 线程。
不幸的是,没有用于异步读取的标准 API,并且
leb128
crate 没有提供任何 tokio 集成,因此需要做一些工作。但是,不会太多,因为
&[u8]
实现了Read
,并且之后Read
切片将被更新为指向未读取的字节。由于您使用的是 TCP,我假设您已经为接收的字节设置了某种缓冲区 - 将它们传递给解码器 - 因此您应该只使用该缓冲区。
不过,我觉得上面的代码结构不太好。混合异步 I/O 和解码意味着您无法单独测试解码,这很麻烦,我真的建议尽可能选择Sans IO设计。
相反,我鼓励您编写一个
Framer
或Decoder
来处理部分(或全部)解码逻辑,并将 I/O 与帧/解码完全分开。这个想法相对简单:将字节推入其中,获取帧字节或解码的消息。
由于我没有解码器,我将改用成帧器,其作用是隔离流中的单个帧(编码消息)。
一旦你有了框架,它实际上就相对简单了:
而且非常重要的是,可以很容易地测试框架是否可以处理各种消息。
实际的成帧器代码相对简单: