如何使用poll C函数在Linux中监视命名管道?

13
我正在尝试编写一个程序,在其中使用poll函数来监视一些命名管道的末端。我有一个for循环来检查每个管道,每当poll返回>0时。我知道当从另一端的进程关闭一个管道时,我将在pollfd结构体的revents字段中收到POLLHUP或POLLIN | POLLHUP。
我的问题是:当确实关闭了一个管道并向我返回POLLHUP时,下一次循环会发生什么?它会在下一次和任何后续循环中再次返回POLLHUP,还是在第一个POLLHUP之后,poll函数会忽略它?

5
听起来这看起来是相当容易通过测试来找出的。 - Ben Voigt
我尝试过,但出了些问题,导致 pollhup 返回的次数比预期的要多得多,所以我想知道我的代码可能出了什么问题,因此我提出了那个问题。 - nikos
2
@nikos:你可能想要告诉轮询器,你不再对来自该fd的事件感兴趣了(或者如果连接是半关闭的话,只对某些事件感兴趣)。 - Ben Voigt
如果我不告诉它轮询,它会继续检查那个管道并每次返回POLLHUP,对吗? - nikos
1
@nikos:只要您将该fd保留在传递给poll的列表中,它就必须对其进行检查。是的,我希望它继续返回POLLHUP,因为该fd仍处于该状态。 - Ben Voigt
告诉轮询器我不再对该fd的事件感兴趣:将fd设置为-1(找到了我的答案)- @Voigt:如果管道中有数据,读取会失败吗?- 请参见此处的不一致性 - Mr_and_Mrs_D
2个回答

5

最简示例

以下是源代码。用法:

sudo mknod poll0.tmp p
sudo mknod poll1.tmp p
sudo chmod 666 poll*.tmp
./poll.out

在另一个终端中:

printf a > poll0.tmp
printf b > poll1.tmp

输出:

loop
POLLIN i=0 n=1 buf=a
loop
POLLHUP i=0
loop
POLLIN i=1 n=1 buf=b
POLLHUP i=1
loop

注意 poll 如何在不循环的情况下等待读取。

更酷的例子:

(while true; do date; sleep 1; done) > poll0.tmp &
(while true; do date; sleep 2; done) > poll1.tmp &

每秒钟会写入0,每两秒钟会写入1,这展示了poll()如何同时处理两个输入而不相互阻塞。
来源:

poll.c

#define _XOPEN_SOURCE 700
#include <fcntl.h> /* creat, O_CREAT */
#include <poll.h> /* poll */
#include <stdio.h> /* printf, puts, snprintf */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <unistd.h> /* read */

int main(void) {
    enum { N = 2 };
    char buf[1024], path[1024];
    int fd, i, n;
    short revents;
    struct pollfd pfds[N];

    for (i = 0; i < N; ++i) {
        snprintf(path, sizeof(path), "poll%d.tmp", i);
        /* O_NONBLOCK is required or else the open blocks
         * until the other side of the pipe opens. */
        fd = open(path, O_RDONLY | O_NONBLOCK);
        if (fd == -1) {
            perror("open");
            exit(EXIT_FAILURE);
        }
        pfds[i].fd = fd;
        /* Only events in this mask will be listened to.
         * However, there are also some events that are unmaskable,
         * notably POLLHUP when pipe closes! */
        pfds[i].events = POLLIN;
    }
    while (1) {
        puts("loop");
        i = poll(pfds, N, -1);
        if (i == -1) {
            perror("poll");
            exit(EXIT_FAILURE);
        }
        for (i = 0; i < N; ++i) {
            revents = pfds[i].revents;
            if (revents & POLLIN) {
                n = read(pfds[i].fd, buf, sizeof(buf));
                printf("POLLIN i=%d n=%d buf=%.*s\n", i, n, n, buf);
            }
            if (revents & POLLHUP) {
                printf("POLLHUP i=%d\n", i);

                /* This happens when the other side closed.
                 * This event is only cleared when we close the reader. */

                /* poll won't set POLLHUP anymore once all fds are closed.
                 * Any futher polls on this will give the POLLNVAL event instead. */
                close(pfds[i].fd);

                /* negative fds are ignored. So if we negate an FD,
                 * we can both turn if off for a while, and turn it on
                 * later on by re-nagating it. */
                pfds[i].fd *= -1;
            }
        }
    }
}

编译方式:

gcc -o poll.out -std=c99 poll.c

在Ubuntu 14.04中进行了测试。

GitHub上游

回答原问题:

当一个管道确实关闭并向我返回POLLHUP时,在下一次循环中会发生什么?它会在下一个和任何后续循环中再次返回POLLHUP,还是poll函数会在第一个POLLHUP之后忽略它?

删除以下行:

close(pfds[i].fd);
pfds[i].fd *= -1;

你会看到它在POLLHUP上无限循环。

只需删除:

close(pfds[i].fd);

如果尝试使用一个已关闭的文件描述符,您将得到POLLNVAL,而不是预期的结果:如何处理Linux套接字revents POLLERR,POLLHUP和POLLNVAL?


3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接