您的位置:首页 > 运维架构 > Linux

Linux-进程通信(管道)

2018-03-28 16:04 288 查看

进程间通信目的

数据传输:一个进程需要将他的数据传输给其他进程。

资源共享:多个进程之间共享相同的资源。

通知事件:一个进程需要向另一个进程发送消息,通知它发生了某种事件。

进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

管道

管道是unix中最古老的进程间通信的方式。

管道的定义:我们把从一个进程连接到另一个进程的一个数据流称为“管道”



如图所示,注意的是数据的”传输”都是在内核中进行。

管道分为两种:匿名管道与命名管道。

匿名管道

函数原型:



参数:文件描述符数组,

fd[0]:表示读端

fd[1]:表示写端

返回值:成功返回0,失败返回-1与错误代码。

举例说明;

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

int main()
{
int fd[2];
char buf[100];
int len;

int ret=pipe(fd);
if(ret==-1)
{
perror("pipe");
exit(0);
}

//read from stdin
while(1)
{
fgets(buf,100,stdin);
len=strlen(buf);
//write into pipe
ssize_t s=write(fd[1],buf,len);
if(s!=len)
{
perror("write");
break;
}
memset(buf,0,sizeof(buf));

//read from pipe
len=read(fd[0],buf,100);
if(len==-1)
{
perror("read");
break;
}
//write to stdout
s=write(1,buf,len);
if(s!=len)
{
perror("write");
break;
}
}
}


该例实现了,从键盘(标准输入)中获取数据,写入到管道里,再从管道里读出,写入标准输出中(显示屏)。

结果如下:



原理可以用下图来解释:



fork之后的父子进程各自都有一个读端和写端。



将父进程的写端关掉,子进程的读端关掉。

也可以从文件描述符的角度来说明:

父进程有一个files_struct结构体,里有一组文件指针数组。



父进程fork出子进程后,子进程也有一套同样的内容。



最后一步,父进程关闭读端,子进程关闭写端。



可以注意到,不管从哪个角度来理解管道这种机制,都是从父进程fork子进程开始的,所以将这种管道称为“匿名管道”,具有亲缘关系的。

匿名管道特点

只能用于具有共同祖先的进程(具有亲缘关系的,兄弟进程也可以)之间通信。

一般而言,进程退出,管道释放。所以管道生命周期随进程。

一般而言,内核会对管道进行同步与互斥。

管道提供面向字节流服务,数据只能向一个方向传输,双方传输时,需要建起两个管道。

管道读写规则

写方一只写,读方不读:write调用阻塞,直到有进程读走数据。

写方不写,读方一直读:read调用阻塞,直到有数据来为止。

写方将写端关闭,读方一直读:读方将读完然后返回0;

写方一直写,读方将读端关闭:会产生SIGPIPE信号,终止write进程。

命名管道

匿名管道的一个限制就是必须在具有共同祖先的进程间通信。

如果想在毫无相关的进程间交换数据,就要使用命名管道。

命名管道是一种特殊类型的文件。

创建一个命名管道有两种方式:

在命令行创建:

mkfifo filename


命名管道可以从程序里创建,相关函数是;

int mkfifo(const char8 filename,mode_t mode);


成功返回0,失败返回-1;



举例说明:

server.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.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
{
printf("client quit\n");
break;
}

}
close(fd);
return 0;
}
client.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.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;

}


实现效果如下:





匿名管道与命名管道的区别

匿名管道有函数pipe创建并打开

命名函数有mkfifo创建,打开用open

命名管道与匿名管道之间唯一的区别在他们创建于打开的方式不同,一旦这些工作做好之后,他们具有相同的的语义。

最大的区别是:命名管道时文件在硬盘上有对应的文件,而匿名管道是在内核中实现的。

打开规则

为读打开时:

无写方:阻塞直到有相应进程来打开该FIFO

有写方:返回成功;

为写打开:

无读方:阻塞直到有相应进程为读而打开该FIFO

有读方:返回失败;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: