< 返回版块

jojo 发表于 2021-01-25 12:32

Tags:std::io::Read

已解决

不存在 一劳永逸 的流读取方法,应针对不同通信协议制定不同的流读取方法


当缓冲区 buf 的大小恰好与io流中的数据大小相等时,下面的函数将不会返回一直卡死。应该如何解决?

use std::io;

fn read_once<R: ?Sized + io::Read>(reader: &mut R) -> io::Result<Vec<u8>> {
    let mut result = Vec::new();
    let mut buf = [0; 128];

    loop {
        let len = match reader.read(&mut buf) {
            Ok(0) => break,
            Ok(len) => len,
            Err(e) => return Err(e),
        };
        result.append(&mut buf[..len].to_vec());

        if len < buf.len() {
            break;
        }
    }
    Ok(result)
}

我用回声程序来测试可触发bug, 仓库链接

评论区

写评论
me1ting 2021-01-26 11:33

一般是读取一定长度就可以分辨其所属的协议(根据协议头以及其它特征),再由相关的协议handler处理后续的流程。

shadowsocks windows客户端的入口是采用的同端口http.socks5协议识别。 gost也支持同端口多协议。

可以看看这两个以及其它项目,看看它们是如何实现的。

作者 jojo 2021-01-26 10:09

谢谢,受益匪浅。我其实在尝试做代理,但在对同一端口支持多协议和加密通讯这里绕进去了。 协商 是重点,不应该试图读取全部数据,而是要针对不同的协议逐一去识别、读取。

--
👇
me1ting: 补充

从你函数逻辑来讲,其退出循环的条件是:

  • 接收到EOF
  • 发生非Interrupted异常
  • 接收到的数据长度是非缓冲区大小整数倍的

当你发送的数据是非缓冲区大小整数倍的,因为退出了循环,所以client可以继续执行。但是当其长度是缓冲区大小整数倍时,进入循环,由于client的发送、回显是单线程执行的,循环没有退出,又无法发送数据从而中断循环,因此阻塞在了读取数据上。

网络编程都是采取协议来进行协商的,不是凭心情来发送、接收数据,你可以进一步学习《计算机网络》,并尝试一些网络编程练习,比如了解HTTP代理、SOCKS5代理,编写一个网络代理软件。

me1ting 2021-01-26 01:47

补充

从你函数逻辑来讲,其退出循环的条件是:

  • 接收到EOF
  • 发生非Interrupted异常
  • 接收到的数据长度是非缓冲区大小整数倍的

当你发送的数据是非缓冲区大小整数倍的,因为退出了循环,所以client可以继续执行。但是当其长度是缓冲区大小整数倍时,进入循环,由于client的发送、回显是单线程执行的,循环没有退出,又无法发送数据从而中断循环,因此阻塞在了读取数据上。

网络编程都是采取协议来进行协商的,不是凭心情来发送、接收数据,你可以进一步学习《计算机网络》,并尝试一些网络编程练习,比如了解HTTP代理、SOCKS5代理,编写一个网络代理软件。

me1ting 2021-01-26 01:35

“我的需求是从io流中接收任意大小的数据并返回” 你缺乏对流模型的理解,而这是与语言无关的,或者说是系统底层提供的抽象

read()方法本来就是读取一次,无论是阻塞读还是非阻塞读

可以多看看API,了解下底层,多思考,同时参考其它语言的API进行了解,比如Golang的文档就很简单清晰

pub fn read_once<R: ?Sized + Read>(r: &mut R) -> io::Result<Vec<u8>> {
    let mut result = Vec::new();
    let mut buf = [0; 8];

    loop {
        let len = match r.read(&mut buf) {//read本来就是读一次,为啥画蛇添足
            Ok(0) => break,//官方库的文件、网络流都是阻塞模型,返回0表示到达EOF,退出循环
            Ok(len) => len,
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
            Err(e) => return Err(e),//错误退出循环
        };
        result.append(&mut buf[..len].to_vec());

        if len < buf.len() {//读不满退出循环,也就是输入不是buf.size的整数倍的情况下
            break;
        }
    }
    Ok(result)
}

所以你这代码根本实现不了你的需求。

而且没有代码能满足你的需求,因为你理想中的读一次是用一个无限大的缓冲区来接受数据,并返回。但是谁也无法预先知道会有多少数据发送来,该用多大的缓冲区来接收。而且因为底层存在的缓冲,数据发送的时间间隔是并不可靠的,这也是为什么,所有语言的API都是这样设计:

//伪代码
func read(buf)//需要你提供一个固定大小的缓冲区
func read()->vec //而不是什么都不提供,直接返回一个包含数据的数组,因为办不到
zhylmzr 2021-01-25 17:37

read如果读取到EOF会返回0,按照代码逻辑直接break循环了,那么应该不是一直循环导致的卡死,可能是read一直没有返回?建议开启断点调试。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=144da5300d4f9264b1eb4d321ae31036

Neutron3529 2021-01-25 17:28

你是怎么卡死的?

我测试时候发现返回值很正常

use std::io;
use std::io::prelude::*;
use std::io::BufWriter;
use std::fs::File;

fn read_once<R: ?Sized + io::Read>(reader: &mut R) -> io::Result<Vec<u8>> {
    let mut result = Vec::new();
    let mut buf = [0; 128];

    loop {
        let len = match reader.read(&mut buf) {
            Ok(0) => break,
            Ok(len) => len,
            Err(e) => return Err(e),
        };
        result.append(&mut buf[..len].to_vec());

        if len < buf.len() {
            break;
        }
    }
    Ok(result)
}


fn main() -> io::Result<()> {
    {
        let mut writer = BufWriter::new(File::create("foo.txt")?);

        // write a byte to the buffer
        writer.write(&[65;1024])?;

    }
    let mut f = File::open("foo.txt")?;

    // read the whole file
    println!("{:?}",read_once(&mut f));
    Ok(())
}
7sDream 2021-01-25 15:02

这功能看着像 read_to_end

https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_end

作者 jojo 2021-01-25 14:30

我看了一下read_exact的源码,它的作用是将buf缓冲区填满,貌似不太符合我的需求。 我的需求是从io流中接收任意大小的数据并返回。。我的read_once函数运行的不错,唯独当接收到的数据和缓冲区一样大小时就出bug了...

--
👇
uno: 去看read_exact的代码就知道了

uno 2021-01-25 13:42

去看read_exact的代码就知道了

1 共 9 条评论, 1 页