简体   繁体   English

命名管道上 read() 的 Linux 超时

[英]Linux timeout for read() on named pipe

Suppose I create a named pipe on a Linux system:假设我在 Linux 系统上创建了一个命名管道:

$ mkfifo my_pipe

The next thing I want to do is write a little monitor program which tries to read() from my_pipe , but times out after a while.我要做的下一件事是编写一个小监视器程序,它尝试从my_pipe read() ,但一段时间后超时。 In the following pseudo-code, I have used a fictional function wait_for_avail(fd, timeout_ms) :在下面的伪代码中,我使用了一个虚构的函数wait_for_avail(fd, timeout_ms)

int fd = open("my_pipe", O_RDONLY);
while (1) {
    //Fictional wait_for_avail(fd, timeout_ms). Is there a real function
    //that has this behaviour?
    int rc = wait_for_avail(fd, 500);
    if (rc == 1) {
        char buf[64];
        read(fd, buf, 64);
        //do something with buf
    } else {
        fprintf(stderr, "Timed out while reading from my_pipe\n");
        //do something else in the program
    }
}

I thought poll with the POLLIN flag might work, but it does not.我想pollPOLLIN标志可能会奏效,但事实并非如此。 From my simple trials, I have found that it simply waits until another process has opened the named pipe for writing (but not for data to be available, ie read() would not block).从我的简单试验中,我发现它只是等待另一个进程打开命名管道进行写入(但不是为了数据可用,即read()不会阻塞)。 By the way, for some reason, poll ignores your timeout and just seems to block forever until another process opens the pipe.顺便说一句,由于某种原因, poll忽略您的超时,并且似乎永远阻塞,直到另一个进程打开管道。

The only other solution I can think of is to open() the file with O_NONBLOCK , and sort of manually watch the time going by as I constantly try read() ing with a count of 0 bytes.我能想到的唯一其他解决方案是使用O_NONBLOCK open()文件,并在我不断尝试使用 0 字节计数的read()时手动观察时间的流逝。

Is there a better solution out there?有没有更好的解决方案?

EDIT: The process I have here blocks on opening the named pipe.编辑:我在这里的过程阻止了打开命名管道。 However, if you use the O_NONBLOCK flag, the file opens right away.但是,如果您使用O_NONBLOCK标志,文件会立即打开。 At that point, poll() can be used to wait (with an optional timeout) for the other end of the pipe to be opened for writing.此时, poll()可用于等待(带有可选超时)管道的另一端打开以进行写入。

However, this still does have the behaviour of implementing a timeout for the read() function.但是,这仍然具有为read()函数实现超时的行为。 It still appears to block as soon as you call read() (even if the pipe was opened with O_NONBLOCK )只要您调用read()它似乎仍然会阻塞(即使管道是用O_NONBLOCK打开的)

Your idea about opening the fifo in non-blocking mode is correct.您关于在非阻塞模式下打开 fifo 的想法是正确的。 If you do that, poll() / select() /etc.如果你这样做, poll() / select() /etc. can be used to wait for the other end to be opened, or timeout first.可用于等待另一端打开,或先超时。

The following example program just runs in an infinite loop waiting for other programs to write to my_pipe and echos the written text, with the occasional status update when there's no data or writer:以下示例程序只是在无限循环中运行,等待其他程序写入my_pipe并回显写入的文本,并在没有数据或写入器时偶尔更新状态:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  while (1) {
    int fd = open("my_pipe", O_RDONLY | O_NONBLOCK);
    if (fd < 0) {
      perror("open");
      return EXIT_FAILURE;
    }

    struct pollfd waiter = {.fd = fd, .events = POLLIN};

    while (1) {
      // 10 second timeout
      switch (poll(&waiter, 1, 10 * 1000)) {
      case 0:
        puts("The fifo timed out.");
        break;
      case 1:
        if (waiter.revents & POLLIN) {
          char buffer[BUFSIZ];
          ssize_t len = read(fd, buffer, sizeof buffer - 1);
          if (len < 0) {
            perror("read");
            return EXIT_FAILURE;
          }
          buffer[len] = '\0';
          printf("Read: %s\n", buffer);
        } else if (waiter.revents & POLLERR) {
          puts("Got a POLLERR");
          return EXIT_FAILURE;
        } else if (waiter.revents & POLLHUP) {
          // Writer closed its end
          goto closed;
        }
        break;
      default:
        perror("poll");
        return EXIT_FAILURE;
      }
    }
  closed:
    if (close(fd) < 0) {
      perror("close");
      return EXIT_FAILURE;
    }
  }
}

After a lot of help and patience from @Shawn, I managed to come up with an answer I found satisfying.经过@Shawn 的大量帮助和耐心,我设法想出了一个令我满意的答案。 Here are the contents of a file called pipe_watcher.c :这是一个名为pipe_watcher.c的文件的内容:

#include <stdio.h>  //printf etc.
#include <errno.h>  //errno
#include <string.h> //perror
#include <signal.h> //SIGALRM, sigaction, sigset
#include <time.h>   //timer_create, timer_settime
#include <fcntl.h>  //open, O_RDONLY
#include <unistd.h> //close

/* This code demonstrates how you can monitor a named pipe with timeouts on the
 * read() system call.
 * 
 * Compile with:
 * 
 *  gcc -o pipe_watcher pipe_watcher.c -lrt
 * 
 * And run with:
 * 
 *  ./pipe_watcher PIPE_FILENAME
*/

//Just needed a dummy handler
void sigalrm_handler(int s) {
    return;
}

int main(int argc, char **argv) {
    //Check input argument count
    if (argc != 2) {
        puts("Usage:\n");
        puts("\t./pipe_watcher PIPE_FILENAME");
        return -1;
    }

    //Create a timer object
    timer_t clk;
    int rc = timer_create(CLOCK_REALTIME, NULL, &clk);
    if (rc < 0) {
        perror("Could not create CLOCK_REALTIME timer");
        return -1;
    }

    //Create some time values for use with timer_settime
    struct itimerspec half_second = {
        .it_interval = {.tv_sec = 0, .tv_nsec = 0},
        .it_value = {.tv_sec = 0, .tv_nsec = 500000000}
    };

    struct itimerspec stop_timer = {
        .it_interval = {.tv_sec = 0, .tv_nsec = 0},
        .it_value = {.tv_sec = 0, .tv_nsec = 0}
    };

    //Set up SIGALRM handler
    struct sigaction sigalrm_act = {
        .sa_handler = sigalrm_handler,
        .sa_flags = 0
    };
    sigemptyset(&sigalrm_act.sa_mask);
    rc = sigaction(SIGALRM, &sigalrm_act, NULL);
    if (rc < 0) {
        perror("Could not register signal handler");
        timer_delete(clk);
        return -1;
    }

    //We deliberately omit O_NONBLOCK, since we want blocking behaviour on
    //read(), and we're willing to tolerate dealing with the blocking open()
    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        char msg[80];
        sprintf(msg, "Could not open [%s]", argv[1]);
        perror(msg);
        timer_delete(clk);
        return -1;
    }

    puts("File opened");

    while (1) {
        //Buffer to read() into
        char buf[80];
        int len;

        //Set up a timer to interrupt the read() call after 0.5 seconds
        timer_settime(clk, 0, &half_second, NULL);

        //Issue read() system call
        len = read(fd, buf, 80);

        //Check for errors. The else-if checks for EOF
        if (len < 0) {
            if (errno == EINTR) {
                //This means we got interrupted by the timer; we can keep going
                fprintf(stderr, "Timeout, trying again\n");
                continue;
            } else {     
                //Something really bad happened. Time to quit.       
                perror("read() failed");
                //No point waiting for the timer anymore
                timer_settime(clk, 0, &stop_timer, NULL);
                break;
            }
        } else if (len == 0) {
            puts("Reached end of file");
            break;
        }

        //No error or EOF; stop the timer and print the results
        timer_settime(clk, 0, &stop_timer, NULL);
        write(STDOUT_FILENO, buf, len);
    }

    //Cleanup after ourselves
    timer_delete(clk);
    close(fd);
    return 0;
}

The technique is to set up a timer before a (blocking) read() call.该技术是在(阻塞) read()调用之前设置一个计时器。 Then, we can simply check the return value of read() to see if it was interrupted due to a timeout, if a general error occurred, if EOF was reached, or if it successfully read data.然后,我们可以简单地检查read()的返回值,看看它是否因超时而中断,是否发生了一般错误,是否达到了 EOF,或者是否成功读取数据。

There's only one snag: you can't open the file in non-blocking mode;只有一个障碍:你不能在非阻塞模式下打开文件; this causes open() to block until another process opens the pipe for writing.这会导致open()阻塞,直到另一个进程打开管道进行写入。 However, in my application this is actually a desirable feature.但是,在我的应用程序中,这实际上是一个理想的功能。 You could also set up SIGALRM to enforce a timeout on the open() , or maybe do it in another thread.您还可以设置 SIGALRM 以在open()上强制超时,或者在另一个线程中执行此操作。

In fact, this technique should work with any other system call, so I might put together a little helper library to make this pattern easier to use.事实上,这种技术应该适用于任何其他系统调用,所以我可能会组合一个小助手库来使这种模式更易于使用。

EDIT编辑

One more thing: it is very important to not use the SA_RESTART flag when registering the signal handler.还有一件事:在注册信号处理程序时不要使用SA_RESTART标志非常重要。 Otherwise, even if a system call is interrupted by a signal, Linux will try it again after the signal is handled.否则,即使系统调用被信号中断,Linux 也会在信号处理后再次尝试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM