我有一个循环。在此循环内,我尝试使用select()检测是否在命名管道(FIFO)文件上触发了读取或写入操作。

如果触发了读取,则在FIFO文件描述符上调用read()
如果触发了写入,则在FIFO文件描述符上调用write()

问题是,如果发生写操作并且我写到FIFO,它将触发读操作。然后,当我从FIFO读取数据时,它将触发写操作。造成无限循环。

如果在模式O_RDWR中使用相同的文件描述符,则会立即发生此循环。如果我为读取和写入创建了单独的文件描述符,则在第一次写入后会发生此循环。

#include <errno.h>
#include <sys/select.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>

int main() {
    // Open export fifo
    int fd = open("./foo-fifo", O_RDWR | O_CREAT);
    if (fd < 0) { // Failed to open
        perror("error opening fifo");
    }

    // Read or write fifo until "quit" is in buffer
    while (true) {
        fd_set read_fds;
        fd_set write_fds;

        FD_ZERO(&read_fds);
        FD_SET(fd, &read_fds);

        FD_ZERO(&write_fds);
        FD_SET(fd, &write_fds);

        int num_fds = select(fd+1, &read_fds, &write_fds, NULL, NULL);
        if (num_fds < 0) { // Failed to select
            perror("failed to select fifo fd");
        } else if (num_fds == 0) { // Timeout
            continue;
        }

        // If read
        if (FD_ISSET(fd, &read_fds)) {
            char buf[1000] = "";

            if (read(fd, buf, sizeof(buf)) < 0) {
                perror("error reading fifo");
            }

            printf("read: \"%s\"\n", buf);

            if (strcmp(buf, "quit\n") == 0) {
                break;
            }
        }

        // If write
        if (FD_ISSET(fd, &write_fds)) {
            char *buf = "foo";

            if (write(fd, buf, sizeof(buf)) < 0) {
                perror("error writing fifo");
            }

            printf("write: \"%s\"\n", buf);
        }
    }

    // Close fifo
    if (close(fd) < 0) { // Failed to close
        perror("failed to close export fifo");
    }

    return 0;
}


通过下载代码from here (GitHub Gist)运行该示例。然后运行:

gcc -o fifo fifo.c
./fifo


输出将显示读写之间的循环:

write: "foo"
read: ""
write: "foo"
read: ""
write: "foo"
...

最佳答案

注意:这是我的主要评论的开头。

我们需要两个进程(例如服务器和客户端)。

fifos是单向的(作家和读者),不像插座。

因此,要使用fifos做到这一点,您将需要两个。 (例如)给定进程A和B,我们需要两个管道/ FIFO:pipeABpipeBA

进程A向pipeAB写入,而B从pipeAB读取。

进程B向pipeBA写入,而A从pipeBA读取

如果要使用套接字,可以执行PF_UNIX(也称为AF_UNIX)套接字。请参见man 7 unixman 2 socketpair

或者,您可以使用将主机设置为localhost并使用某个固定端口号的完整AF_INET套接字。

作为一项练习[适合您],请考虑以几种方式进行。就是说,有一个argv选项,例如-Tp用于双管道,-Tu用于AF_UNIX,-Ts用于AF_INET,等等。只有初始化是不同的。否则该协议将几乎相同。

对于AF_UNIX套接字,如果客户端和服务器是不同的程序,则在文件系统中创建套接字类型的文件可能会更容易。这可以通过使用“文件名”填充struct sockaddr_un,然后在bind调用后使用socket来完成。参见:https://www.ibm.com/support/knowledgecenter/en/SSB23S_1.1.0.13/gtpc1/unixsock.html作为示例

07-24 09:46
查看更多