< 返回版块

jessun2017 发表于 2021-01-17 11:20

问题背景

新手在学习 Rust 程序语言设计,https://kaisery.github.io/trpl-zh-cn/ch20-01-single-threaded.html

这里有一处说明:

接下来,需要实际读取流。这里分两步进行:首先,在栈上声明一个 buffer 来存放读取到的数据。这里创建了一个 512 字节的缓冲区,它足以存放基本请求的数据并满足本章的目的需要。如果希望处理任意大小的请求,缓冲区管理将更为复杂,不过现在一切从简。接着将缓冲区传递给 stream.read ,它会从 TcpStream 中读取字节并放入缓冲区中。

请教,如果想使用 TcpStream 处理任意大小请求,应该如何实现?

目前已经做的

google 上按照 rust TcpStream 关键字搜索,没有看到有提到这个问题的,都是引用原书。

评论区

写评论
zhylmzr 2021-01-17 19:02

如果想使用 TcpStream 处理任意大小请求 就是完整读取TcpStream直到遇到EOF呗

read_to_end就是从源读取直到遇到EOF。但是实际上又不是这么简单的。

一般来说上层的协议比如HTTP,发送端并不是一次把数据都发送完毕然后直接发送EOF。它是发送端发送数据(请求)、接收端发送数据(响应)、发送端收到所有数据发送EOF关闭连接(读取响应)、接收端收到EOF关闭连接。

所以作为服务端的话,TcpStream在读取的时候要检查是否请求进来了,如果进来了就得及时响应。

eg:

use std::io::{Read, Write};
use std::net::TcpListener;

// 简单检查一下是否http请求
fn http_check(buf: &Vec<u8>) -> bool {
    let l = buf.len();
    return if l <= 4 {
        false
    } else {
        buf[l - 1] == b'\n' && buf[l - 2] == b'\r' && buf[l - 3] == b'\n' && buf[l - 4] == b'\r'
    };
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:2233").unwrap();
    let mut buf = vec![0u8; 0];

    let (mut socket, _) = listener.accept().unwrap();
    loop {
        let mut b = vec![0u8; 1024];
        if http_check(&buf) {
            break;
        }

        let n = socket.read(&mut b).unwrap();
        if 0 == n {
            println!("eof");
            return;
        } else {
            buf.append(&mut b[..n].to_vec());
        }
    }

    socket
        .write(b"HTTP/1.1 200\r\nContent-Length: 0\r\n\r\n")
        .unwrap();
    println!("{}", String::from_utf8_lossy(&buf));
}

c5soft 2021-01-17 15:08

read与read_to_end都是std::io::Read这个trait里的方法,返回值为Result, 是用Result包裹的实际读取到的字节大小,需要通过unwrap或使用?语法糖简写的match表达式才能拿到usize。

实际应用中咱们不再用std::net了,要用tokio/async-std,采用async/await来干活。

c5soft 2021-01-17 14:53

先开一个空间自动增长的u8缓冲区:

let mut incoming_bytes = Vec::<u8>::new();
  

接下来有两种方法拿到incoming_bytes:

如果长度不大,一次读完:

let _bytes_count = stream.read_to_end(&mut incoming_bytes)?;

如果长度很大,开一个分段缓冲区,loop读取:

loop {
    let mut incoming_trunk =vec![0u8; 1024*8];
    let _bytes_count = stream.read(&mut incoming_trunk)?;
    println!("{}",_bytes_count);
    if _bytes_count == 0 {
      break;
    } else {
      incoming_bytes.append(&mut incoming_trunk);
    }
  }
songzhi 2021-01-17 11:48

TCP是连续的字节流,分割成单独的请求是应用层协议的事情。一般的应用层协议都会有一个请求头,其中有字段指定请求的长度。在读取头部之后知道了请求的长度,可以创建一个Vec<u8>或者Box<[u8]>或者String来继续读取请求体的数据。

use std::io::{self, Read};

struct Request {
    body: Box<[u8]>,
}

impl Request {
    pub fn read_from(stream: &mut impl Read) -> io::Result<Self> {
        let mut bytes = [0u8; 8];
        stream.read_exact(&mut bytes)?;
        let len = u64::from_be_bytes(bytes);
        let mut body = vec![0u8; len as usize];
        stream.read_exact(&mut body)?;

        Ok(Request {
            body: body.into_boxed_slice(),
        })
    }

    pub fn body(&self) -> &[u8] {
        &self.body
    }
}
1 共 4 条评论, 1 页