前言说明:
宿主主机系统:window 7 旗舰版
虚拟机:VMware 10.0.4
操作平台:Ubuntu 12.04
本文代码在个人电脑可以运行,不确保在其他平台可以正确运行
这两天在学进程通信,学到管道通信,弄了有两天,今天才算能弄个明白,赶紧写来保存方便然后查看,如果能帮到大家就更好了。
1、介绍:管道文件是linux下的一种特殊缓存文件(linux的文件其实都是对应底层的一种设备,管道文件和普通文件,一般对应的是我们的磁盘设备)所谓缓存就是只会存在于进程执行的时候,进程关闭管道文件也关闭了,这与普通文件不一样,普通文件会一直存在的。管道文件是linux特有的,windows下是没有的,所以当你在linux的共享目录下建立管道文件是不可行的(特别注意,管道文件没有文件后缀,里面也没有保留东西,她就好像是自来水管,这一刻流进去的,下一秒就会流出来,不会停留在里面的)
无名管道:是指没有文件节点的管道文件,只能用于有亲缘关系的进程间的通信
有名管道:是指有文件节点的管道文件,能用于没有亲缘关系和有亲缘关系的进程间的通信
划重点:什么是文件节点(看的很多博客都没有说到这个问题提一下)
linux下的管道文件是一种树形结构的,树形结构我们都知道,要找到当前内容的下一个内容我们需要知道一个根节点,通过根节点来遍历找到下一个对象。而我们上面说的文件加点就是这个根节点。因为无名管道没有根节点,要想找到一个对象,必须直接找到她,或者通过其中的亲缘关系找到她,就好比如,你跟你父亲之间不需要第三个中间的人就可以直接认识通信一样。(关于无名管道会专门写一篇blog,这里主要介绍有名管道)
有名管道有文件节点的意思即是说,两个进程不需要任何关系也可以通信,因为他们有节点联系,可以通过节点遍历找到需要通信的两个进程,然后进行通信。这个关系就好比如,你跟我认识,但是我不认识你朋友,我跟你的朋友交流我需要通过你这个节点来找到你朋友,才能建立通信。
2、创建有名管道
mkfifo函数
头文件
#include<sys/types.h>
#include<sys/stat.h>
原型
int mkfifo(const char *pathname, mode_t mode)
成功返回0,失败返回-1
第一个参数是存放的有名管道的路径,包含了有名管道文件的名字,例如:"/tmp/myfifo"的意思就是,在tmp目录下创建了一个名为myfifo的管道文件。但是,如果你写成"/mnt/hgfs/share/myfifo"就会失败,这个是共享目录,链接到windows所以会失败。
第二个参数是读写权限,是个八进制的数,这个以后会花时间写,本文的读写权限为0777(可读可写可执行)
创建成功的管道文件,就可以把他当做是普通文件一样进行操作(但是本质上两者不同)可操作如下:
open()
read()
write()
close()
3、实例 有不少的文章说管道文件只能以读写的方式打开,这个说法是正确的
1、假设有名管道文件以读或写的方式打开。我们知道管道文件是有出入口的(有名管道和普通文件一样共用一个出入口,只要一个文件标识符,无名管道有两个标识符,一个入口一个出口,分开的。)读写的方式打开,就代表,一个进程自己把东西写进管道,再读出来给自己用,这个意义就不大了,管道是用来给两个进程的通信,不是这样用的。网上的说法说“只能以读或写的方式打开“是对的
2、但是有些人在代码实现上面就出现了错误 点击打开链接
写操作
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <string.h> int main() { const char *fifo_name = "/tmp/my_fifo"; int pipe_fd = -1; int data_fd = -1; int res = 0; const int open_mode = O_WRONLY | O_NONBLOCK; int bytes_sent = 0; char buffer[PIPE_BUF + 1]; int bytes_read = 0; if(access(fifo_name, F_OK) == -1) { printf ("Create the fifo pipe.\n"); res = mkfifo(fifo_name, 0777); if(res != 0) { fprintf(stderr, "Could not create fifo %s\n", fifo_name); exit(EXIT_FAILURE); } } printf("Process %d opening FIFO O_WRONLY\n", getpid()); pipe_fd = open(fifo_name, open_mode); printf("Process %d result %d\n", getpid(), pipe_fd); printf("%d\n",pipe_fd); if(pipe_fd != -1) { bytes_read = 0; data_fd = open("Data.txt", O_RDONLY); if (data_fd == -1) { close(pipe_fd); fprintf (stderr, "Open file[Data.txt] failed\n"); return -1; } bytes_read = read(data_fd, buffer, PIPE_BUF); buffer[bytes_read] = '\0'; while(bytes_read > 0) { res = write(pipe_fd, buffer, bytes_read); if(res == -1) { fprintf(stderr, "Write error on pipe\n"); exit(EXIT_FAILURE); } bytes_sent += res; bytes_read = read(data_fd, buffer, PIPE_BUF); buffer[bytes_read] = '\0'; } close(pipe_fd); close(data_fd); } else exit(EXIT_FAILURE); printf("Process %d finished\n", getpid()); exit(EXIT_SUCCESS); }
pipe_fd 是定义的文件操作符,我们知道,open函数打开成功返回的是0,失败返回-1;上面返回-1说明文件打开失败
为什么会打开失败呢?因为程序没有阻塞在open()函数里面,程序一直执行到后面直接close文件了。有名管道文件关闭了就不存在内容了,所以返回失败。
说明:源代码没有该打印语句,个人加上去测试的,具体点击链接查看别人的源代码
怎么修改呢?如下:
const int open_mode = O_WRONLY | O_NONBLOCK;
修改为
const int open_mode = O_WRONLY
结果:
可以看见,程序暂停了,阻塞在open函数里面,这才算是成功打开的操作
读操作
#include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <limits.h> #include <string.h> int main() { const char *fifo_name = "/tmp/my_fifo"; int pipe_fd = -1; int data_fd = -1; int res = 0; int open_mode = O_RDONLY | O_NONBLOCK; char buffer[PIPE_BUF + 1]; int bytes_read = 0; int bytes_write = 0; memset(buffer, '\0', sizeof(buffer)); printf("Process %d opening FIFO O_RDONLY\n", getpid()); pipe_fd = open(fifo_name, open_mode); data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644); if (data_fd == -1) { fprintf(stderr, "Open file[DataFormFIFO.txt] failed\n"); close(pipe_fd); return -1; } printf("Process %d result %d\n",getpid(), pipe_fd); if(pipe_fd != -1) { do { res = read(pipe_fd, buffer, PIPE_BUF); bytes_write = write(data_fd, buffer, res); bytes_read += res; }while(res > 0); close(pipe_fd); close(data_fd); } else exit(EXIT_FAILURE); printf("Process %d finished, %d bytes read\n", getpid(), bytes_read); exit(EXIT_SUCCESS); }
读操作同样是这个问题,
int open_mode = O_RDONLY | O_NONBLOCK;
修改为:
int open_mode = O_RDONLY;
最后再开一个终端,运行读操作(注意不要把之前写操作的终端关闭),最后结过如下:
当然他的最后结果对不对不是我谈论的范围,我只是告诉读者正确打开方式
总结一下:
管道文件的特殊性,要求管道文件要在读写的时候,先阻塞在写操作的open函数,当有读操作进来的时候,通信链路建立,写操作开始往管道里写东西,写完成之后,在读操作会把东西读出来,最后两个进程都正常结束退出。注意当你读完一次之后再去读管道文件的时候,是会失败的,因为我前面说过,管道就像是水管,流出去之后就没有了,read完成里面就没东西了。
声明:本文在参考他人blog情况下原创,转载请说明。如有侵权联系删除。
参考文献
1、https://www.cnblogs.com/fangshenghui/p/4039805.html
2、https://blog.csdn.net/best_fiends_zxh/article/details/52923560
3、https://blog.csdn.net/xqhrs232/article/details/53636364