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之前是否已准备好读取完整行。

+0

'select'文件描述符运行,但'fgets'。如果你想非阻塞I/O你可能不应该用fgets'FILE *' – 2014-11-05 00:40:08

+0

工作; [见这里](http://stackoverflow.com/questions/5351994/will-read-ever-block-after-select)信息 – 2014-11-05 00:41:10

+0

这很少是一个好主意,你混合无缓冲的I/O('select()'在这里)和缓冲I/O(这里是'fgets()')在同一个文件上同时存储相同的数据。 – alk 2014-11-05 05:43:40

正如MBlanc提到的那样,使用read()来实现您自己的缓冲就是这里的方法。

下面是一个演示一般方法的程序。我不建议正是这种做,因为:

  1. 这里介绍的功能使用静态变量,并且将只为一个单一的文件工作,将无法使用一旦结束了。实际上,你需要为每个文件设置一个单独的struct,并将每个文件的状态存储在那里,每次将它传递到你的函数中。

  2. 这样可以在将缓冲区中的某些数据从缓冲区中移除后,保留缓冲区。实际上,实施一个循环队列可能是一个更好的方法,但基本用法是一样的。

  3. 如果此处的输出缓冲区大于内部缓冲区,它将永远不会使用该额外空间。实际上,如果你遇到这种情况,你要么调整内部缓冲区大小,要么将内部缓冲区复制到输出字符串中,然后返回并在返回之前尝试第二次调用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'。这个动作消耗底层文件描述符的输入,所以你需要自己实现一个非阻塞版本。

+1

当你说“你需要自己实现一个非阻塞版本”时,你是否建议我在'select()'调用之间使用'read()'来缓冲数据,然后每次完成'read ()',检查是否存在''\ n''? – RPGillespie 2014-11-05 01:20:55

+0

@RPGillespie是,这是_exactly_我​​的意思:) – MBlanc 2014-11-06 07:19:22