fgets()当缓冲区太大时阻塞
我正在使用select()来判断是否有数据要在文件描述符中读取,因为我不希望fgets阻塞。这可以在99%的时间内运行,但是,如果select()检测到fp中的数据,但数据没有换行符并且数据比我的缓冲区小,则它将被阻塞。有什么办法可以告诉有多少字节可以被读取?fgets()当缓冲区太大时阻塞
//See if there is data in fp, waiting up to 5 seconds
select_rv = checkForData(fp, 5);
//If there is data in fp...
if (select_rv > 0)
{
//Blocks if data doesn't have a newline and the data in fp is smaller than the size of command_out!!
if (fgets(command_out, sizeof(command_out)-1, fp) != NULL)
{
printf("WGET: %s", command_out);
}
}
else if (select_rv == 0)
{
printf("select() timed out... No output from command process!\n");
}
我想我真正想要的是一种方法来知道在调用fgets之前是否已准备好读取完整行。
正如MBlanc提到的那样,使用read()
来实现您自己的缓冲就是这里的方法。
下面是一个演示一般方法的程序。我不建议正是这种做,因为:
这里介绍的功能使用静态变量,并且将只为一个单一的文件工作,将无法使用一旦结束了。实际上,你需要为每个文件设置一个单独的
struct
,并将每个文件的状态存储在那里,每次将它传递到你的函数中。这样可以在将缓冲区中的某些数据从缓冲区中移除后,保留缓冲区。实际上,实施一个循环队列可能是一个更好的方法,但基本用法是一样的。
如果此处的输出缓冲区大于内部缓冲区,它将永远不会使用该额外空间。实际上,如果你遇到这种情况,你要么调整内部缓冲区大小,要么将内部缓冲区复制到输出字符串中,然后返回并在返回之前尝试第二次调用
read()
。
但是实现所有这些会给示例程序增加太多的复杂性,这里的一般方法将显示如何完成任务。
为了模拟延迟在接收输入,主程序将管从下面的方案,该方案只是输出几次,有时用换行,有时并没有和sleep()
S IN的输出之间的输出:
delayed_output.c
:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("Here is some input...");
fflush(stdout);
sleep(3);
printf("and here is some more.\n");
printf("Yet more output is here...");
fflush(stdout);
sleep(3);
printf("and here's the end of it.\n");
printf("Here's some more, not ending with a newline. ");
printf("There are some more words here, to exceed our buffer.");
fflush(stdout);
return 0;
}
主程序:
buffer.c
:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/select.h>
#define INTBUFFERSIZE 1024
#define BUFFERSIZE 60
#define GET_LINE_DEBUG true
/* Prints a debug message if debugging is on */
void get_line_debug_msg(const char * msg, ...)
{
va_list ap;
va_start(ap, msg);
if (GET_LINE_DEBUG) {
vfprintf(stderr, msg, ap);
}
va_end(ap);
}
/*
* Gets a line from a file if one is available.
*
* Returns:
* 1 if a line was successfully gotten
* 0 if a line is not yet available
* -1 on end-of-file (no more input available)
*
* NOTE: This function can be used only with one file, and will
* be unusable once that file has reached the end.
*/
int get_line_if_ready(int fd, char * out_buffer, const size_t size)
{
static char int_buffer[INTBUFFERSIZE + 1] = {0}; /* Internal buffer */
static char * back = int_buffer; /* Next available space in buffer */
static bool end_of_file = false;
if (!end_of_file) {
/* Check if input is available */
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv = {0, 0};
int status;
if ((status = select(fd + 1, &fds, NULL, NULL, &tv)) == -1) {
perror("error calling select()");
exit(EXIT_FAILURE);
}
else if (status == 0) {
/* Return zero if no input available */
return 0;
}
/* Get as much available input as will fit in buffer */
const size_t bufferspace = INTBUFFERSIZE - (back - int_buffer) - 1;
const ssize_t numread = read(fd, back, bufferspace);
if (numread == -1) {
perror("error calling read()");
exit(EXIT_FAILURE);
}
else if (numread == 0) {
end_of_file = true;
}
else {
const char * oldback = back;
back += numread;
*back = 0;
get_line_debug_msg("(In function, just read [%s])\n"
"(Internal buffer is [%s])\n",
oldback, int_buffer);
}
}
/* Write line to output buffer if a full line is available,
* or if we have reached the end of the file. */
char * endptr;
const size_t bufferspace = INTBUFFERSIZE - (back - int_buffer) - 1;
if ((endptr = strchr(int_buffer, '\n')) ||
bufferspace == 0 ||
end_of_file) {
const size_t buf_len = back - int_buffer;
if (end_of_file && buf_len == 0) {
/* Buffer empty, we're done */
return -1;
}
endptr = (end_of_file || bufferspace == 0) ? back : endptr + 1;
const size_t line_len = endptr - int_buffer;
const size_t numcopy = line_len > (size - 1) ? (size - 1) : line_len;
strncpy(out_buffer, int_buffer, numcopy);
out_buffer[numcopy] = 0;
memmove(int_buffer, int_buffer + numcopy, INTBUFFERSIZE - numcopy);
back -= numcopy;
return 1;
}
/* No full line available, and
* at end of file, so return 0. */
return 0;
}
int main(void)
{
char buffer[BUFFERSIZE];
FILE * fp = popen("./delayed_output", "r");
if (!fp) {
perror("error calling popen()");
return EXIT_FAILURE;
}
sleep(1); /* Give child process some time to write output */
int n = 0;
while (n != -1) {
/* Loop until we get a line */
while (!(n = get_line_if_ready(fileno(fp), buffer, BUFFERSIZE))) {
/* Here's where you could do other stuff if no line
* is available. Here, we'll just sleep for a while. */
printf("Line is not ready. Sleeping for five seconds.\n");
sleep(5);
}
/* Output it if we're not at end of file */
if (n != -1) {
const size_t len = strlen(buffer);
if (buffer[len - 1] == '\n') {
buffer[len - 1] = 0;
}
printf("Got line: %s\n", buffer);
}
}
if (pclose(fp) == -1) {
perror("error calling pclose()");
return EXIT_FAILURE;
}
return 0;
}
和输出:
[email protected]:~/src/sandbox/buffer$ ./buffer
(In function, just read [Here is some input...])
(Internal buffer is [Here is some input...])
Line is not ready. Sleeping for five seconds.
(In function, just read [and here is some more.
Yet more output is here...])
(Internal buffer is [Here is some input...and here is some more.
Yet more output is here...])
Got line: Here is some input...and here is some more.
Line is not ready. Sleeping for five seconds.
(In function, just read [and here's the end of it.
Here's some more, not ending with a newline. There are some more words here, to exceed our buffer.])
(Internal buffer is [Yet more output is here...and here's the end of it.
Here's some more, not ending with a newline. There are some more words here, to exceed our buffer.])
Got line: Yet more output is here...and here's the end of it.
Got line: Here's some more, not ending with a newline. There are some
Got line: more words here, to exceed our buffer.
[email protected]:~/src/sandbox/buffer$
有什么办法可以告诉有多少字节准备被读取?
不是我在C99/POSIX中知道的。我猜这个功能没有被认为是有用的,因为文件具有固定的大小(大部分时间,反正)。不幸的是select()
是非常基本的,因为你已经看到了。
我想我真正想要的是一种方法来知道在调用fgets之前是否已准备好读取完整行。
fgets()
缓冲区循环直到达到'\n'
。这个动作消耗底层文件描述符的输入,所以你需要自己实现一个非阻塞版本。
当你说“你需要自己实现一个非阻塞版本”时,你是否建议我在'select()'调用之间使用'read()'来缓冲数据,然后每次完成'read ()',检查是否存在''\ n''? – RPGillespie 2014-11-05 01:20:55
@RPGillespie是,这是_exactly_我的意思:) – MBlanc 2014-11-06 07:19:22
'select'文件描述符运行,但'fgets'。如果你想非阻塞I/O你可能不应该用fgets'FILE *' – 2014-11-05 00:40:08
工作; [见这里](http://stackoverflow.com/questions/5351994/will-read-ever-block-after-select)信息 – 2014-11-05 00:41:10
这很少是一个好主意,你混合无缓冲的I/O('select()'在这里)和缓冲I/O(这里是'fgets()')在同一个文件上同时存储相同的数据。 – alk 2014-11-05 05:43:40