2014-11-04 277 views
2

我正在使用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

回答

2

正如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$ 
1

有什麼辦法可以告訴有多少字節準備被讀取?

不是我在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