目录
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)。