代码之家  ›  专栏  ›  技术社区  ›  Roflcopter4

从c中的stdin读取不可预测和不确定(即没有eof)大小的输入的最佳方法是什么?

c
  •  0
  • Roflcopter4  · 技术社区  · 6 年前

    这个 必须 做一个愚蠢的问题,因为这应该是一个非常常见和简单的问题,但我一直找不到答案,所以我会咬紧牙关问。

    在无法确定数据大小的情况下,究竟应该如何从标准输入中读取数据?显然,如果数据以nul或eof之类的终止符结尾,那么这是非常简单的,但我的数据不是。这是一个简单的ipc:两个程序需要来回对话,用eof结束文件流会破坏一切。

    我觉得这应该很简单。很明显,程序总是在管道上互相交谈,而不需要任何神秘的技巧,所以我希望有一个简单的答案,我太愚蠢了,没有想到。我试过的都没用。

    一些显而易见的事情,比如(为了简洁而忽略必要的realloc):

    int size = 0, max = 8192;
    unsigned char *buf = malloc(max);
    while (fread((buf + size), 1, 1, stdin) == 1)
        ++size;
    

    由于fread()阻塞并等待数据,因此无法工作,因此此循环不会终止。据我所知,stdio中没有允许非阻塞输入的东西,所以我甚至没有尝试任何这样的函数。这是我能想到的最好的办法:

    struct mydata {
        unsigned char *data;
        int slen; /* size of data */
        int mlen; /* maximum allocated size */
    };
    
    ...
    
    struct mydata *buf = xmalloc(sizeof *buf);
    buf->data = xmalloc((buf->mlen = 8192));
    buf->slen = 0;
    
    int nread = read(0, buf->data, 1);
    if (nread == (-1))
            err(1, "read error");
    
    buf->slen += nread;
    fcntl(0, F_SETFL, oflags | O_NONBLOCK);
    
    do {
        if (buf->slen >= (buf->mlen - 32))
            buf->data = xrealloc(buf->data, (buf->mlen *= 2));
        nread = read(0, (buf->data + buf->slen), 1);
        if (nread > 0)
            buf->slen += nread;
    } while (nread == 1);
    
    fcntl(0, F_SETFL, oflags);
    

    哪里 oflags 是一个全局变量,包含STDIN的原始标志(在程序开始时缓存,以防万一)。这种愚蠢的方法只要所有的数据都立即出现就可以工作,但如果不这样做就会失败。因为这将read()设置为非阻塞,所以如果没有数据,它只返回-1。与矿井通信的程序通常在任何时候都会发送响应,而不是一次响应,所以如果数据完全大,则退出太早而失败。

    3 回复  |  直到 6 年前
        1
  •  2
  •   Acorn    6 年前

    在无法确定数据大小的情况下,究竟应该如何从标准输入中读取数据?

    总有一种方法来决定大小。否则,程序将需要无限的内存,因此不可能在物理计算机上运行。

    这样想:即使在数据源源不断的情况下,也必须有一些块或点,你必须处理它。例如,直播视频必须解码其中的一部分(例如帧)。或者一个视频游戏,一个接一个地处理信息,即使游戏的长度不确定。

    无论您决定使用哪种类型的I/O(阻塞/非阻塞、同步/异步…),这都是正确的。例如,如果你想使用典型的阻塞同步I/O,你所要做的就是在循环中处理数据:每次迭代,你可以读取尽可能多的数据,并尽可能多地处理。无论你不能处理什么(因为你还没有收到足够多的数据),你会继续进行下一次迭代。然后,循环的其余部分就是程序逻辑的其余部分。

    最后,不管你做什么,你(或其他人,例如一个库,操作系统,硬件缓冲器…)必须缓冲传入的数据,直到它可以被处理。

        2
  •  1
  •   Chris Dodd    6 年前

    基本上,有两种选择——同步或异步——它们都有各自的优缺点。

    对于synchronous,您需要delimeters或一个嵌入到记录中的长度字段(或固定长度的记录,但这是非常不灵活的)。这对于同步协议(如同步rpc或simplex客户机-服务器交互)最有效,在这种情况下,一次只有一方在说话,而另一方在等待。对于基于ascii/文本的协议,通常使用控制字符分隔符(如nl/eol、nul或ctx)来标记消息的结尾。二进制协议通常使用嵌入的长度字段——接收器首先读取长度,然后读取全部(预期的)数据。

    对于异步,使用非阻塞模式。在stdio流中使用非阻塞模式是可能的,只是需要一些注意。数据外条件显示为类stdio错误条件,因此需要使用 ferror clearerr FILE * 视情况而定。

    两者都可以使用——例如,在客户机-服务器交互中,客户机可以使用同步(它们发送请求并等待答复),而服务器使用异步(在出现行为不正常的客户机时是健壮的)。

        3
  •  0
  •   doron    6 年前

    这个 read Linux上的API或 ReadFile windows上的api将立即返回,而不是等待指定的字节数来填充缓冲区(在读取管道或套接字时)。读取然后重新读取读取的字节数。

    这意味着,当从管道读取时,设置缓冲区大小,尽可能多地读取和处理过程。然后你读下一段。唯一被阻塞的时间是没有数据可用。

    这与 fread 它只返回所需的字节数,或者流确定这样做是不可能的(如eof)。