一、进程间三大类通信
1、古老的通信方式
无名管道 有名管道 信号
2、IPC对象通信 system v(5) BSD suse fedora kernel.org
消息队列(用的相对少,这里不讨论)
共享内存
信号量集(进程间做互斥与同步semaphore)
3、socket通信
网络通信
二、管道
无名管道 ===》pipe ==》只能给有亲缘关系进程通信
有名管道 ===》fifo ==》可以给任意单机进程通信
1.管道的特性
1)管道是 半双工的工作模式
2)所有的管道都是特殊的文件不支持定位操作。lseek->> fd fseek ->>FILE*
3)管道是特殊文件,读写使用文件IO。fgets,fread,fgetc(标准IO,文本文件常用)
open,read,write,close;(文件IO,一般用这个,二进制文件常用)
2.无名管道使用注意事项
1)读端存在,一直向管道中去写(写得太快),超过64k,写会阻塞。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid>0)
{
char buf[1024]={0};
close(pipefd[0]);
memset(buf,'a',1024);
int i = 0 ;
for(i=0;i<65;i++)
{
write(pipefd[1],buf,sizeof(buf));
printf("i is %d\n",i);
}
}
else if(0 == pid)
{
close(pipefd[1]);
char buf[100]={0};
while(1)sleep(1);
}
else
{
perror("fork");
exit(1);
}
return 0;
}
2)写端存在,读管道,(写得慢,读得快)如果管道为空的话,读会阻塞。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid>0)
{
char buf[]="capper";
close(pipefd[0]);
sleep(3);
write(pipefd[1],buf,strlen(buf));
}
else if(0 == pid)
{
close(pipefd[1]);
char buf[100]={0};
read(pipefd[0],buf,sizeof(buf));
printf("pipe %s\n",buf);
}
else
{
perror("fork");
exit(1);
}
return 0;
}
阻塞3s后,才打印出结果
3)管道破裂,读端关闭,写管道出错,程序结束。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid>0)
{
char buf[]="hello,world";
close(pipefd[0]);
sleep(3);
// 管道破裂 gdb 跟踪
write(pipefd[1],buf,strlen(buf));
printf("aaaa\n");
}
else if(0 == pid)
{
close(pipefd[1]);
close(pipefd[0]);
}
else
{
perror("fork");
exit(1);
}
return 0;
}
管道破裂,程序退出
4)写端关闭,如果管道没有内容,读端返回0,read 0 ;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid>0)
{
char buf[]="hello,world";
close(pipefd[0]);
write(pipefd[1],buf,strlen(buf));
write(pipefd[1],buf,strlen(buf));
close(pipefd[1]);
}
else if(0 == pid)
{
close(pipefd[1]);
char buf[5]={0};
while(1)
{
//memset(buf,0,5);
bzero(buf,sizeof(buf));
int rd_ret = read(pipefd[0],buf,sizeof(buf)-1);
if(rd_ret<=0)
{
break;
}
printf("pipe %s\n",buf);
}
}
else
{
perror("fork");
exit(1);
}
return 0;
}
3.无名管道使用框架
创建管道 ==》读写管道 ==》关闭管道
1.无名管道 ===》管道的特例 ===>pipe函数
特性:
1.1 亲缘关系进程使用
1.2 有固定的读写端
2.3.1创建并打开管道
pipe函数
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==>无名管道的固定读端
pipefd[1] ==>无名管道的固定写端
返回值:成功 0
失败 -1;
注意事项:
无名管道的架设应该在fork之前进行。
无名管道的读写:===》文件IO的读写方式。
读: read()
写: write()
关闭管道: close();
练习:相当于cp的功能
设计一个多进程程序,用无名管道完成父子进程间的任意信息:传送,包括数字、字符串等。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int pipefd[2]={0};
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid>0)
{
close(pipefd[0]);
int fd = open("/home/linux/1.png",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[4096]={0};
int rd_ret = read(fd,buf,sizeof(buf));
if(rd_ret<=0)
{
break;
}
write(pipefd[1],buf,rd_ret);
}
close(fd);
close(pipefd[1]);
}
else if(0 == pid)
{
close(pipefd[1]);
int fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(-1==fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[1024]={0};
int rd_ret = read(pipefd[0],buf,sizeof(buf));
if(rd_ret<=0)
{
break;
}
write(fd,buf,rd_ret);
}
close(fd);
close(pipefd[0]);
}
else
{
perror("fork");
exit(1);
}
return 0;
}
运行结果:将1.png复制为2.png
验证如下问题:
1、父子进程是否都有fd[0] fd[1],如果在单一进程中写fd[1]能否直接从fd[0]中读到。可以,写fd[1]可以从fd[0]读
2、管道的数据存储方式是什么样的数据是否一直保留?
栈, 先进后出
队列形式存储 读数据 会剪切取走数据 不会保留 <先进先出>3、管道的数据容量是多少,有没有上限值。
操作系统的建议值: 512* 8 = 4k
man 7 pipe
代码测试实际值: 65536byte= 64k4、管道的同步效果如何验证?读写同步验证。
读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止
写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞结论:读写端必须同时存在,才能进行管道的读写。
5、固定的读写端是否就不能互换?
能否写fd[0] 能否读fd[1]? 不可以,是固定读写端。
4.有名管道
有名管道===》fifo ==》有文件名称的管道。文件系统中可见
框架:
创建有名管道 ==》打开有名管道 ==》读写管道==》关闭管道 ==》卸载有名管道
2.4.1 创建:mkfifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
mode 8进制文件权限//0666/0777
返回值:成功 0
失败 -1;
2.4.2 打开有名管道 open
注意:该函数使用的时候要注意打开方式,因为管道是半双工模式,所有打开方式直接决定当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
不能是 O_RDWR 方式打开文件
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数
2.4.3 有名管道的读写: 文件IO
读: read(fd-read,buff,sizeof(buff));
写: write(fd-write,buff,sizeof(buff));
2.4.4 关闭管道
close(fd);
2.4.5 卸载有名管道
remove();
int unlink(const char *pathname);
功能:将指定的pathname管道文件卸载,同时从文件系统中删除。
参数: ptahtname 要卸载的有名管道
返回值:成功 0
失败 -1
01fifo_w.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
//open 会阻塞,等到另一端读段打开,解除阻塞
int fd = open("myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
char buf[256]="hello,fifo,test";
write(fd,buf,strlen(buf));
close(fd);
return 0;
}
02fifo_r.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int ret = mkfifo("myfifo",0666);
if(-1 == ret)
{
//如果是管道文件已存在错误,让程序继续运行
if(EEXIST== errno)
{
}else
{
perror("mkfifo");
exit(1);
}
}
int fd = open("myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
exit(1);
}
char buf[256]={0};
read(fd,buf,sizeof(buf));
printf("fifo %s\n",buf);
close(fd);
//remove("myfifo");
return 0;
}
1、是否需要同步,以及同步的位置。
读端关闭 是否可以写,不能写什么原因。
写端关闭 是否可以读。结论:有名管道执行过程过必须有读写端同时存在。
如果有一端没有打开,则默认在open函数部分阻塞。2、有名管道是否能在fork之后的亲缘关系进程中使用。
结论: 可以在有亲缘关系的进程间使用。
注意: 启动的次序可能会导致其中一个稍有阻塞。3、能否手工操作有名管道实现数据的传送。
读: cat fifoname
写: echo "asdfasdf" > fifoname