进程通信的目的
- 数据传输:进程间需要数据的传输
- 资源共享:两个或多个进程共享相同资源
- 通知事件:当事件发生时需要通知另一个进程
- 进程控制:一个进程需要控制另一个进程运行时,如调试程序
进程间通信的方式
管道:匿名管道和有名管道
system V IPC: system 共享内存,system 消息队列,system 信号量
posix IPC:消息队列,共享内存,信号量,信号量集,条件变量, 读写锁
匿名管道
匿名管道特性:
匿名管道是一个伪文件,实质上是一在Linux内核借助内核缓冲区,使用环形队列实现。
匿名管道由两个文件描述符引用,返回一个文件描述符数组fd[2],其中fd[0] 为读端,fd[1]为写端。
匿名管道是半双工通信,管道中数据只能从一个进程流向另一个进程 。
匿名管道中数据遵从先入先出,并且读取后管道中数据会消失。
匿名管道所传输的是无格式的字节流。这就需要管道输入方和输出方事先约定好数据格式。
匿名管道的大小有限制。
匿名管道因为没有名字,需要通过文件描述符进行操作,因为只有具有亲缘关系的进程拷贝文件描述符表进行操作,因此只能在具有亲缘关系进程间通信(如父子进程,兄弟进程进程)
pipe函数,创建一个匿名管道
函数原形:int pipe(int fd[2]);
参数:fd: 文件描述符数组,其中fd[0]为读端,fd[1]为写端。和流的描述符一直0为读1为写
返回值:成功返回0,失败返回错误码
pipe管道读写特性:
当管道设置为阻塞属性时(默认为阻塞)
当写端文件描述符没有关闭,read 函数进行读操作时,如果管道有数据,将返回实际读取的字节数,当管道没有数据时, read函数将被阻塞,直到管道中被写入数据进行读取返回,
当所有写端文件描述符被关闭,read 函数进行读操作时,如果管道有数据,将返回实际读取的字节数,当管道没有数据时,read函数将返回0
当读端文件描述符没有关闭,当write进行写操作时,如果管道没有被写满就进行写入并返回实际写入字节数,如果管道已经被写满write将被阻塞,直到管道数据被读出有空间,才会写入数据返回写入的字节数。
当所有读端文件描述符被关闭,当write进行写操作时,会收到SIGPIPE信号
当管道设置为非阻塞属性时
当写端文件符没有关闭时,read函数进行读操作时,如果管道有数据,将返回实际读取的字节数,管道无数据时直接返回
当写端关闭时同阻塞
当读端没有关闭时,write进行写操作时,如果管道有空间将进行写入返回写入字节数,当管道满时,
当所有读端都关闭时,write进行写操作时收到SIGPIPE信号
设置非阻塞io方法:fcntl(fd, F_SETFL, O_NONBLOCK);
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
int fd[2];
if(pipe(fd) < 0)
{
perror("pipe error");
}
//管道必须在创建子进程之前进行创建,只有这样才能在创建子进程时
//拷贝父进程文件描述符表,使得父子进程都拥有两个分别指向读写的描述符
//并且指向同一管道
pid_t pid = fork();
if(pid < 0)
{
perror("creat child process failure");
}
else if(pid == 0)
{
printf("this is read process\n");
close(fd[1]);
char buff[1024] = { 0 };
int ret = read(fd[0], buff, 1023);
printf("ret:%d\nbuff:%s\n", ret, buff);
//memset(buff, 0, 1024);
//ret = read(fd[0], buff, 1023);
//printf("ret:%d\nbuff:%s\n", ret, buff);
close(fd[0]);
printf("read exit\n");
_Exit(1);
}
else if(pid > 0)
{
pid_t pid2 = fork();
if(pid2 == 0)
{
printf("this is write process\n");
close(fd[0]);
write(fd[1], "hello!!", 7);
sleep(2);
printf("write\n");
write(fd[1], "world", 5);
close(fd[1]);
printf("write exit\n");
_Exit(0);
}else
{
//父进程进行检查子进程状态
int status;
close(fd[0]);
close(fd[1]);
printf("this is parent\n");
while(1)
{
int ret = waitpid(-1, &status, WNOHANG);
if(ret < 0)
{
break;
}
else if(ret > 0)
{
if(status && 0xFF)
{
printf("sig code:%d\n", status & 0x7F);
}
}
}
}
}
return 0;
}
上面程序建立两个子进程(一个写进程一个读进程)之后关闭读端写端,写进程写入管道数据,读进程读取管道数据之后关闭读端,这时写进程进行写入数据时会收到SIGPIPE信号终止进程。