使用 select 多路复用未命名管道和其他文件描述符

Posted

技术标签:

【中文标题】使用 select 多路复用未命名管道和其他文件描述符【英文标题】:multiplexing unnamed pipe and other file descriptors using select 【发布时间】:2018-03-23 21:03:25 【问题描述】:

我正在尝试将未命名管道与其他一些文件描述符复用。 问题是管道文件描述符总是出现在select 的结果中。换句话说,事件循环从管道中读取无限次。这是我想要做什么以及实际发生的事情的隐喻。

#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <stdio.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>


using namespace std;
int main()
    fd_set master;    // master file descriptor list
    fd_set read_fds;  // temp file descriptor list for select() 
    FD_ZERO(&master);    // clear the master and temp sets
    FD_ZERO(&read_fds);
    int fdmax;        // maximum file descriptor number
    int pfd[2];
    if(pipe(pfd)!=0) cout<<"Unable to create a pipe.\n",exit(1);;

    FD_SET(0, &master);
    FD_SET(pfd[0],&master);

    fdmax=pfd[0];

    if(fork())//Parent


        for (;;)
            read_fds = master; // copy it
            if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) 
                perror("select");
                exit(4);
            
            for(int i = 0; i <= fdmax; i++) 
                if (FD_ISSET(i, &read_fds)) 
                    int n;
                    char buff[200];

                    if (i==pfd[0])
                        close(pfd[1]);
                        n=read(pfd[0],buff,sizeof(buff));
                        buff[n]=0;
                        cout<<"Read from pipe:"<<buff<<endl;
                    else if(i==0)
                        n=read(0,buff,sizeof(buff));
                        buff[n]=0;
                        cout<<"Read from std:"<<buff<<endl;
                    
                
            

        
    else//Child
        usleep(50000);
        char buff[200]="This is a simple sample.";
        close(pfd[0]);
        write(pfd[1],buff,sizeof(buff));
        close(pfd[1]);
        exit(0);
    

【问题讨论】:

您不能在对 select 的调用中重复使用 fd 集。在循环中重新初始化read_fds @user58697 line read_fds = master; 在循环中重新初始化read_fds 【参考方案1】:

首先,read() 调用可以读取少于最后一个参数中指定的字节数 ant id 不会自动附加零字节终止符,因此您的接收代码可以轻松访问 buff[] 和之后的未初始化内存它(如果没有零字节)。您在调用read时需要检查返回值,并且只使用缓冲区中的这么多字节。

然后,当 readfds 集中的任何文件描述符在后续读取时不会阻塞时,select 调用将返回。其中包括文件结束条件。当分叉的进程关闭其 fd 时,这可能会发生在您的情况下。也见this SO question。

这可能是您遇到问题的原因吗?在调用 read 时检查返回值会让您清楚这一点,因为 read 在 fd 到达文件末尾时返回零。

最后一个细节——仅在pfd[0]readfds 中返回后关闭pfd[1] 没有多大意义。你应该在 fork 之后立即关闭它,这样只有当你在父进程中没有使用它时它才会在子进程中保持打开状态。

【讨论】:

感谢您的勇敢回答,您对 read 的返回值是正确的。它多次返回零。 我不应该关闭pfd[0]。关于关闭pfd[1],如你所说,最好在父进程fork后关闭一次,虽然在这种情况下不会打扰。 我从来没有告诉过你应该关闭 pfd[0],我相信。关于 pfd[1],请注意,不及时关闭它可能会影响是否/如何接收 EOF 事件。在您的代码段中不是特别重要,但一般来说,当子进程不会在管道中推送任何数据而是直接终止(无论出于何种原因)时,您的父进程不会看到任何事件,也不会看到 EOF,因为它拥有自己的pfd[1] 的副本打开(我假设,请随时检查有关 EOF 的详细信息并自行关闭管道 fds)。

以上是关于使用 select 多路复用未命名管道和其他文件描述符的主要内容,如果未能解决你的问题,请参考以下文章

多路复用epoll

IO多路复用机制(转)

多路复用select

redis中的IO多路复用select和epoll

Go语言协程并发---select多路复用应用

hdl 中的管道多路复用器