进程间通信
首先我们先来了解一下进程间通信。
进程间通信目的:
1.数据传输:一个进程需要将它的数据发送给另一个进程
2.资源共享:多个进程之间共享同一份资源
3.通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件
4.进程控制:有些进程希望完全控制另一给进程的执行(如Debug进程),此时控制希望能够拦截另一个进程的所有陷入和异常,并能够即使知道它的状态改变。
进程间通信分类:
管道
匿名管道
命名管道
System IPC
System V 消息队列
System V 共享内存
System V 信号量
POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
而今天我们先学习一下管道。
首先我们要了解一下什么是管道呢?管道它是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
匿名管道
#include<unistd.h>
功能:创建一个无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码
这个时候我们来看一个例题:
例:从键盘读取数据,写⼊入管道,读取管道,写到屏幕
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(void)
{
int fds[2];
char buf[100];
int len;
if (pipe(fds) == -1)
perror("make pipe"), exit(1);
//读取数据
while (fgets(buf, 100, stdin)){
len = strlen(buf);
//写入管道
if (write(fds[1], buf, len) != len){
perror("write to pipe");
break;
}
memset(buf, 0x00, sizeof(buf));
//读取管道
if ((len = read(fds[0], buf, 100)) == -1){
perror("read from pipe");
break;
}
//写到屏幕
if (write(1, buf, len) != len){
perror("write to stdout");
break;
}
}
}
(1)用fork来共享管理管道的原理
(2)站在文件描述符角度来深度理解管道
1、父进程创建
2、父进程fork出子进程
3、父进程关闭fd[0],子进程关闭fd[1]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2] = {0};
if(pipe(fd)==-1)
{
perror("pipe");
return 1;
}
pid_t pid = fork();
if(pid==-1)
{
perror("fork");
return 2;
}
else if(pid==0)
{//child write
close(fd[0]);
char *msg="father,I am child";
int i = 5;
while(i--)
{
write(fd[1],msg,strlen(msg));
sleep(1);
}
}
else
{//father read
close(fd[1]);
char buf[1024];
while(1)
{
ssize_t s = read(fd[0],buf,sizeof(buf));
if(s>0)
{
buf[s]=0;
printf("father: %s\n",buf);
}
else if(s==0)
{
printf("child quit!!!\n");
break;
}
else
{
perror("read");
return 3;
}
}
}
return 0;
}
结果如下:
管道的特点
- 1、只能用于具有亲缘关系的进程之间进行通信(常为父子);通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可用该管道。
4、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需建立起两个管道
5、管道通信基于字节流
利用两个管道实现双向通信 如上图
管道的读写规则:
1、写端的文件描述符关闭,读端一直读
2、写端的文件描述符没关闭,但写端也不写数据,而读端一直读
3、写端一直写数据,读端不读,也不关闭它的文件描述符
4、写端写数据,而读端的文件描述符关闭
命名管道
管道应用的一个限制就是只能在具有相同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件。
首先我们要知道如何创建一个管道呢? 创建管道由两种方法,第一种是命令创建,第二种是函数创建;现在我们分别来看一下怎么创建一个管道:
命令行:
$ mkfifo filename
函数原型:
int mkfifo(const char *filename,mode_t mode);
创建成功返回0,创建失败返回-1;
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
if(mkfifo("fifo",0644)==-1)
{
perror("mkfifo");
return 1;
}
return 0;
}
用命名管道实现client/server通信:
直接看代码:
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
if(mkfifo("mypipe",0644)==-1)//创建命名管道
{
perror("mkfifo");
return 1;
}
int rfd = 0;
if((rfd = open("mypipe",O_RDONLY))==-1)//以只读方式打开管道
{
perror("open");
return 2;
}
char buf[1024];
while(1)
{
ssize_t s = read(rfd,buf,sizeof(buf)-1);//从管道中读数据
if(s>0)
{
buf[s-1]=0;
printf("client say:%s\n",buf);
}
else if(s==0)
{
printf("client quit!!!\n");
break;
}
else
{
perror("read");
return 3;
}
}
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int wfd = 0;
if((wfd = open("mypipe",O_WRONLY))==-1)//以只写方式打开管道
{
perror("open");
return 1;
}
char buf[1024];
while(1)
{
printf("Please Enter:");
//从标准输入读取数据
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
write(wfd,buf,strlen(buf));//将读取到的数据写进管道
}
else if(s==-1)
{
perror("write");
return 2;
}
}
return 0;
}
由于两个代码需要编译,这里我们写一个Makefile
代码如下:
.PHONY:all
all:client server
client:client.c
gcc -o $@ $^
server:server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
测试结果如下:
1、首先我们先编译代码,输入make即可
2、然后我们输入./server ,这时就会创建一个管道,然后再打开一个终端,输入ls,此时我们会发现管道已经创建好
3、现在我们再这个新的终端上输入./client,就可以进行通信了