进程间通信及管道理解

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程。
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件
  • 进程控制:有些进程希望完全控制另一个进程的执行(Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

    进程间通信分类

  • 管道
  • System V进程间通信
  • POSIX进程间通信

    管道

  • 匿名管道pipe

  • 命名管道FIFO

    System V进程间通信

  • System V 消息队列

  • System V 共享内存
  • System V 信号量

    POSIX进程间通信

  • 消息队列

  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

在本篇中我们先讨论管道,在之后的文章中会对SystemV版本的进程间通信作介绍。

管道

管道是Unix中最古老的一种通信方式,它是一个进程通往另一个进程的数据流。
这里写图片描述

对于管道而言,它只能单向通信,即进程1能够通过已创建的管道将数据传达到进程2,但进程2不能通过这个管道传数据给进程1,必须重新再创建一个管道。

匿名管道
匿名管道是一个仅能在有亲缘关系的进程之间进行通信的管道(如父子进程)

#include <unistd.h>

       int pipe(int pipefd[2]);

pipefd[2]表示一个文件描述符数组,其中fd[0]表示读端,fd[1]表示写段
函数调用成功返回0,失败返回-1。

测试代码:从键盘读取数据写入管道,从管道中读取数据写到屏幕

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    int fd[2];
    int ret=pipe(fd);
    char buf[100]={0};

    if(ret<0)
    {
        perror("pipe");
        return 1;
    }
    printf("pipe OK\n");
    int len;
    while(fgets(buf,100,stdin))
    {
        len=strlen(buf);
        //write into pipe
        ssize_t write_size=write(fd[1],buf,len);
        if(write_size<0)
        {
            perror("write");
            return 1;
        }
        memset(buf,0x00,sizeof(buf));

        //read from pipe
        ssize_t read_size=read(fd[0],buf,100);
        if(read_size < 0)
        {
            perror("read");
            return 1;
        }

        //write to pipe
        if(write(1,buf,len)!=len)
        {
            perror("write");
            return 1;
        }
    }
    return 0;
}

这里写图片描述
如上我们从键盘输入一段数据到标准输入,就有相同的数据从标准输出输出。

用fork来共享管道
前面我们说到,匿名管道是用于有亲缘关系的进程间通信的,那么我们fork创建出的子进程是如何来共享管道的呢?
这里写图片描述

父进程在fork出子进程后,子进程拥有了和父进程一样的代码,当然也拥有和父进程一样的文件描述符,在fork出子进程后,父进程关闭管道的读端fd[0],子进程关闭管道的写端fd[1],从而来实现父子进程之间的管道通信,即从子进程向管道内写数据,父进程从管道内读数据。

管道的读写规则

  • 当没有数据数据可读时

    • O_NONBLOCK(阻塞打开): read 调用阻塞,进程暂停执行,一直等到有数据来为止
    • O_NONBLOCK (非阻塞打开):调用返回-1,errno值为EAGAIN
  • 当管道满的时候

    • O_NONBLOCK(阻塞打开): write 调用阻塞,进程暂停执行,一直等到有数据读走数据
    • O_NONBLOCK (非阻塞打开):调用返回-1,errno值为EAGAIN

如果所有管道写段对应的文件描述符关闭,则read返回0。
如果所有管道读端对应的文件描述符关闭,则write操作产生信号SIGPIPE,进而导致write进程退出。
当要写入的数据不大于管道的大小时,Linux保证数据写入的原子性。
当要写入的数据大于管道的大小时,Linux将不再保证数据写入的原子性。

匿名管道的特点

  • 只能用于具有亲缘关系的进程;通常管道由一个进程创建,然后该进程调用fork,父子进程之间就可用该管道。
  • 管道面向字节流(如果一次写100个,可以每次读10个这样来读)
  • 管道的生命周期是随进程。进程退出,管道在内核中所对应的资源就释放
  • 内核会对管道操作进行同步与互斥
  • 管道只能往单向方向通信,若要实现双向通信必须创建两个管道

命名管道(FIFO)

命名管道相较于匿名管道的不同,就是能够适用于任意进程间的通信。

我们可以直接在命令行输入mkfifo+文件名的方式来创建命名管道,也可以在程序中调用mkfifo函数来创建命名管道
这里写图片描述

不同的是,匿名管道在用pipe函数创建的同时也打开了,而命名管道在mkfifo创建了之后,还需用open函数打开该命名管道。

命名管道的打开规则

  • 当以只读方式打开时(O_RDONLY)

    • O_NONBLOCK(阻塞打开 O_RDONLY): read 调用阻塞,进程暂停执行,一直等到有进程以写方式打开
    • O_NONBLOCK (非阻塞打开 O_RDONLY|O_NONBLOCK ):open函数立刻返回,read会直接返回0,当有其他进程按写方式打开时,read返回-1(虽然按写方式打开,但没人写数据)
  • 当以只写方式打开时

    • O_NONBLOCK(阻塞打开 O_WRONLY): write 调用阻塞,进程暂停执行,一直等到有进程以只读方式打开
    • O_NONBLOCK (非阻塞打开 O_WRONLY|O_NONBLOCK):立即返回失败。

用命名管道实现server&client通信

server

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

int main()
{
    umask(0);
    int ret=mkfifo("./myfifo",0666);
    if(ret < 0)
    {
        perror("mkfifo");
    }
    printf("mkfifo ok!\n");

    int rfd = open("myfifo",O_RDONLY);
    if(rfd < 0)
    {
        perror("open");
    }
    printf("open ok!\n");

    char buf[1024];
    while(1)
    {
        buf[0]=0;
        printf("Please wait...\n");
        ssize_t s = read(rfd , buf, sizeof(buf)-1);
        if(s > 0)
        {
            buf[s -1] =0;
            printf("client say %s\n",buf);
        }
        else if(s == 0)
        {
            printf("read done!\n");
            return 0;
        }
        else
        {
            perror("read");
        }
    }

    close(rfd);
    return 0;
}

client

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

int main()
{
    int wfd = open("myfifo",O_WRONLY);
    if(wfd < 0)
    {
        perror("open");
    }
    printf("open ok!\n");

    char buf[1024];
    while(1)
    {
        buf[0]=0;
        printf("Please Enter...\n");
        ssize_t s = read(0 , buf, sizeof(buf)-1);
        if(s > 0)
        {
            buf[s] =0;
            write(wfd,buf,sizeof(buf)-1);
        }
        else
        {
            perror("read");
        }
    }

    close(wfd);
    return 0;
}

结果如下(server端先运行)
这里写图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值