Linux下进程间通信——管道

一、现在在Linux中使用较多的进程间通信方式主要有以下几种:

(1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道除具有管道所觉有的功能外,它还允许无亲缘关系进程间的通信。

(2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。

(3)消息队列(Message Queue):消息队列是消息的链接表,包括Posix消息队列和Syetem V消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新消息;对消息队列有度权限的进程可以从消息队列中读取消息。

(4)共享内存(Shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新,这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。

(5)信号量(Semaphore):主要作为进程之间以及同一进程的不同线程之间的同步和互斥手段。

(6)套接字(Socket):这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

二、管道

1.无名管道

(1)无名管道创建和关闭

        无名管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,fds[1]固定用于写管道,这样就构成半双工的通道。而关闭无名管道只需将这两个文件描述符通过close()关闭。

扫描二维码关注公众号,回复: 2623902 查看本文章

(2)无名管道创建函数——pipe()

pipe()函数语法要点
所需头文件 #include <unistd.h>
函数原型 int pipe(int fd[2])
函数传入值 fd[2]:管道的两个文件描述符,之后就可以直接操作这两个文件描述符
函数返回值 成功:0
失败:-1

(3)无名管道读写说明

        通常父进程先是创建一个管道,再通过fork()函数创建一个子进程,该子进程会继承父进程中所创建的管道,此时父进程有读写两个文件描述符,子进程也有两个文件描述符。

        “子进程写入父进程读取”:子进程的读描述符关闭,父进程的写描述符关闭。

        “父进程写入子进程读取”:子进程的写描述符关闭,父进程的读描述符关闭。

(4)使用实例

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

#define BUFF_SIZE_MAX 256
#define DELAY_TIME    1

int main()
{
    pid_t pid;
    pid_t fds[2];
    char buff[BUFF_SIZE_MAX];
    const char data[] = "Pipe test Program";
    int real_read,real_write;

    if(pipe(fds) < 0)
    {
        printf("pipe error.\n");
        exit(1);
    }

    pid = fork();

    if(pid == -1)
    {
        printf("fork error.\n");
        exit(1);
    }
    else if(pid == 0)
    {
        memset((void *)buff,0,BUFF_SIZE_MAX);
        sleep(DELAY_TIME * 3);
        close(fds[1]);

        if((real_read = read(fds[0],buff,BUFF_SIZE_MAX)) > 0)
        {
            printf("Child read:%s\n",buff);
        }
        close(fds[0]);
        exit(0);
    }
    else
    {
        close(fds[0]);
        sleep(1);
        if((real_write = write(fds[1],data,sizeof(data))) > 0)
        {
            printf("Father write:%s\n",data);
        }

        close(fds[1]);
        waitpid(pid,NULL,0);
        exit(0);
    }
}

2.标准流管道

(1)标准流管道函数说明

        Linux中提供了popen()函数和pclose()函数,它的作用是将无名管道所做的工作进行合并,大大减少了代码的编写量。所做工作如下: 

        ①创建一个管道

        ②fork()一个子进程

        ③在父子进程中关闭不需要的文件描述符

        ④执行exec函数族调用

        ⑤执行函数中所指定的命令

        但是同时也存在不一定的缺点,即它不如pipe()那么灵活,popen()创建的管道必须使用标准IO函数进行操作,不能使用read()、write()等不带缓冲的IO函数。

(2)语法要点

popen()函数语法要点
所需头文件 #include <stdio.h>
函数原型 FILE *popen(const char *command,const char *type)
函数传入值 command:指向的是一个以null结束符结尾的字符串,这个字符串包含一个shell命令,并被送到/bin/sh以-c参数执行,即由shell来执行
type:

"r":文件指针连接到command的标准输出,即该命令的结果产生输出

"w":文件指针连接到command的标准输入,即该命令的结果产生输入

函数返回值 成功:文件流指针
出错:-1
pclose()函数语法要点
所需头文件 #include <stdio.h>
函数原型 int pclose(FILE *stream)
函数传入值 stream:要关闭的文件流
函数返回值 成功:返回由popen()所执行的进程的退出码
出错:-1

(3)使用实例

include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFSIZE 1024

int main(void)
{
    FILE *fp;
    char *cmd = "ls -l";
    char buf[BUFSIZE];

    if((fp = popen(cmd,"r")) < 0)
    {
        printf("popen error.\n");
        exit(1);
    }

    while((fgets(buf,BUFSIZE,fp)) != NULL)
    {
        printf("%s",buf);
    }
    pclose(fp);
    exit(0);
}

3.有名管道——FIFO

(1)有名管道说明

        有名管道和无名管道的区别在于,无名管道只适用于亲缘进程之间的通信,而有名管道可以使互不相关的两个进程实现彼此通信。有名管道在创建之后,在系统中是可见的,进程间可以像操作普通文件一样对其进行操作。但是FIFO是严格遵循先进先出规则的,对管道及FIFO的读取总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持lseek()等文件定位操作。

        有名管道的阻塞和非阻塞打开读写问题:

阻塞与非阻塞
读进程 写进程
若该管道是阻塞打开,且当前FIFO内没有数据,则对读进程而言将一直阻塞到有数据写入。 若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入。
若该管道是非阻塞打开,则不论FIFO内是否有数据,读进程都会立即执行读操作。即如果FIFO内没有数据,则读函数将立刻返回。 若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败。

(2)函数语法

mkfifo()函数语法要点
所需头文件

#include <sys/types.h>

#include <sys/state.h>

函数原型 int mkfifo(const char *filename,mode_t mode)
函数传入值 filename:要创建的管道
mode: O_RDONLY:读管道
O_WRONLY:写管道
O_RDWR:读写管道
O_NONBLOCK:非阻塞
O_CREAT:如果该文件不存在,那么就创建一个新的文件,并用第三个参数为其设置权限
O_EXCL:如果使用O_CREAT时文件存在,那么可返回错误消息。这一参数可测试文件是否存在
函数返回值 成功:0
出错:-1
FIFO相关的出错信息
EACCESS 参数filename所指定的目录路径无可执行的权限
EEXIST 参数filename所指定的文件已存在
ENAMETOOLONG 参数filename的路径名称太长
ENOENT 参数filename包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数filename路径中的目录存在但却非真正的目录
EROFS 参数filename所指定的文件存在于只读文件系统

(3)使用实例

fifo_write.c

/* fifo_write.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define MYFIFO   "./myfifo"
#define BUFSIZE  PIPE_BUF

int main(int argc,char *argv[])
{
    int fd;
    char buff[BUFSIZE];
    int real_write;

    if(argc <= 1)
    {
        printf("Usage:./pipe_write string\n");
        exit(1);
    }
    sscanf(argv[1],"%s",buff);

    if((fd = open(MYFIFO,O_WRONLY)) < 0)
    {
        printf("Open MYFIFO error.\n");
        exit(1);
    }

    if((real_write = write(fd,buff,sizeof(buff))) < 0)
    {
        printf("write error.\n");
        exit(1);
    }
    printf("Write process write:%s\n",buff);
    close(fd);
    exit(0);
}

fifo_read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define MYFIFO   "./myfifo"
#define BUFSIZE  PIPE_BUF

int main(int argc,char *argv[])
{
    int fd;
    char buff[BUFSIZE];
    int real_read;

    if(access(MYFIFO,F_OK) == -1)
    {
        if((mkfifo(MYFIFO,0666) < 0) && (errno != EEXIST))
        {
            printf("mkfifo error.\n");
            exit(1);
        }
    }

    if((fd = open(MYFIFO,O_RDONLY)) < 0)
    {
        printf("Open MYFIFO error.\n");
        exit(1);
    }

    while(1)
    {
        memset(buff,0,sizeof(buff));
        if((real_read = read(fd,buff,BUFSIZE)) > 0)
        {
        {
            printf("Read process read:%s\n",buff);
        }
    }
    close(fd);
    exit(0);
}
操作:打开两个终端,先运行./fifo_read,另一个终端运行./fifo_write 字符串
write将字符串输入到FIFO中,read无限循环读取FIFO中的数据并打印。

三、有名管道的IO复用 实例

       在有名管道的基础上加入了select()函数:

/* pipe_select.c */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#define FIFO1        "in1"            //有名管道1名称
#define FIFO2        "in2"            //有名管道2名称
#define BUFSIZE      512              //BUF大小
#define IN_FILES     3                //文件描述符个数
#define DELAY_TIME   60               //超时时间60s
#define MAX(a,b)     ((a>b)?(a):(b))  //取最大值宏

int main()
{
    int fds[IN_FILES];          //分别为标准输入、有名管道1、有名管道2
    char buf[BUFSIZE];          //缓存区
    int i,res,real_read,maxfd;  //real_read表示read实际返回值,maxfd表示fd的最大值
    struct timeval tv;          //时间结构体,用于select超时判断
    fd_set inset,tmp_inset;     //inset读文件描述符集合,tmp_inset是inset的COPY版

    if(access(FIFO1,F_OK) == -1)   //判断FIFO1是否存在
    {
        if((mkfifo(FIFO1,0666) < 0) && (errno != EEXIST)) //创建有名管道1
        {
            printf("FIFO1:mkfifo error.\n");
            exit(1);
        }
    }
    if(access(FIFO2,F_OK) == -1)   //判断FIFO2是否存在
    {
        if((mkfifo(FIFO2,0666) < 0) && (errno != EEXIST)) //创建有名管道2
        {
            printf("FIFO2:mkfifo error.\n");
           exit(1);
        }
    }

    fds[0] = 0; //0表示标准输入
    if((fds[1] = open(FIFO1,O_RDONLY|O_NONBLOCK)) < 0)  //打开FIFO1,这里是非阻塞打开
    {
        printf("FIFO1:open error.\n");
        exit(1);
    }
    printf("FIFO1:open succeed.\n");
    if((fds[2] = open(FIFO2,O_RDONLY|O_NONBLOCK)) < 0)  //打开FIFO2,这里是非阻塞打开
    {
        printf("FIFO2:open error.\n");
        exit(1);
    }
    printf("FIFO2:open succeed.\n");

    maxfd = MAX(MAX(fds[0],fds[1]),fds[2]);   //获得文件描述符最大值
    FD_ZERO(&inset);                          //清空文件描述符集
    for(i=0;i<IN_FILES;i++)
    {
        FD_SET(fds[i],&inset);                //将三个输入加入文件描述符集
    }
    FD_SET(0,&inset);                         //将0加入文件描述符集合

    tv.tv_sec = DELAY_TIME;                   //设置时间结构体,秒
    tv.tv_usec = 0;                           //微秒

    //循环判断三个输入是否在文件描述符集中
    while(FD_ISSET(fds[0],&inset) || FD_ISSET(fds[1],&inset) || FD_ISSET(fds[2],&inset))
   {
        tmp_inset = inset;                               //COPY文件描述符集
        res = select(maxfd+1,&tmp_inset,NULL,NULL,&tv);  //select返回准备好的文件描述符个数

        switch(res)
        {
            case -1:                                     //select出错
            {
                printf("select error.\n");
                return 1;
            }
            break;

            case 0:                                      //select超时
            {
                printf("time out.\n");
                exit(1);
            }
            break;

            default:                                     
            {
                for(i=0;i<IN_FILES;i++)                       
                {
                    if(FD_ISSET(fds[i],&tmp_inset)) //判断三个文件描述符是否在准备好的集合中
                    {
                        memset(buf,0,BUFSIZE);      //清空buf
                        real_read = read(fds[i],buf,BUFSIZE);
                        if(real_read < 0)
                        {
                            if(errno != EAGAIN)
                                return 1;
                        }
                        else if(real_read == 0)
                        {
                            printf("real_read = 0\n");
                            close(fds[i]);
                            FD_CLR(fds[i],&inset);
                        }
                        else
                        {
                            if(i == 0) //0表示终端
                            {
                                if(buf[0] == 'q' || buf[0] == 'Q') //当终端输入q或Q时退出
                                {
                                    return 1;
                                }
                            }
                            else
                            {
                                //打印FIFO1或FIFO2内容
                                buf[real_read] = '\0';
                                printf("%s\n",buf);
                            }
                        }
                    }
               }
            }
            break;
        }
    }
    return 0;
}

操作:打开三个终端,先在主终端中执行./pipe_select创建两个有名管道,退出。此时两个有名管道已创建。在另外两个终端中分别执行“cat > in1”和“cat > in2”命令,然后再次在主终端中执行./pipe_select

猜你喜欢

转载自blog.csdn.net/q1449516487/article/details/81477165