繁体   English   中英

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

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

假设我在 Linux 系统上创建了一个命名管道:

$ mkfifo my_pipe

我要做的下一件事是编写一个小监视器程序,它尝试从my_pipe read() ,但一段时间后超时。 在下面的伪代码中,我使用了一个虚构的函数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
    }
}

我想pollPOLLIN标志可能会奏效,但事实并非如此。 从我的简单试验中,我发现它只是等待另一个进程打开命名管道进行写入(但不是为了数据可用,即read()不会阻塞)。 顺便说一句,由于某种原因, poll忽略您的超时,并且似乎永远阻塞,直到另一个进程打开管道。

我能想到的唯一其他解决方案是使用O_NONBLOCK open()文件,并在我不断尝试使用 0 字节计数的read()时手动观察时间的流逝。

有没有更好的解决方案?

编辑:我在这里的过程阻止了打开命名管道。 但是,如果您使用O_NONBLOCK标志,文件会立即打开。 此时, poll()可用于等待(带有可选超时)管道的另一端打开以进行写入。

但是,这仍然具有为read()函数实现超时的行为。 只要您调用read()它似乎仍然会阻塞(即使管道是用O_NONBLOCK打开的)

您关于在非阻塞模式下打开 fifo 的想法是正确的。 如果你这样做, poll() / select() /etc. 可用于等待另一端打开,或先超时。

以下示例程序只是在无限循环中运行,等待其他程序写入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;
    }
  }
}

经过@Shawn 的大量帮助和耐心,我设法想出了一个令我满意的答案。 这是一个名为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;
}

该技术是在(阻塞) read()调用之前设置一个计时器。 然后,我们可以简单地检查read()的返回值,看看它是否因超时而中断,是否发生了一般错误,是否达到了 EOF,或者是否成功读取数据。

只有一个障碍:你不能在非阻塞模式下打开文件; 这会导致open()阻塞,直到另一个进程打开管道进行写入。 但是,在我的应用程序中,这实际上是一个理想的功能。 您还可以设置 SIGALRM 以在open()上强制超时,或者在另一个线程中执行此操作。

事实上,这种技术应该适用于任何其他系统调用,所以我可能会组合一个小助手库来使这种模式更易于使用。

编辑

还有一件事:在注册信号处理程序时不要使用SA_RESTART标志非常重要。 否则,即使系统调用被信号中断,Linux 也会在信号处理后再次尝试。

暂无
暂无

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

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