Linux加油站 - 进程通信(1)

匿名管道

     

        管道也叫无名 (匿名)管道,是 UNIX 系统中IPC(进程间通信)的最古老形式,所有的 UNIX系统都支持这种通信机制。匿名管道只能在具有公共祖先(父子进程或具有亲缘关系的进程)的进程之间使用。

         管道其实是内核中的一块缓冲区,进行通信时要先把数据拷贝到内核中,然后通过管道传输后,到达另一端的内核态,再次拷贝到用户态,从而完成通信(通常这种机制也是管道效率较低的表现)。

        

        为什么匿名管道只能用于亲缘关系间通信?

        父进程调用fork() 创建的子进程会复制父进程的struct files_struct,同时在这里面fd的数组(也就是文件描述符集合)会复制一份,但是fd指向的struct file对于同一个文件还是只有一份,这样就做到了,两个进程各有两个fd指向同一个struct file的模式,两个进程就可以通过各自的fd写入和读取同一个管道文件实现跨进程通信了。

         下面看看管道的有关接口:

#include <unistd.h>

int pipe(int pipefd[2]);
    功能:创建一个匿名管道,用来进程间通信。
    参数:int pipefd[2] 这个数组是一个传出参数。(会返回两个文件描述符,一个读端,一个写端)
              pipefd[0] 对应的是管道的读端
              pipefd[1] 对应的是管道的写端
    返回值:
            成功 0
            失败 -1

  

        用匿名管道实现父子进程的通信,子进程发送数据给父进程,父进程读取到数据输出:

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

int main() {

    int pipefd[2];     //先定义好两个文件描述符,作为创建管道函数的参数,0为读端,1为写端
    pipe(pipefd);     //创建一个匿名管道

    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {
        close(pipefd[1]);
        printf("i am parent process \n ");        // 父进程

        char buf[1024] = {0};
        int len = read(pipefd[0], buf , sizeof(buf));  
        printf("parent recive : %s\n "  , buf);

    } else if(pid == 0){        // 子进程
        close(pipefd[0]);
        printf("i am child process \n");

        sleep(5);           // 子进程睡眠十秒,父进程是读不到数据的,匿名管道默认是阻塞的
        char * str = " hello, i am child";
        write(pipefd[1] , str , strlen(str));      
    }
    return 0;
}

 编译运行后,可以看到运行结果:

        

 补充一点:

        上面写原理的时候也提到了,创建管道后父子进程是这个样子的:

        仔细查看代码,可以发现父子进程内的代码都提前调用了close()关闭其中管道的其中一端,这是因为管道只能一端写入,另一端读出,如果基于上图这种模式会造成混乱,因为父进程和子进程都可以写入,也都可以读出,通常的方法是父进程关闭读取的fd,只保留写入的fd,而子进程关闭写入的fd,只保留读取的fd,变成下图:

        总结一下匿名管道的特点:

read( )读管道时:
        管道中有数据时:read( )返回实际读到的字节数。
        管道中无数据时:
            - 如果写端被全部关闭,read( )返回0,相当于读到文件的末尾。
            - 如果写端没有完全关闭,read( )阻塞等待。

write( )写管道时:
        管道读端全部被关闭:进程异常终止,进程收到SIGPIPE信号。
        管道读端没有全部关闭:
            -管道已满,write( )阻塞。
            -管道没有满,write( )将数据写入,并返回实际写入的字节数。

有名管道

        有名管道也叫命名管道,或者叫FIFO文件,不同于匿名管道之处的是:提供了一个路径名与之关联,以 FIFO的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,即使与 FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO文件 相互通信。

        有名管道可以用命令或者接口创建 :

//用命令mkfifo 创建一个有名管道
mkfifo 管道名字

//用函数创建有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
    参数:
        - pathname: 管道名称的路径
        - mode: 文件的权限 , 是一个八进制的数
    返回值:成功返回0,失败返回-1,并设置错误号

        用命令mkfifo myfifo 创建一个有名管道文件 myfifo:

        模拟一下两个进程的通信,一个进程往管道写数据write.c , 一个进程往管道读数据read.c,下面看看write.c 的实现:

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

int main() {
    // 调用open()拿到管道文件的文件描述符,且以只写的方式打开管道 , 如果你要写 ,那就把读关掉
    int fd = open("myfifo", O_WRONLY);           

    // 写数据
    for(int i = 0; i < 100; i++) 
    {
        char buf[1024];
        sprintf(buf, "hello, %d\n", i);
        printf("write data : %s\n", buf);

        write(fd, buf, strlen(buf));
        sleep(1);
    }
    close(fd);  
    return 0;
}

        接下来的read.c 的实现:

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

int main() {
    int fd = open("myfifo", O_RDONLY);        //以只读的权限打开管道文件

    // 读数据
    while(1) 
    {
        char buf[1024] = {0};
        int len = read(fd , buf  ,sizeof(buf));
        
        if(len > 0)     // 大于0则表示读取成功,返回的是读到的字节数数量。
        {
            printf("recv buf : %s\n" , buf);
        }
        else if(len == 0)     //等于0证明管道的写端已经关闭了,数据也读完了。    
        {
            printf("写端断开连接  \n");
            break;
        }
        else if(len == -1)  //调用失败
        {
            printf("异常退出  \n");
        }
    }
    close(fd);
    return 0;
}

        编译运行一下,如果只运行write.c ,可以看到是处于阻塞等待的情况,因为读端还没有打开:

         运行一下 read.c ,可以看到开始通信,写端在往管道写数据:

         读端在往管道读数据:

         关闭任意一方都会使得程序结束。

        最后总结一下有名管道的特点(与匿名管道类似):

        当一个进程以只读的方式打开一个管道时会阻塞,直到另外一个进程以只写的方式打开管道后才接触阻塞。(总之就是只有一方打开管道默认是阻塞的)

     

read( )读管道时:

        管道中有数据,read返回实际读到的字节数。

        管道中无数据:

                -管道写端被全部关闭,read返回0,(相当于读到文件末尾)。

                -写端没有全部被关闭,read阻塞等待。

write( )写管道时:

        管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)

        管道读端没有全部关闭:

                -管道已经满了,write会阻塞。

                -管道没有满,write将数据写入,并返回实际写入的字节数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值