进程间通信(一):管道

让毫不相关的进程想办法让他们看到一份公共资源

管道的 目的
  1. 数据传输
  2. 资源共享(多个进程共享)
  3. 通知事件
  4. 进程 控制(控制之前进行沟通)

管道: 将一个进程连接到另一个进程的数据流称为管道

管道的特点
  1. 管道只能单向传输,如果需要双向传输,建立两个管道即可
  2. 管道的生命周期随进程变化,进程退出,管道释放
  3. 管道提供字节流服务
  4. 内核会对管道的操作进行同步和互斥
  5. 一般管道大小为4096

匿名管道(只允许有血缘关系的两个进程相互通信):pipe
一些概念
  1. 数据不一致:当两个毫不相干的进程有对于一个资源可能一个正在写,另一个正在读,就是读完了之后就会发生问题(即自己的低读写影响到了别人)
  2. 临界资源:两个毫不相干的进程看到的资源(一块资源)
  3. 临界区:两个进程中访问临界资源的那一部分代码
  4. 互斥:在任何一个时间点,有且只有一个进程在访问临界资源
  5. 同步:规定在访问临界资源时具有一种合法的顺序性
  6. 访问临界资源的 原子性 :在进行某些资源的操作时,只能做完或者不做,不能做一半
  7. 饥饿问题:如果一个进程因为优先级较低,长时间申请不到资源,会发生饥饿问题

管道的四种情况
  1. 如果读方不读,写方一直写的话,管道总会被写满,写满之后,写方就会停止写入
  2. 如果写方不写,读方会一直阻塞式的读数据
  3. 如果写方一直在写,当写方不写了,将自己的写文件描述符关闭,读方会一直读到管道结束
  4. 当写方一直在写,读方不读并且将读文件描述符关闭,读方进程直接终止并且向操作系统发送13号信号(SIGNPIPE)


用fork来共享管道的原理图,将父进程的写文件描述符关掉,将子进程的读文件描述符关掉就行成了一个单向通信的管道


#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  int fd[2];
  int ret = pipe(fd);
  if(ret < 0)
  {
    perror("pipe");
    return 1;
  }
  pid_t id = fork();
  int count = 0;
  if(id == 0)
  {//child->w
    //关闭读文件描述符
    //每隔一秒写一次
    close(fd[0]);
    const char *msg = "hello f,i am c\n";
    while(1)
    {
      write(fd[1],msg,strlen(msg));
      printf("%d\n",count++);
      sleep(1);
    }
  }
  else
  {//father->r;
    //关闭写文件描述符
    //将读到的内容写出来
    close(fd[1]);
    char buf[64];
    while(1)
    {
      ssize_t s = read(fd[0],buf,sizeof(buf)-1);
      if(s > 0)
      {
        buf[s] = '\0';
        printf("f say:%s\n",buf);
        break;
      }
    }
  }
}

命名管道(是一种特殊类型的文件 .fifo):mkfifo
命名管道就是两个进程打开一个公共的文件一个只写另一个只读,由此来实现进程通信的功能

创建一个命名管道

mkfifo fifo//(文件名)

int main()
{
    mkfifo('./fifo',0644);//扩号前是路径
    return 0;
}


匿名管道和命名管道的区别
  1. 匿名管道由pipe创建并打开
  2. 命名管道由mkfifo创建,打开用open
  3. 创建和打开方式不同


用命名管道实现server和client通信
首先是Makefile
.PHONY:all

all:server client
           
server:server.c
        gcc -o $@ $^

client:client.c
        gcc -o $@ $^

.PHONY:clean

clean:
        rm -f server client


先创建一个对公共文件只读的进程 server.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>


int main()
{
  //创建一个命名管道
  if(mkfifo("./fifo",0644) < 0)
  {
    printf("mkfifo error!!\n");
    return 1;
  }

  //打开临界文件
  int fd = open("./fifo",O_RDONLY);//以只读方式打开
  if(fd < 0)
  {
    perror("open");
    return 2;
  }

  char buf[64];
  while(1)
  {
    ssize_t s = read(fd,buf,sizeof(buf)-1);
    //读到东西了,输出
    if(s > 0)
    {
      buf[s] = '\0';
      printf("server # %s\n",buf);
    }
    //读完了
   else if(s == 0)
    {
      printf("client quit!!\n");
      break;
    }
  }
  close(fd);
  return 0;
}

在创建一个对公共文件进行只写操作的进程
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>


int main()
{

  //打开临界文件
  int fd = open("./fifo",O_WRONLY);//以只写方式打开
  if(fd < 0)
  {
    perror("open");
    return 2;
}

  char buf[64];
  while(1)
  {
    printf("Please enter:");
    scanf("%s",buf);
    if(strcmp(buf,"quit") == 0)
    {
      break;
    }
    write(fd,buf,strlen(buf));
  }

  close(fd);
  return 0;
}


那儿我们在运行client和server文件的时候,那我们先运行client和server程序呢?
其实管道文件有这么一个特点,管道文件只有在同时具有读和写的特性时,操作系统才会个这个管道文件真正的分配内存,而且如果只有读或写的特性时,那么这个文件便会阻塞式的等待有文件来以写的方式来打开它,那么操作系统才会分配内存,所以先打开那个都无所谓

通过一个进程打开一个文件对它进行写操作的时候,而另一个进程打开它并对他进行读操作的时候我们就完成了进程通信的操作,就比如我们的服务器端到客户端的消息传输,我们可以画个图理解一下



猜你喜欢

转载自blog.csdn.net/qq_36767247/article/details/80412470