这个 必须 是一个愚蠢的问题,因为这应该是一个非常常见和简单的问题,但是我无法在任何地方找到答案,所以我我会硬着头皮问。
当无法确定数据大小时,我应该如何从标准输入中读取?显然,如果数据以某种终止符(例如NUL或EOF)结尾,那么这是微不足道的,但是我的数据却没有。这是简单的IPC:这两个程序需要来回通信,以EOF结束文件流会破坏一切。
我认为这应该很简单。显然,程序始终可以通过管道相互通信,而无需任何神秘的技巧,因此我希望有一个简单的答案,我太愚蠢以至于没有想到。我尝试过的一切都没有奏效。
类似的东西(为了简洁起见,忽略了必要的重新分配):
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。与我的通信程序通常会在需要时发送响应,而不是一次发送所有响应,因此,如果数据很大,则退出的时间过早并失败。
答案 0 :(得分:2)
当无法确定数据大小时,我应该如何从标准输入中读取数据?
始终必须有一种确定大小的方法。否则,该程序将需要无限的内存,因此无法在物理计算机上运行。
这样考虑:即使在数据流永无止境的情况下,也必须有一些块或点需要处理。例如,实时流传输的视频必须解码其一部分(例如,帧)。或者是一个视频游戏,即使游戏的长度不确定,也可以一一处理消息。
无论您决定使用哪种I / O(阻塞/非阻塞,同步/异步...),这都适用。例如,如果要使用典型的阻塞同步I / O,您要做的就是循环处理数据:每次迭代,您将读取尽可能多的数据,并尽可能多地进行处理。无论您无法处理什么(因为您还没有收到足够的信息),请保留下一个迭代。然后,循环的其余部分就是程序的其余逻辑。
最后,无论您做什么,您(或其他人,例如库,操作系统,硬件缓冲区...)都必须缓冲传入的数据,直到可以处理它为止。
答案 1 :(得分:1)
基本上,您有两种选择-同步或异步-都有其优点和缺点。
对于同步,您需要在记录(或定长记录)中嵌入分度符或长度字段,但这非常不灵活。这对于同步协议(例如同步rpc或单工客户端-服务器交互)最有效,其中一次仅一次通话,另一侧等待。对于基于ASCII /文本的协议,通常使用控制字符定界符(例如NL / EOL或NUL或CTX)来标记消息的结尾。二进制协议通常使用嵌入式长度字段-接收器首先读取长度,然后读取全部(预期)数据。
对于异步,您使用非阻塞模式。可以将非阻塞模式与stdio流一起使用,只需要进行一些注意即可。数据不足条件会像错误条件一样出现在stdio上,因此您需要在ferror
上适当使用clearerr
和FILE *
。
两者都可以使用-例如,在客户端与服务器的交互中,客户端可以使用同步(它们发送请求并等待答复),而服务器使用异步(在存在以下情况时保持健壮性)行为不端的客户)。
答案 2 :(得分:0)
Linux上的read
api或Windows上的ReadFile
Api将立即返回,而不用等待指定的字节数来填充缓冲区(在读取管道或套接字时)。读取然后再次读取读取的字节数。
这意味着,当从管道中读取数据时,您需要设置缓冲区大小,读取返回的内容并对其进行处理。然后,您阅读下一点。您唯一被阻止的时间就是根本没有可用数据。
这不同于fread
,后者仅在返回所需的字节数或流确定无法这样做(例如eof)时才返回。