[英]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
}
}
我想poll
与POLLIN
标志可能会奏效,但事实并非如此。 从我的简单试验中,我发现它只是等待另一个进程打开命名管道进行写入(但不是为了数据可用,即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.