在 Tokio 中使用 TcpStream 读取碎片化的 TCP 数据包

Read fragmented TCP packets with TcpStream in Tokio

我在读取 TCP 数据包时遇到一些问题。

我正在尝试读取 JSON 响应,大小为 5000 字节,但是查看 Wireshark 中的数据包,它们被分为三个不同的数据包,第一个和第二个1448 字节,第三个大小为 2530 字节。

当我尝试用 Tokio-rs 读取它们时,我只收到第一个,所以我没有收到完整的 JSON 数据。

为了阅读,我使用了以下代码:

pub async fn read(stream: &mut TcpStream) -> Result<Bytes, std::io::Error>{
    let mut buf = BytesMut::with_capacity(8128);
    let mut resp = [0u8; 8128];
    let buf_len = stream.read(&mut resp).await?;
    buf.extend_from_slice(&resp);
    buf.truncate(buf_len);
    println!("{}", buf.len());
    Ok(buf.freeze())
}

buf.len() returns 1448 正好是第一个和第二个数据包的大小,但是 buf 包含第一个数据包的数据。

现在我想知道我是否遗漏了什么并且 TcpStream 在收到第一个数据包时关闭,或者我是否在某处缺少缓冲区大小。

读取方法,如 Read::read or AsyncReadExt::read,通常不保证每次都会消耗多少数据。如果 TcpStream 有三个数据包可用,它可能只消耗第一个数据包,或者前两个数据包,或者第一个数据包和第二个数据包的一半。无论它做什么都是一个实现细节。您可以做出的唯一假设是,如果它 returns 0(即没有读取任何字节),它已达到其 "end of stream"(例如因为连接已关闭)。

正因如此,一般应该循环阅读:

let mut buf = BytesMut::with_capacity(8128);
let mut resp = [0u8; 8128];

loop {
    let buf_len = stream.read(&mut resp).await?;
    buf.extend_from_slice(&resp[0..buf_len]);

    if buf_len == 0 {
      // end of stream
      panic!("Unexpected EOF");
    } else if buf.len() >= 5000 {
      //      ^---------------^
      //               \_________ some condition to check if buffer is "ready"

      // buffer has been filled with enough bytes
      break;
    } else {
      // buffer does not have enough bytes, keep reading...
      continue;
    }
}

println!("{}", buf.len());
Ok(buf.freeze())

或者,如果您想填满整个缓冲区,您可以使用 read_exact method instead, which will read in a loop for you until the buffer is full, or read_to_end,它将读取直到到达流的末尾。