进程间通信-管道

进程间通信介绍

什么是进程间通信:

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

进程间通信的前提:不同的进程之间必须可以看到一份公共资源。

进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程

  • 资源共享:多个进程之间共享同样的资源。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信发展

  • 管道

  • System V进程间通信

  • POSIX进程间通信

具体分类:

管道

  • 匿名管道pipe

  • 命名管道

System V IPC

  • System V 消息队列

  • System V 共享内存

  • System V 信号量

POSIX IPC

  • 消息队列

  • 共享内存

  • 信号量

  • 互斥量

  • 条件变量

  • 读写锁

管道间通信的前提:必须让这两个进程看到同一份资源

管道

什么是管道

  • 管道是Unix中最古老的进程间通信的形式

  • 我们把从一个进程连接到另一个进程的一个数据流称为一个管道。

管道特点

  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

  2. 管道提供流式服务

  3. 一般而言,进程退出,管道释放,所以管道的生命周期随进程

  4. 一般而言,内核会对管道操作进行同步与互斥

  5. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

    匿名管道

匿名管道的特点

  1. 管道只允许具有血缘关系的进程间通信,如父子进程间的通信。

  2. 管道只允许单向通信。

  3. 管道内部保证同步机制,从而保证访问数据的一致性。

  4. 面向字节流

  5. 管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。

匿名管道的创建

匿名管道是由系统调用函数pipe()实现的。

函数简单实现:

#include<unistd.h>
int pipe(int pipefd[2])//pipe创建管道成功返回0,出错返回-1

pipefd参数返回两个文件描述符,pipefd[0]指向管道的读端,pipefd[1]指向管道的写端。pipefd[1]的输出是pipefd[0]的输入。

实现

#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main()
{
  int pipeid[2]={0};
  pipe(pipeid);
  pid_t id=fork();
  if(id==0)
  {
    //child write
    close(pipeid[0]);
    const char* msg="i am child...!\n";
    int count=0;
    while(1)
    {
      write(pipeid[1],msg,strlen(msg));
      printf("child:%d\n",count++);
      //    if(count == 5){
      //        close(pipefd[1]);
      //        break;
      //    }
​
    }
    exit(2);
  }
  else if(id>0)
  {
    //parent read
    
    close(pipeid[1]);
    char buffer[64];
    int count = 0;
    while(1){
       ssize_t s = read(pipeid[0], buffer, sizeof(buffer)-1);
       if(s > 0){
       buffer[s] = 0;
​
                printf("father get message: %s\n", buffer);
                sleep(1);
            }
            if(count++ == 3){
                close(pipeid[0]);
                break;
            }
        }
        int status = 0;
        waitpid(id, &status, 0);
        printf("child exit get a sig, sig number:%d\n", status & 0x7F);
    
    
  }
  else
    perror("fork fail!");
  return 0;
}
 

运行结果

 

匿名管道读取数据的四种情况

1.读端不读,写段一只写

 

2.写端不写,但是读端一直读

 

3.读端一直读,且fd[0]保持打开,而写端写了一部分数据不写了,并且关闭fd[1]。

 

如果一个管道读端一直在读数据,而管道写端的引⽤计数⼤于0决定管道是否会堵塞,引用计数大于0,只读不写会导致管道堵塞。

4.读端读了一部分数据,不读了且关闭fd[0],写端一直在写且f[1]还保持打开状态

 

总结:

如果一个管道的写端一直在写,而读端的引⽤计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只写不读再次调用write会导致管道堵塞;

如果一个管道的读端一直在读,而写端的引⽤计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只读不写再次调用read会导致管道堵塞;

而当他们的引用计数等于0时,只写不读会导致写端的进程收到一个SIGPIPE信号,导致进程终止,只写不读会导致read返回0,就像读到⽂件末尾⼀样。

命名管道

特点

  1. 可以是非亲缘进程之间。

  2. 读写必须同时执行,否则阻塞

命名管道的创建

命名管道是由mkfifo函数创建 的 ,打开用open

#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const *pathname,mode_t mode);//成功返回0,失败返回-1.

mkfifo函数创建出一个管道文件,例:

 

利用这个管道文件实现进程间通信

参数解释:

​ char* pathname:要创建的管道文件所在路径及文件名,

​ mode_t mode:创建文件的初始权限。使用八进制数字表示,例:0666。

实现

//server.c 写端
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_FILE "./fifo"
int main()
{
  umask(0); //只是在这个程序里设置为了0,不会影响系统的umask值。
  if(-1==mkfifo(FIFO_FILE,0666))
  {
    perror("mkfifo error!");
    return 1;
  }
  int fd=open(FIFO_FILE,O_RDONLY);
  if(fd>=0)
  {
    char buf[64];    //设置缓冲区
    while(1)
    {
      ssize_t s= read(fd,buf,sizeof(buf)-1);
      if(s>0)
      {
        buf[s]=0;     //设置文件结束当读到0时读取结束。
        printf("client# %s",buf);
      }
      else if(s==0)
      {
        printf("client quit, me too!\n");
        break;
      }
      else{
        perror("读取错误!");
        break;
      }
    }
  }
}
//client.c 读端
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_FILE "./fifo"
int main()
{
  int fd=open(FIFO_FILE,O_WRONLY);
  if(fd>=0)
  {
    char buf[64];    //设置缓冲区
    while(1)
    {
      printf("请输入你要发送的信息:");
      fflush(stdout);   //刷新缓冲区的字符到标准输出。
      ssize_t s= read(0,buf,sizeof(buf)-1);
      if(s>0)
      {
        buf[s]=0;     //设置文件结束当读到0时读取结束。
         write(fd,buf,s);
      }
    }
  }
}

运行结果:

 

读端 写端

管道如何实现通信

  1. 父进程创建管道。得到两个文件描述符指向管道的两端。

 

2.父进程fork出子进程,子进程也有两个文件描述符指向同一管道。

 

3.父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值