Чтение фрагментированных TCP-пакетов с помощью TcpStream в Tokio - PullRequest
0 голосов
/ 18 апреля 2020

У меня возникают некоторые проблемы с чтением пакетов 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() возвращает 1448, что точно размер первого и второго пакета, но buf содержит данные из первого пакета.

Теперь мне интересно, пропустил ли я что-то, и TcpStream закрывается с первым полученным пакетом или я пропустил размер буфера где-то.

1 Ответ

2 голосов
/ 18 апреля 2020

Методы чтения, такие как Read::read или AsyncReadExt::read, обычно не гарантируют, что объем данных будет потребляться для каждого из них. Если TcpStream имеет три доступных пакета, он может использовать только первый пакет, или первые два пакета, или первый пакет и половину второго. Что бы он ни делал, это деталь реализации. Единственное допущение, которое вы можете сделать, это то, что если он возвращает 0 (то есть не было прочитано ни одного байта), он достиг своего «конца потока» (например, потому что соединение было закрыто).

Из-за этого обычно вы должны читать в al oop:

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, который будет читайте в al oop для вас, пока буфер не заполнится, или read_to_end, который будет считывать, пока не будет достигнут конец потока.

...