一、需要进程通信的原因
在大型的应用系统中,其进程的数量往往是非常多的,需要这些进程之间相互协作才能完成工作,而这也就需要进程之间有效地进行通信协作完成任务。
二、Linux 进程间通信的目的:
1.数据传输:
一个进程需要将它的数据发送给另一个进程
2.资源共享:
多个进程之间共享同样的资源
3.通知事件:
一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件
4.进程控制:
有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变
三、什么是POSIX
POSIX全称可移植操作系统接口,设置POSIX的目的是为了提高UNIX环境下应用程序的可移植性。
四、进程间的主要通信方式:
①管道:又分为有名管道和无名管道
②信号
③消息队列
④共享内存
⑤信号量
⑥套接字
这里我们重点介绍管道和信号,其余的方式后续会继续更新。
五、进程通信之管道
1、管道:分有名管道(FIFO)和无名管道(Pipe),其容量为64KB,其具有以下的特点:
① 管道为半双工的,数据只能向一个方向流动,当需双方进行通信时,通常需建立两个管道。
②读写端口:一个进程向管道中写内容被管道的另一端的进程读取,写的内容每次都加在管道缓冲区的尾部,每次读取数据都是从缓冲区的头部读取。
③管道对管道两端的进程而言是一个文件,但它是单独构成一种文件系统,并且只存在于内存中。
④进程试图从空管道中读取数据时,进程将会被阻塞;进程试图向满数据的管道写数据时,进程同样会被阻塞。
2、有名管道和无名管道的区别
无名管道用于父进程和子进程间的通信;有名管道用于运行位于同一系统中的任意两个进程间的通信。
3、关于无名管道的使用方法
使用无名管道时通常有以下4个步骤:
① 首先先创建管道:用pipe函数实现
函数原型:int pipe(int filedes[2]);
头文件:#include <unistd.h>
参数:新建的两个文件描述符,由filedes数组返回。filedes[0]为管道的读取端,filedes[1]为管道的写入端
返回值:执行成功时,返回0;执行失败时,返回-1。
注意:通常建立管道后,用fork()创建一个子进程,该子进程才会继承父进程所创建的管道,如果这两个语句的顺序反过来,那么子进程将不会继承管道产生的文件描述符。
② 读取管道数据:读管道数据时,读取的进程应将filedes[1]关闭(关闭写入,只开读取)
③ 写数据到管道:写数据到管道时,写入数据的进程应将filedes[0]关闭(关闭读取,只开写入)
④ 关闭管道:使用完管道后将管道关闭
4、关于有名管道的使用方法
(1)使用有名管道通常为以下6个步骤:
① 创建管道:用mkfifo函数实现
函数原型:int mkfifo(const char *pathname, mode_t mode);
头文件:#include <sys/types.h>
#include <sys/stat.h>
参数:
filename:有名管道的路径或名称
mode:管道的方式:
① O_NONBLOCK:打开FIFO时,读取的操作会立即返回,为非阻塞。
② O_RDONLY:只读
③ O_WRONLY:只写
④ O_RDWR:读写
返回值:执行成功,返回0;执行失败,返回-1
②打开管道:通常定义一个管道宏,再用open函数打开
③读管道:用read函数从管道中读取数据
④写管道:用write函数向管道中写数据
⑤关闭管道:close函数
⑥删除管道:unlink函数
(2)一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO
有名管道(FIFO)和普通文件操作的区别:
① 读取fifo文件的进程只能以“RDONLY”方式打开fifo文件
② 写fifo文件的进程只能以“WRONLY”方式打开fifo文件
③ fifo文件里面的内容被读取后就消失,普通文件的内容任然保存。
六、管道创建的实现
1、无名管道
下面是运用pipe()函数实现的无名管道的创建,创建成功返回0值。其中新的文件描述符由pipe_fd数组返回。
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
close(pipe_fd[0]);
close(pipe_fd[1]);
}
执行效果:
2、管道用于进程间的相互通信,下面是一无名管道用于父子进程之间通信的实现,注意,这里pipe()创建在fork()子进程创建之前,具体效果如下
<span style="font-size:14px;">#include <stdio.h></span>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int pipe_fd[2];
int read_num;
int write_num;
int write_chd;
int read_pa;
pid_t pid;
char r_buf[200];
memset(r_buf,0,sizeof(r_buf)); //初始化数组
if(pipe(pipe_fd) < 0) //创建管道,小于0失败
{
printf("create pipe error!\n");
return -1;
}
else
{
printf("create pipe success!\n");
}
pid = fork();//创建子进程
if(pid == 0)//子进程运行
{
close(pipe_fd[1]);//关闭管道写,只开管道读
sleep(2);//等待子进程完成关闭写端操作
if((read_num = read(pipe_fd[0],r_buf,100)) > 0)
{
printf("%d numbers read from pipe is %s\n",read_num,r_buf);
}
close(pipe_fd[0]);//关闭读操作
exit(0);//退出子进程
}
if(pid > 0) //父进程运行
{
close(pipe_fd[0]);//关闭管道读,只开管道写
sleep(2);//等待父进程完成关闭读端的操作
write_num = write(pipe_fd[1],"hello",50);//向管道写数据“hello“
if(write_num != -1)
{
printf("parent process write to pipe hello\n");
}
if(write_num < -1)
{
printf("write error!\n");
}
close(pipe_fd[1]);//关闭管道写操作
waitpid(pid,NULL,0);//等待子进程结束
exit(0);
}
return 0;
}
执行效果:
但假如我们把fork()和pipe()顺序倒过来会出现什么效果?
下面是更改的程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int pipe_fd[2];
int read_num;
int write_num;
int write_chd;
int read_pa;
pid_t pid;
char r_buf[200];
memset(r_buf,0,sizeof(r_buf)); //初始化数组
pid = fork();//创建子进程
if(pipe(pipe_fd) < 0) //创建管道,小于0失败
{
printf("create pipe error!\n");
return -1;
}
else
{
printf("create pipe success!\n");
}
if(pid == 0)//子进程运行
{
close(pipe_fd[1]);//关闭管道写,只开管道读
sleep(2);//等待子进程完成关闭写端操作
if((read_num = read(pipe_fd[0],r_buf,100)) > 0)
{
printf("%d numbers read from pipe is %s\n",read_num,r_buf);
}
close(pipe_fd[0]);//关闭读操作
exit(0);//退出子进程
}
if(pid > 0) //父进程运行
{
close(pipe_fd[0]);//关闭管道读,只开管道写
sleep(2);//等待父进程完成关闭读端的操作
write_num = write(pipe_fd[1],"hello",50);//向管道写数据“hello“
if(write_num != -1)
{
printf("parent process write to pipe hello\n");
}
if(write_num < -1)
{
printf("write error!\n");
}
close(pipe_fd[1]);//关闭管道写操作
waitpid(pid,NULL,0);//等待子进程结束
exit(0);
}
return 0;
}
执行效果:
这里子进程拷贝了fork()语句前的夫进程的数据段,但并没有继承父进程的管道,自己执行了一遍pipe(),也就是说两个进程间无法实现通信,故应注意,在创建无名管道进行父子进程间通信时创建管道应在创建子进程之前。
2、有名管道
下面是用有名管道进行的无联系的进程间的通信,一个进程发送信息,一个进程读取信息。管道在一个进程中建立另一进程打开管道即可。
发送信息进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FIFO "/root/201610/1022/myfifo" //管道宏
int main(int argc,char **argv)
{
int nwrite;
int fd;
char write_buf[100];
fd = open(FIFO,O_WRONLY|O_NONBLOCK,0); //打开管道
if(argc == 1)
{
printf("please send something!\n");
exit(1);
}
strcpy(write_buf,argv[1]); //通过命令行将参数argv[1]复制到数组write_buf中
if(fd < 0)
{
printf("open pipe error!\n");
exit(1);
}
if((nwrite = write(fd,write_buf,100)) == -1) //将数据写到管道中
{
printf("write error!\n");
}
else
{
printf("write %s to the file\n",write_buf);
}
}
接收消息进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#define FIFO "/root/201610/1022/myfifo"
int main(int argc,char **argv)
{
char buf[100];
int fd;
int nread;
if( mkfifo(FIFO,O_CREAT|O_EXCL) < 0) //创建有名管道
{
printf("cannot create fifoserver!\n");
}
printf("preparing for reading bytes....\n");
memset(buf,0,sizeof(buf));//初始化数组
fd = open(FIFO,O_RDONLY|O_NONBLOCK,0);//打开管道
if(fd == -1)
{
printf("open error!\n");
exit(1);
}
while(1)//不断获取管道里的信息
{
memset(buf,0,sizeof(buf));
if((nread = read(fd,buf,100)) == -1||(nread = read(fd,buf,100) == 0))
{
if(errno == EAGAIN)
{
printf("read error!\n");
system("./f_write running");
}
}
printf("read %s from fifo\n",buf); //打印从管道中读取到的信息
sleep(2);
}
pause();//等待中断信号到来,信号到达往下执行
unlink(FIFO);//删除管道
}
执行效果:
上面箭头处为发消息进程发送后读取进程获得的信息并打印
上面为发消息进程发送消息后的效果