进程间通信 day36

七:进程间通信

一:分类

1、古老的通信方式
无名管道 有名管道 信号

2、IPC对象通信 system v BSD suse fedora kernel.org unix
消息队列(用的相对少,这里不讨论)
共享内存
信号量集
3、socket通信
网络通信

​ 线程信号,posix sem_init

特列:古老的通信方式中信号是唯一的异步通信
所有的通信方式中共享内存是唯一的最高效

二:管道

管道 – >无名管道、有名管道

无名管道 – >pipe – >只能给有亲缘关系进程通信
有名管道 – >fifo – >可以给任意单机进程通信

管道的特性
1、管道是 半双工的工作模式
2、所有的管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE*
3、管道是特殊文件,读写使用文件IO。fgets,fread,fgetcopen,read,write,close;


1,读端存在,一直向管道中去写,超过64k,写会阻塞。
2,写端是存在的,读管道,如果管道为空的话,读会阻塞。

3.管道破裂:如果 管道的读端关闭(即没有进程/线程再读),而 写端再写,此时操作系统会向写入者发送 SIGPIPE 信号,导致程序崩溃(默认行为)。

4.read 0 :写端关闭,读端读,读完内容之后还会向下读,如果管道没有内容,read 0 ;

//2和4的区别
| 状态       | 管道是否有数据   | 写端状态   | `read()`返回值    | 说明          |
| ------ 	| ------- 		 | ----- 	 | ----------- 		 | ----------- |
| 管道为空   | ❌ 没数据       | ✅ 打开   | 阻塞或 -1(非阻塞) | 只是没数据,等待    |
| 管道内容为空 | ❌ 没数据     | ❌ 已关闭  | 0           	  | 表示 EOF,不能再读 |

使用框架:创建管道 ==》读写管道 ==》关闭管道

1、无名管道 ===》管道的特例 ===>pipe函数
特性:1.1 亲缘关系进程使用
1.2 有固定的读写端

三:创建/打开管道:pipe

#include <unistd.h>
int pipe(int pipefd[2]);

功能:创建并打开一个无名管道
参数:pipefd[0] ==> 无名管道的固定读端
pipefd[1] ==> 无名管道的固定写端
返回值:成功 0, 失败 -1;

注意事项:1、无名管道的架设应该在fork之前进行。

无名管道的读写:===》文件IO的读写方式。
读: read()
写: write()

关闭管道: close();

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

int	main(int argc, char **argv)
{
    int fd[2]={0};
    int ret = pipe(fd);
    pid_t pid = fork();
    if(pid>0)
    {

        //父进程写管道,不读.所以关闭管道的读段.
        close(fd[0]);
        char buf[]="hello";
        sleep(3);
        write(fd[1],buf,strlen(buf));
        exit(0);

    }
    else if(0 == pid)
    {
         //子进程读管道,不写.所以关闭管道的写段.
        close(fd[1]);
        char buf[50]={0};
        // 读阻塞. 父进程会等待一会,在写管道 .
        read(fd[0],buf,sizeof(buf)); 
        printf("pipe %s\n",buf);
        exit(0);
    }
    else  
    {
        perror("fork");
        exit(1);
    }

    //system("pause");
    return 0;
}
3.1写堵塞
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  int fd[2] = {0};
  int ret = pipe(fd);
  pid_t pid = fork();
  if (pid > 0)
  {
    //父进程写管道,不读.所以关闭管道的读段.
    close(fd[0]);
    int i = 0;
    char buf[1024] = {0};
    memset(buf, 'a', sizeof(buf));
    //这个地方会写阻塞, 管道大小64K 
    for (i = 0; i < 65; ++i)
    {
      write(fd[1], buf, sizeof(buf));
      printf("i  is %d\n", i + 1);
    }
  }
  else if (0 == pid)
  {
    //子进程读管道,不写.所以关闭管道的写段.
    close(fd[1]);
    int i = 5;
    while (i--)
    {
      sleep(1);
    }
  }
  else
  {
    perror("fork");
    exit(1);
  }

  // system("pause");
  return 0;
}
3.2管道破裂
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  int fd[2] = {0};
  int ret = pipe(fd);
  pid_t pid = fork();
  if (pid > 0)
  {
    //父进程写管道,不读.所以关闭管道的读段.
    close(fd[0]);
    char buf[] = "hello";
    printf("father id:%d\n", getpid());
    sleep(5);  //确保子进程 关闭管道的读段
    //借助gdb 观测管道破裂
    write(fd[1], buf, strlen(buf));  // 管道破裂 ,当前进程结束,异常
    printf("------------\n");
  }
  else if (0 == pid)
  {
    printf("child %d\n", getpid());
    //子进程读管道,不写.所以关闭管道的写段.
    close(fd[1]);
    close(fd[0]);  //读段关闭
    int i = 5;
  }
  else
  {
    perror("fork");
    exit(1);
  }

  // system("pause");
  return 0;
}
3.3cp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
int	main(int argc, char **argv)
{
    int fd[2]={0};
    int ret = pipe(fd);
    pid_t pid = fork();
    if(pid>0)
    {
        close(fd[0]);
      int srcfd = open("/home/linux/1.png",O_RDONLY);
      if(-1 == srcfd )
      {
        perror("father open");
        exit(1);
      }
      int num =0;
      while(1)
      {
        char buf[4096]={0};
        int ret = read(srcfd,buf,sizeof(buf));
        if(ret<=0)
        {
            time_t tm;
            time(&tm);
            printf("发送结束,num %d, %lu\n",num,tm);
            break;
        }
        write(fd[1],buf,ret);
        num+=ret;
      }
        close(srcfd);
        close(fd[1]);
        exit(0);
    }
    else if(0 == pid)
    {
        close(fd[1]);
      
        int dstfd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
        if(-1 == dstfd )
      {
        perror("child open");
        exit(1);
      }
        int num = 0 ;
      while(1)
      {
        char buf[4096]={0};
        int ret = read(fd[0],buf,sizeof(buf));
        if(ret<=0)
        {
            time_t tm;
            time(&tm);
            printf("读取数据结束,%d, tm:%lu\n",num,tm);
            break;
        }
        write(dstfd,buf,ret);
        num+=ret;
      }
      close(fd[0]);
      close(dstfd);
      exit(0);
    }
    else  
    {
        perror("fork");
        exit(1);
    }

    //system("pause");
    return 0;
}

四:管道的特性

1.管道的数据存储方式:栈, 先进后出
队列形式存储 读数据会剪切取走数据不会保留,先进先出

2.管道的数据容量:建议值: 512* 8 = 4k,实际值:65536byte= 64k

3.管道的同步效果如何验证?

结论:读写端必须同时存在,才能进行管道的读写。

//读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止 
//写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞

4.固定的读写端能否互换:不可以,是固定读写端(返回值都不一样)

五:创建有名管道mkfifo

有名管道===》fifo ==》有文件名称的管道。文件系统(逻辑可以当文件写)中可见

框架:创建有名管道 ==》打开有名管道 ==》读写管道==》关闭管道 ==》卸载有名管道

#include <sys/types.h>
#include <sys/stat.h>
 remove();
int mkfifo(const char *pathname, mode_t mode);

功能:在指定的pathname路径+名称下创建一个权限为 mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
mode 8进制文件权限。
返回值:成功 0, 失败 -1;

2、打开有名管道 open
注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定当前进程的读写方式。
一般只有如下方式:

int fd-read = open("./fifo",O_RDONLY); //==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); //==>fd 是固定写端

不能是 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数

六:有名管道的一些问题

1、是否需要同步,以及同步的位置。
读端关闭 是否可以写,不能写什么原因。
写端关闭 是否可以读。

结论:有名管道执行过程过必须有读写端同时存在。
	  如果有一端没有打开,则默认在open函数部分阻塞。

2、有名管道是否能在fork之后的亲缘关系进程中使用。
结论: 可以在有亲缘关系的进程间使用。
注意: 启动的次序可能会导致其中一个稍有阻塞。

3、能否手工操作有名管道实现数据的传送。
读: cat fifoname
写: echo “asdfasdf” > fifoname
strcat

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值