进程间通信——管道通信

目录

 1 管道概念

 2 无名管道(pipe)只能给有亲缘关系进程通信

步骤

注意事项 

3 有名管道(fifo) 可以给任意单机进程通信

步骤

注意事项


1 管道概念

管道是UNIX 系统IPC 的最古老形式, 并且所有UNIX 系统都提供此种通信机制。管道有下
面两种局限性:
一、 历史上, 它们是半双工的( 即数据只能在一个方向上流动) 。现在, 某些系统提供全双工管道, 但是为了最佳的可移植性, 我们决不应预先假定系统使用此特性。
二、它们只能在具有公共祖先的进程之间使用。通常, 一个管道由一个进程创建, 然后该
进程调用fork, 此后父、子进程之间就可应用该管道。。

尽管有这两种局限性, 半双工管道仍是最常用的IPC 形式.。

管道的特性:
    1、管道是 半双工的工作模式
    2、所有的管道都是特殊的文件不支持定位操作。
    3、管道是特殊文件,读写使用文件IO。(open,read,write,close)

 2 无名管道(pipe)只能给有亲缘关系进程通信

无名管道: 大小 64k
函数接口
int pipe(int pipefd[2]);
功能
创建一个用来通信的无名管道(在内核中)
参数
pipefd:存放文件描述符数组空间首地址
pipefd[0]:读管道文件描述符
pipefd[1]:写管道文件描述符
返回值
成功返回0 
失败返回-1 

步骤

创建管道 == 》 读写管道 ==》关闭管道 

注意事项 

管道中至少有一个写端:
1.如果管道中有数据,直接读取 
2.如果管道中没有数据,阻塞等待,直到有数据写入,再读取数据
管道中没有写端:
1.如果管道中有数据,直接读取
2.如果管道中没有数据,不阻塞等待,直接返回
管道中至少有一个读端:
1.向管道中写入数据,如果没有写满,则直接写入
2.如果写满(64k),阻塞等待数据读出,才能继续写入
管道中没有读端:
1.向管道中写入数据会产生管道破裂的错误

 下面我通过一个代码示例来分析这个无名管道

                父子进程利用无名管道对文件进行传输,这里我对一个图片src.jpg利用管道传输生成拷贝出一个dst.jpg

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


int main(int argc, char const *argv[])
{
     pid_t pid;
     int pipefd[2],ret;
     ret = pipe(pipefd);
     if(ret < 0)
     {
          perror("fail to pipe");
          return -1;
     }
     pid = fork();

     if(pid > 0)//父进程写
     {
          close(pipefd[0]);
          int fd;
          ssize_t nret;
          fd = open("./src.jpg",O_RDONLY);
          char tmpbuf[1024] = {0};
          if(-1 == fd)
          {
               perror("fail open");
               return -1;
          } 
          while (1)
          {
               nret = read(fd,tmpbuf,sizeof(tmpbuf));
               if(0 >= nret)break;
               write(pipefd[1],tmpbuf,nret);
          }
          
          close(pipefd[1]);
          close(fd);
          wait(NULL); 
     }
     else if(0 == pid)//子进程读
     {
          close(pipefd[1]);
          int fd;
          ssize_t nret;
          fd = open("./dst.jpg",O_WRONLY | O_CREAT | O_TRUNC,0664);
          if(-1 == fd)
          {
               perror("fail open");
               return -1;
          }
          char tmp[1024] = {0};
          
          while (1)
          {
               nret = read(pipefd[0],tmp,sizeof(tmp));
               if(0 >= nret)break;
               write(fd,tmp,nret);
          }
          
          close(pipefd[0]);
          close(fd);
          exit(0);
     }
     else
     {    
          perror("fail to fork");
     }
     return 0;
}

可以看出这个和文件io的操作十分像,只是利用了管道的功能对两个父子进程进行了进程间的通信

代码运行后我们可以看一看src和dst的大小一模一样都为706103

 毋庸置疑而且图片也一样

3 有名管道(fifo) 可以给任意单机进程通信

函数接口:
int mkfifo(const char *pathname, mode_t mode);
功能
创建一个有名管道
参数
pathname:有名管道的路径
mode:有名管道的权限
返回值
成功返回0 
失败返回-1 

步骤

1、创建:mkfifo()

2、打开有名管道 open()

3、管道的读写: 文件IO

    读: read(fd-read,buff,sizeof(buff));
    写: write(fd-write,buff,sizeof(buff));

4、关闭管道:close(fd);

5、卸载管道:remove();

注意事项

有名管道必须读写两端同时加入后才能继续向下执行,否则以只读或只写方式
打开,会发生阻塞(等待另一端的加入) 。

以O_RDONLR打开时 ,管道在open处阻塞
以O_WRONLY打开时 ,管道在open处阻塞

当两端同时打开时,才解除阻塞。

然而我用两个不同的.c文件来利用有名管道,进行终端的聊天(两个c文件,A_B.c由进程知识书写,B_A.c由线程知识写的) .

A_B.c

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
     pid_t pid;
     int ret1 = mkfifo("./A_B", 0664); // 创建有名管道
     int ret2 = mkfifo("./B_A", 0664); // 创建有名管道
     if ((-1 == ret1 || -1 == ret2) && errno != EEXIST)
     {
          perror("fail to mkfifo");
          return -2;
     }
     pid = fork();
     if (pid > 0) // 父进程a->b(A发送)
     {
          char tmpbuf[1024] = {0};
          int fa_b = open("./A_B", O_WRONLY); // 打开管道文件(写)
          while (1)
          {
               fgets(tmpbuf, sizeof(tmpbuf), stdin);
               if (!strcmp("quit\n", tmpbuf))break;
               write(fa_b, tmpbuf, strlen(tmpbuf) + 1);
          }
          close(fa_b);
          remove("A_B");
          return 0;
     }
     else if (0 == pid) // 子进程b->a(A接收)
     {
          char tmpbuf[1024] = {0};
          int fb_a = open("./B_A", O_RDONLY); // 打开管道文件(读)
          while (1)
          {
               int nret = read(fb_a, tmpbuf, sizeof(tmpbuf));
               if (!strcmp("quit\n", tmpbuf) || nret <= 0)break;
               printf("\33[32mB->A\33[0m: %s", tmpbuf);
          }
          close(fb_a);
          remove("./B_A");
          return 0;
     }
     else
     {
          perror("fail to fork");
          return 0;
     }
}

 B_A.c

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
void *A_B(void *arg) // a->b(B接收)
{
     char tmpbuf[1024] = {0};
     int fa_b = open("./A_B", O_RDONLY); // 打开管道文件(读)
     while (1)
     {
          int nret = read(fa_b, tmpbuf, sizeof(tmpbuf));
          if (!strcmp("quit\n", tmpbuf))break;
          printf("\33[33mA->B\33[0m: %s", tmpbuf);
     }
     close(fa_b);
     remove("./A_B");
     exit(0);
}
void *B_A(void *arg) // b->a(B发送)
{
     char tmpbuf[1024] = {0};
     int fb_a = open("./B_A", O_WRONLY); // 打开管道文件(写)
     while (1)
     {
          fgets(tmpbuf, sizeof(tmpbuf), stdin);
          if (!strcmp("quit\n", tmpbuf))break;
          write(fb_a, tmpbuf, strlen(tmpbuf) + 1);
     }
     close(fb_a);
     remove("B_A");
     exit(0);
}
int main(int argc, char const *argv[])
{
     pthread_t tid1, tid2;
     int ret1 = mkfifo("./A_B", 0664); // 创建有名管道
     int ret2 = mkfifo("./B_A", 0664); // 创建有名管道
     if ((-1 == ret1 || -1 == ret2) && errno != EEXIST)
     {
          perror("fail to mkfifo");
          return -2;
     }
     pthread_create(&tid1, NULL, A_B, NULL);
     pthread_create(&tid2, NULL, B_A, NULL);

     pthread_join(tid1, NULL);
     pthread_join(tid2, NULL);
     return 0;
}

就这样,当两个代码同时运行,就会创建出两个管道在进行半双工的收发通信,如图(缺少的文字是因为,半角全角中文在终端删除的bug)。

 

猜你喜欢

转载自blog.csdn.net/m0_58193842/article/details/128554609