【Linux】多进程管道通信,及select、poll函数在其中的应用

管道是常用的进程间的通信方法。通常的实验例程为——创建管道->创建进程->父进程通过管道发送数据->子进程通过管道接收数据并显示。如下代码所示,需要的东西都写在注释中。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int     res;
    char    *buf = "this is data to trans"; //管道发送的数据
    char    readbuf[64] = {0};              //管道接受缓存
    int     fd[2];                          //0->读管道 1->写管道
    int     pid;

    //创建管道
    res = pipe(fd);                         
    if(res != 0)
    {
        printf("create pipe error\n");
        return 0;
    }

    //创建进程
    pid = fork();                           
    if(pid == -1)
    {
        printf("fork fail\n");
    }
    //子进程
    else if(pid == 0)                       
    {
        printf("this is child\n");
        close(fd[1]);
        //从读管道读取数据
        read(fd[0],readbuf,64);             
        printf("child rev :%s\n",readbuf);
        close(fd[0]);
    }
    //父进程
    else                                    
    {
        printf("this is parent\n");
        usleep(5000000);
        close(fd[0]);
        write(fd[1],buf,64);
        close(fd[1]);
        //等待子进程结束
        waitpid(pid,NULL,0);
    }

    return 0;
}

通过以上代码可以看出,在创建子进程后,父进程等待了5秒才通过管道向子进程发送了数据。而子进程一经创建,就通过read在试图获取读管道中的数据。实验结果如下

.wy@wy-VirtualBox:~/test/book/csdn$ ./a.out 
this is parent
this is child
child rev :this is data to trans
wy@wy-VirtualBox:~/test/book/csdn$

其中child rev :this is data to trans是在程序运行5秒后才打印出来的,这充分说明子进程中的read函数是默认阻塞的。通常,用read读字符终端设备、网络socket、管道时都是默认阻塞的。那么举一反三,某些情况下如果文件描述符中一直没有数据来,那我只能一直等着吗?select和poll这两个函数,就是加入了超时限制的触发函数。具体作用可以在linux下使用man手册查看,用在管道通信时,其大概功能描述就是,在超时时间内等待某一通道有数据可读,根据超时、可读等状态返回不同的返回值,然后我们就可以根据不同的返回值进行不同的操作。具体程序如下,select、poll的具体使用方法可自行man查看:
1.使用select的管道通信

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>

int main()
{
    int     res;
    char    *buf = "this is data to trans";
    char    readbuf[64] = {0};
    int     fd[2];
    int     pid;
    fd_set  fdread;                         //select文件句柄
    struct timeval timeout;                 //超时结构体

    timeout.tv_sec  = 6;
    timeout.tv_usec = 0;

    res = pipe(fd);
    if(res != 0)
    {
        printf("create pipe error\n");
        return 0;
    }
    //清select句柄
    FD_ZERO(&fdread);      
    //将读管道加入select句柄                 
    FD_SET(fd[0],&fdread);                  

    pid = fork();
    if(pid == -1)
    {
        printf("fork fail\n");
    }
    else if(pid == 0)
    {
        printf("this is child\n");
        close(fd[1]);

        //调用select,看规定timeout时间内,句柄集有无可读数据
        res = select(fd[0] + 1,&fdread,NULL,NULL,&timeout);
        if(res == -1)
        {
            printf("select error\n");
        }
        else if(res == 0)
        {
            printf("time out\n");
        }
        //有可读数据
        else
        {
            read(fd[0],readbuf,64);
            printf("child rev :%s\n",readbuf);
        }

        close(fd[0]);
    }
    else
    {
        printf("this is parent\n");
        usleep(5000000);
        close(fd[0]);
        write(fd[1],buf,64);
        close(fd[1]);
        //等待子进程结束
        waitpid(pid,NULL,0);
    }

    return 0;
}

2.使用poll的管道通信

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/poll.h>

int main()
{
    int     res;
    char    *buf = "this is data to trans";
    char    readbuf[64] = {0};
    int     fd[2];
    int     pid;
    int     timeout = 6000;                 //超时时间
    struct pollfd pfds;                     //poll结构体

    res = pipe(fd);
    if(res != 0)
    {
        printf("create pipe error\n");
        return 0;
    }

    //设置读管道为poll句柄
    pfds.fd     = fd[0];
    //设置poll的触发事件
    pfds.events = POLLIN | POLLPRI;

    pid = fork();
    if(pid == -1)
    {
        printf("fork fail\n");
    }
    else if(pid == 0) //child
    {
        printf("this is child\n");
        close(fd[1]);
        //规定时间内,监视pdfs这一个通道有无触发事件发生
        res = poll(&pfds,1,timeout);
        if(res == -1)
        {
            printf("poll error\n");
        }
        else if(res == 0)
        {
            printf("time out\n");
        }
        //有POLLIN或者POLLPRI事件,也意味着有数据可读
        else
        {
            read(fd[0],readbuf,64);
            printf("child rev :%s\n",readbuf);
        }

        close(fd[0]);
    }
    else
    {
        printf("this is parent\n");
        usleep(5000000);
        close(fd[0]);
        write(fd[1],buf,64);
        close(fd[1]);
        //等待子进程结束
        waitpid(pid,NULL,0);
    }

    return 0;
}

可以看出,在此程序中,select和poll在管道通信的应用中都是起到了“在超时时间内检查管道内有无可读数据”的作用。若超时时间内无数据可读,则提示超时,若有数据可读,则读出数据并打印。
父进程时隔5秒后向管道内发送数据,而程序中规定的超时时间为6秒,故不会超时,程序运行结果如下,也是5秒后子进程收到数据并打印,和一开始的程序结果相同:

wy@wy-VirtualBox:~/test/book/csdn$ ./a.out 
this is parent
this is child
child rev :this is data to trans
wy@wy-VirtualBox:~/test/book/csdn$

而当将超时时间设为5秒之内时,比如设置为2秒。那么2秒后提示超时,如下所示:

wy@wy-VirtualBox:~/test/book/csdn$ ./a.out 
this is parent
this is child
time out
wy@wy-VirtualBox:~/test/book/csdn$
Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下! Select函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明): int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 先说明两个结构体: 第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。 第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。 具体解释select的参数: int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。 struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 返回值: 负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜老越

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值