进程间通信(1)——管道和命名管道
进程间通信包括本地进程间通信和网络进程间通信
-
网络进程间通信:不同计算机的进程间通信,是基于socket进程的通信
-
本地进程间通信:同一台计算机系统中各进程间的通信,包括下面五种
【Linux下的各种进程间通信方式】
- 管道
- 命名管道
- 消息队列
- 共享内存
- 信号量
【1】管道
(1)概述
-
管道实现数据以数据流的方式在进程间流动。在系统中其相当于文件系统上的一个文件,来缓存所要传输的数据。
-
在某些特性上又不同于文件,例如,当数据读出后,则管道中就没有数据了,但文件没有这个特性。
-
顾名思义,匿名管道在系统中没有实名,并不可以在文件系统中以任何方式看到该管道。它只是进程的一种资源,会随着进程的结束而被系统清除。
-
创建一个管道时生成了两个文件描述符,但对于管道中所使用的文件描述符并没有路径名,也就是不存在任何意义上的文件,它们只是在内存中跟某一个索引节点相关联的两个文件描述符
-
shell中,经常使用(l)来连接两个甚至多个命令
(2)管道的创建
Linux下使用pipe函数创建一个匿名管道,函数原型如下:
#include <unistd.h>
int pipe(int fd[2]);
返回: 成功返回0,出错返回-1
fd[2]:长度为2的文件描述符数组
- fd[0]是读出端的文件描述符
- fd[1]是写入端的文件描述符
- 当函数成功返回后,则自动维护了一个从fd[1]到fd[0]的数据通道
(3)管道的关闭
管道的关闭使用的是基于文件描述符close函数。
(4)一个使用管道的例子,create_pipe.c:
程序中先使用函数pipe建立管道,并使用管道传输数据,在程序结束部分,释放掉管道占用的文件资源(两个文件描述符)
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd[2];/*管道的文件描述符数组*/
char str[256];
if((pipe(fd)) < 0)
{
printf("create the pipe failed!\n");
exit(1);/*出错退出*/
}
write(fd[1],"create the pipe successfully!\n",31);
/*向管道写入端写入数据*/
read(fd[0],str,sizeof(str));
/*从管道输出端读出数据*/
printf("%s",str);
printf("pipe file description are %d , %d\n",fd[0],fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
运行结果:
hyx@hyx-virtual-machine:~/test$ ./create_pipe
create the pipe successfully!
pipe file description are 3 , 4
(5)管道的读写
使用read和write对管道进行操作
- 管道读取规则:
(1)如果管道写端不存在,则认为读到了数据末尾,读函数返回的读出字节数为0
(2)写端存在时,
- 若请求字节数 > PIPE_BUF,则返回管道中现有的数据字节数
(PIPE_BUF在include/linux/limits.h中定义)
- 若请求字节数 < PIPE_BUF,则返回管道中现有数据字节数(此时管道中数据量小于请求数据量)
或返回请求字节数(此时管道中数据量不小于请求数据量)
- 管道写入规则:
- 管道缓冲区一旦有空闲区域,写进程就会立即试图向管道写入数据,如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞
(6)管道在父子进程中的应用
parent _ pipe _ child.c (使用pipe和fork组合实现父子进程通信)
先使用pipe函数建立管道,再使用fork函数创建子进程
在父子进程中维护管道的数据方向,并在父进程中向子进程发送消息,在子进程中接收消息并输出到标准输出
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <limits.h>
#define BUFSIZE PIPE_BUF /*管道默认一次性读写的数据长度*/
void err_quit(char *msg)
{
printf(msg);
exit(1);
}
int main(void)
{
int fd[2];
char buf[BUFSIZE] = "hello my child!\n";/*写入管道的缓冲区*/
pid_t pid;/*定义进程ID的变量*/
int len;
if((pipe(fd)) < 0)/*创建管道*/
err_quit("pipe failed\n");
if((pid = fork()) < 0)/*创建一个子进程*/
err_quit("fork failed\n");
else if(pid > 0)
{
close(fd[0]);/*父进程中关闭管道的读出端*/
write(fd[1],buf,strlen(buf));
exit(0);
}
else
{
close(fd[1]);/*子进程中关闭管道的写入端*/
len = read(fd[0],buf,BUFSIZE);/*子进程从管道中读出数据*/
if(len < 0)
err_quit("process failed when read a pipe\n");
else
write(STDOUT_FILENO,buf,len);/*输出到标准输出*/
exit(0);
}
}
hyx@hyx-virtual-machine:~/test$ hello my child!
(7)管道在兄弟进程间的应用
brother_pipe.c:
-
管道在兄弟进程间应用时,应该先在父进程中建立管道,然后调用fork函数创建两个子进程,在兄弟子进程中维护管道的数据方向
-
在父进程中创建管道,并使用fork函数创建2个子进程。在第一个子进程中发送消息到第2个子进程,第2个子进程读出消息并处理。在父进程中,由于并不使用管道通信,所以什么都不做,直接关闭了管道的两端并退出
-
创建第一个子进程时,并没有关闭管道的两端,创建第二个子进程时,关闭了管道
-
创建第二个子进程时,子进程可以继承存活的管道,而不是一个两端已经关闭的管道,否则下面的读操作无法完成
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <limits.h> #define BUFSIZE PIPE_BUF /*管道默认一次性读写的数据长度*/ void err_quit(char *msg) { printf(msg); exit(1); } int main(void) { int fd[2]; char buf[BUFSIZE] = "hello my brother!\n";/*缓冲区*/ pid_t pid; int len; if((pipe(fd)) < 0) /*创建管道*/ err_quit("pipe failed\n"); if((fork()) < 0) /*创建第一个子进程*/ err_quit("fork failed\n"); else if(pid = 0) /*子进程中*/ { close(fd[0]); /*关闭不使用的文件描述符*/ write(fd[1],buf,strlen(buf));/*写入消息*/ exit(0); } if((pid = fork()) < 0) err_quit("fork failed\n"); else if(pid > 0) /*父进程中*/ { close(fd[0]); close(fd[1]); exit(0); } else /*第二个子进程中*/ { close(fd[1]); /*关闭不使用的文件描述符*/ len = read(fd[0],buf,BUFSIZE);/*读取消息*/ write("STDOUT_FILENO,buf,len");/*将消息输出到标准输出*/ exit(0); } return 0; }
【2】命名管道
(1)概述
-
命名管道(named pipe)也称为FIFO,它是一种文件类型,创建一个FIFO文件类似于创建一个普通文件
-
FIFO解决了只有具有亲缘关系的进程间才能通信。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存放于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。
命名管道区别于管道主要是以下两点:
-
命名管道可以用于任何两个进程间的通信,而并不限制这两个进程同源,因此命名管道的使用比管道的使用要方便灵活
-
命名管道作为一种特殊的文件存放于文件系统中,而不是像管道一样存放于内存(使用完毕后消失)。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会消失
(2)FIFO管道通过mkfifo创建,函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
返回:成功返回0,出错返回-1
第一个参数是一个普通的路径名,即创建后FIFO文件的名字
第二个参数与打开普通文件的open()函数中的mode参数相同
如果mkfifo第一个参数是一个已经存在的路径名时,则返回EEXIST错误,所以般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用FIFO,如closeread、write等
(3)关于FIFO的程序示例
create_FIFO.c
演示了如何使用mkfifo创建一个FIFO
-
程序中从命令行参数中得到一个文件名,然后使用mkfifo函数创建FIFO文件。新创建的FIFO只具有读写权限。由于FIFO文件的特性,所以它被隐性地规定不具有执行权限。
#include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> int main(int argc,char *argv[]) { mode_t mode = 0666; if(argc != 2) { printf("USEMSG:Create_FIFO{FIFO name}\n"); exit(1); } if((mkfifo(argv[1],mode)) < 0) { perror("failed to mkfifo!\n"); exit(1); } else { printf("you successfully create a FIFO name is:%s\n",argv[1]); } return 0; } 运行结果: hyx@hyx-virtual-machine:~/test$ ./create_FIFO USEMSG:Create _FIFO{FIFO name} hyx@hyx-virtual-machine:~/test$ ./create_FIFO testfifo you successful create a FIFO name is : testfifo hyx@hyx-virtual-machine:~/test$
(4)命名管道的读写
- 与普通文件类似,可以使用系统调用open打开一个命名管道,使用read和write函数对命名管道进行读写,使用close关闭一个命名管道
(5)使用FIFO进行两个进程间通信的例子
write_fifo.c和read_fifo.c
-
在程序write_fifo.c中打开一个名为fifo1的FIFO文件,并分10次向这个FIFO中写入数据。在程序read_fifo.c中先打开fifo1文件,然后读取里面的数据并输出到标准输出。
-
运行程序时,要先使用 mkfifo -m 666 fifo1 创建fifo1文件
-
因为分别进行读和写,所以在两个shell中进行
write_fifo.c
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <fcntl.h>
#define BUFES PIPE_BUF
int main(void)
{
int fd;
int n,i;
char buf[BUFES];
time_t tp;
printf("I am %d\n",getpid());
if((fd = open("fifo1",O_WRONLY)) < 0)
{
printf("Open failed!\n");
exit(1);
}
for(i = 0;i < 10;i++)
{
time(&tp);/*提取系统当前时间*/
n = sprintf(buf,"write_fifo %d sends %s",getpid(),ctime(&tp));/*使用sprintf函数向buf中格式化写入进程ID和时间值*/
printf("send msg:%s\n",buf);
if((write(fd,buf,n+1)) < 0)
{/*写入到FIFO中*/
printf("write failed!\n");
close(fd);/*关闭FIFO文件*/
exit(1);
}
sleep(3);/*进程睡眠3秒,便于观察*/
}
close(fd);/*关闭FIFO文件*/
exit(0);
}
read_fifo.c
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <fcntl.h>
#define BUFES PIPE_BUF
int main(void)
{
int fd;
int len;
char buf[BUFES];
mode_t mode = 0666; /*FIFO文件的权限*/
if((fd=open("fifo1",O_RDONLY)) < 0)/*打开FIFO文件*/
{
printf("open failed!\n");
exit(1);
}
while((len = read(fd,buf,BUFES)) > 0)/*开始进行通信*/
printf("read_fifo read:%s",buf);
close(fd);/*关闭FIFO文件*/
exit(0);
}
运行结果:
hyx@hyx-virtual-machine:~/test$ mkfifo -m 666 fifo1
hyx@hyx-virtual-machine:~/test$ ./write_fifo
I am 2964
send msg:write_fifo 2964 sends Thu Dec 3 16:29:26 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:29 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:32 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:35 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:38 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:41 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:44 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:47 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:50 2015
send msg:write_fifo 2964 sends Thu Dec 3 16:29:53 2015
hyx@hyx-virtual-machine:~/test$ ./read_fifo
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:26 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:29 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:32 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:35 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:38 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:41 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:44 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:47 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:50 2015
read_fifo read:write_fifo 2964 sends Thu Dec 3 16:29:53 2015