为什么要学习进程通信?
因为复杂程序都是多任务的,进程间需要相互协作,但进程间的内存空间又是独立的,所以需要掌握进程的通信让他们相互协作。
管道的概念:
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道分
无名管道和
有名管道
无名管道:只能用于父子进程之间通信
1.创建函数:
头文件:
#include <unistd.h>
函数原型:
int pipe(int pipefd[2]);
返回值:成功返回0,失败返回-1
pipefd[0] ---》读端
pipefd[1] ---》写端
注意: ①当管道中没有数据时,读阻塞
②当管道中的数据写满时,写阻塞
③管道文件不知lseek函数
④管道中的数据是先进先出的
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:
1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
-----------------------------------------------------------------------------------------
程序实例1:父进程写入数据到管道,子进程从管道读出数据并打印
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
int pipefd[2];
char *p = "hello world\n";
char buf[100] = {0};
if(pipe(pipefd) == -1)
{
printf("创建管道出错!\n");
return 0;
}
pid_t pid = fork();
if(pid > 0)
{
close(pipefd[0]);//父进程关闭读端
int ret1 = write(pipefd[1],p,strlen(p));//往管道写入数据
if(ret1 < 0)
printf("写入管道失败!\n");
else
printf("写入成功!\n");
wait(NULL);//等待子进程退出
close(pipefd[1]);//写完数据后关闭写端
}
else if(pid == 0)
{
close(pipefd[1]);//子进程关闭写端
int ret2 = read(pipefd[0],buf,sizeof(buf));//读取管道中的数据
if(ret2 < 0)
printf("读取管道失败!\n");
else
printf("读取成功!\n");
//write(STDOUT_FILENO,buf,ret2);//输出到标准输出,即打印显示,与下面等价
printf("buf = %s\n",buf);
close(pipefd[0]);//读取完数据后关闭读端
}
else
{
printf("创建进程失败!\n");
return 0;
}
return 0;
}
-------------------------------------------------------------------------------------------------------
实例程序2:
练习:使用管道实现父子进程间通信,完成:ls -l | wc –l。假定父进程实现ls,子进程实现wc。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
int fd[2];
pipe(fd);
pid = fork();
if (pid == 0)
{ //子进程
close(fd[1]); //子进程从管道中读数据,关闭写端
dup2(fd[0], STDIN_FILENO); //让wc从管道中读取数据
execlp("wc", "wc", "-l", NULL); //wc命令默认从标准读入取数据 ,wc -l 命令是统计行数
close(fd[0]);
}
else if(pid >0)
{
//父进程
close(fd[0]); //父进程向管道中写数据,关闭读端
dup2(fd[1], STDOUT_FILENO); //将ls的结果写入管道中
execlp("ls", "ls","-l", NULL); //ls输出结果默认对应屏幕
close(fd[1]);
}
else
{
printf("创建进程失败!\n");
exit(1);
}
return 0;
}
可见运行程序和在命令行输入命令的结果是一样的。
----------------------------------------------------------------------
有名管道:可以实现同一个系统中任意两个进程通信
有名管道的创建有两种
①命令行创建:mkfifo /home/gec/myfifo ---》创建管道文件
②管道创建函数
头文件:
#include <sys/types.h>
#include <sys/stat.h>
函数原型:
int mkfifo(const char *pathname,mode_t mode);
参数一:需要创建的管道路径
参数二:管道的权限,0666
返回值:成功返回0,失败返回-1
注意:①不能再windows文件夹中创建管道文件
②open函数当以只读方式或只写方式打开管道时,没有写端或读端打开管道,open会阻塞
open()函数中O_NONBLOCK---》以不阻塞的方式打开管道,加入打开写端时没有读端被打开,则打开写端失败。
③read函数本身是阻塞的,当在管道中没有写端时,则read函数不阻塞。
④write函数,当管道文件写满时,write会阻塞,为了保护写入的数据
--------------------------------------------------------
创建一个有名管道,并实现两个进程间(不是父子进程)通信
进程写端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
mkfifo("/home/gec/pi",0666);
int fd = open("/home/gec/pi",O_WRONLY);
if(fd > 0)
printf("open ok\n");
printf("in while\n");
while(1)
{
char buf[50] = {0};
printf("input\n");
scanf("%s",buf);
write(fd,buf,strlen(buf));
}
close(fd);
}
进程读端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
mkfifo("/home/gec/pi",0666);
int fd = open("/home/gec/pi",O_RDONLY);
if(fd >0)
printf("open ok\n");
printf("in while \n");
while(1)
{
char buf[50] = {0};
int ret = read(fd,buf,sizeof(buf));
if(ret >0)
printf("ret = %s\n",buf);
}
close(fd);
}
进程写端编译:
进程读端编译:
在/home/gec目录下可以看到管道文件pi