管道与命名管道
1.二者的异同
管道只能在两个具有亲缘关系的进程之间通信,通过命名管道(Named PiPe)FIFO,不相关的进程也能 交换数据。FIFO不同于管道之处在于它提供一个路径与之关联,以FIFO的文件形式存在于系统中。它在磁盘上有对应的节点,但 没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建 立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被 进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。
2.管道(pipe)
管道是UNIX系统IPC的最古老的形式,所有的UNIX系统都提供此种通信机制。管道的实质是一个内核缓冲区,进程以先进 先出(FIFO, First In First Out)的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺 序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都 不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写 入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。
-
两个局限性:
(1)半双工,数据只能在一个方向流动,现在有些系统可以支持全双工管道,但是为了最佳的可移植性,应认为系统 不支持全双工管道;
(2)管道只能在具有公共祖先之间的两个进程之间使用; -
管道的创建:
管道可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不 属于其他任何文件系统,并且只存在于内存中。管道是通过调用pipe函数创建的。
#include <unistd.h>
int pipe(int fd[2]); //返回值:若成功,返回0,若出错,返回-1.
经由参数fd返回的两个文件描述符:fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入。
通常,进程会先调用pipe,接着调用fork,从而创建了父进程与子进程的IPC通道。fork之后做什么取决于我们想要的数据流的方向,对于从父进程到子进程,父进程关闭管道的读端fd[0],子进程关闭写端 fd[1]。
如下图:
而子进程到父进程则是相反,父进程关闭写端,子进程关闭读端。
- 关闭管道的一端
(1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;
(2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则 wirte返回-1.
父子进程示例:
首先父进程创建管道之后fork(),这时子进程会继承父进程所有打开的 文件描述符(包括管道),这时对于一个管道就有4个读写端(父子进程各有一对管道读写端),如果需要父进程往子进程里写 数据,则需要在父进程中关闭读端,在子进程中关闭写端;而如果需要子进程往父进程中写数据,则可以在父进程关闭写端,然 后子进程中关闭读端。
代码示例:
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<errno.h>
5 #include<sys/types.h>
6 #include<sys/wait.h>
7
8 #define WXJ "hello, child process!"
9
10 int main(int argc, char **argv)
11 {
12 int pipe_fd[2];
13 int rv;
14 int pid;
15 char buf[1024];
16 int wstatus;
17
18 if(pipe(pipe_fd) < 0)
19 {
20 printf("Create pipe failture:%s\n", strerror(errno));
21 return -1;
22 }
23
24 if((pid = fork()) < 0)
25 {
26 printf("Create child process failture:%s\n", strerror(errno));
27 return -2;
28 }
29 else if(pid == 0)
30 {
31 /*child process close write endpoint, then read data from parent process*/
32 close(pipe_fd[1]);
33
34 memset(buf, 0, sizeof(buf));
35 rv = read(pipe_fd[0], buf, sizeof(buf));
36 if(rv < 0)
37 {
38 printf("Child process read from pipe failture:%s\n", strerror(errno));
39 return -3;
40 }
41
42 printf("Child process read %d bytes data from pipe:\"%s\"\n", rv, buf);
43 return 0;
44 }
45
46 /*parent process close read endpoint, then write data to child process*/
47 close(pipe_fd[0]);
48 if(write(pipe_fd[1], WXJ, strlen(WXJ)) < 0)
49 {
50 printf("Parent process write data to pipe failture:%s\n", strerror(errno));
51 return -3;
52 }
53
54 printf("Parent start wait child process exit...\n");
55 wait(&wstatus);
56
57 return 0;
58 }
运行结果:
3.命名管道(fifo)
命名管道(FIFO)又称为先入先出队列,是一种特殊的管道,存在于文件系统中。命名管道一旦创建,就可以像普通文件一样进行读写操作。
特点:
- 命名管道可以用于任何两个进程间的通信,并不限制这两个进程同源;
- 数据被一个进程读出后,将被从管道中删除,其他读进程不能再读到这些数据;
- 命名管道作为一种特殊的文件存在于文件系统中,而不像管道一样存在于内核中。当进程对命名管道使用完毕,命名管道任然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会消失;
- 命名管道只能用于单向数据传输,如果要用命名管道实现两个进程间数据的相互交换,需要使用两个命名管道。
命名管道的创建:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
解析:参数pathname是一个字符串指针,用于存放命名管道的文件名。参数mode用于指定所创建的文件的权限,其取值及含义与创建普通文件的creat函数中的mode相似。调用成功,返回值0;调用失败,返回值-1。
注意:
- 一旦创建了一个FIFO,就可以用open()打开它,一般的文件访问函数(read和write)进行读写,使用系统调用close关闭一个命名管道。若要删除一个命名管道,使用系统调用unlink;
- 使用命名管道时,需要在两个将进行通信的进程中分别打开命名管道,因此当一个进程读打开(或写打开)一个命名管道而没有其他进程写打开(或读打开)此命名管道,该进程就会进入阻塞状态,直到另一个进程写打开(或读打开)此命名管道;
- 打开FIFO,非阻塞标志(O_NONBLOCK)对以后的读写产生如下影响:
没有使用O_NONBLOCK,访问要求无法满足时进程阻塞,如果试图读取空的FIFO,将导致进程阻塞;若使用O_NONBLOCK,访问时无法满足要求时阻塞。
代码示例:
下面这个例程创建了两个掩藏的命名管道文件(.fifo_chat1和.fifo_chat2)在不同的进程间进行双向通信。该程序需要运行两 次(即两个进程),其中进程0(mode=0)从标准输入里读入数据后通过命名管道2(.fifo_chat2)写入数据给进程 1(mode=1);而进程1(mode=1)则从标准输入里读入数据后通过命名管道1(.fifo_chat1)写给进程0,这样使用命名管 道实现了一个进程间聊天的程序。
1 #include<stdio.h>
2 #include<string.h>
3 #include<unistd.h>
4 #include<errno.h>
5 #include<fcntl.h>
6 #include<sys/types.h>
7 #include<sys/wait.h>
8 #include<sys/stat.h>
9 #include<libgen.h>
10 #include<stdlib.h>
11
12 #define FIFO_FILE1 ".fifo_chat1"
13 #define FIFO_FILE2 ".fifo_chat2"
14
15 int g_stop = 0;
16
17 void sig_pipe(int signum)
18 {
19 if(SIGPIPE == signum)
20 {
21 printf("get pipe broken signal and let progname exit\n");
22 g_stop = 1;
23 }
24 }
25
26 int main(int argc, char **argv)
27 {
28 int fdr_fifo;
29 int fdw_fifo;
30 int rv;
31 fd_set rdset;
32 char buf[1024];
33 int mode = 0;
34
35 if(argc!=2)
36 {
37 printf("Usage:%s [0/1]\n", basename(argv[0]));
38 printf("This chat progname need run twice, 1st time run with [0] and 2nd time with [1]\n");
39 return -1;
40 }
41
42 mode = atoi(argv[1]);
43
44 if(access(FIFO_FILE1, F_OK))
45 {
46 printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE1);
47 mkfifo(FIFO_FILE1, 0666);
48 }
49
50 if(access(FIFO_FILE2, F_OK))
51 {
52 printf("FIFO file \"%s\" not exist and create it now\n", FIFO_FILE2);
53 mkfifo(FIFO_FILE2, 0666);
54 }
55
56 signal(SIGPIPE, sig_pipe);
57
58 if( 0 == mode)
59 {
60 printf("start open '%s' for and it will blocked untill write endpoint opened...\n", FIFO_FILE1);
61 if((fdr_fifo=open(FIFO_FILE1, O_RDONLY)) < 0)
62 {
63 printf("Open fifo[%s] for chat read endpoint failture:%s\n",FIFO_FILE1,strerror(errno));
64 return -1;
65 }
66
67 printf("start open '%s' for write...\n", FIFO_FILE2);
68 if((fdw_fifo=open(FIFO_FILE2, O_WRONLY)) < 0)
69 {
70 printf("Open fifo[%s] for chat write endpoint failture:%s\n",FIFO_FILE2,strerror(errno));
71 return -1;
72 }
73 }
74 else
75 {
76 printf("start open '%s' for write and it will blocked untill read endpoint opened...\n",FIFO_FILE1);
77 if((fdw_fifo=open(FIFO_FILE1, O_WRONLY)) < 0)
78 {
79 printf("Open fifo[%s] for chat write endpoint failture:%s\n",FIFO_FILE1,strerror(errno));
80 return -1;
81 }
82
83 printf("start open '%s' for read...\n",FIFO_FILE2);
84 if((fdr_fifo=open(FIFO_FILE2, O_RDONLY)) < 0)
85 {
86 printf("Open fifo[%s] for chat read endpoint failture:%s\n",FIFO_FILE2,strerror(errno));
87 return -1;
88 }
89 }
90 printf("start chating with another program now,please input message now:\n");
91 while(!g_stop)
92 {
93 FD_ZERO(&rdset);
94 FD_SET(STDIN_FILENO,&rdset);
95 FD_SET(fdr_fifo, &rdset);
96
97 rv=select(fdr_fifo+1,&rdset, NULL,NULL,NULL);
98 if(rv <= 0)
99 {
100 printf("Select get timeout or error:%s\n",strerror(errno));
101 continue;
102 }
103
104 if(FD_ISSET(fdr_fifo, &rdset))
105 {
106 memset(buf, 0, sizeof(buf));
107 rv=read(fdr_fifo, buf, sizeof(buf));
108 if(rv<0)
109 {
110 printf("Read data from FIFO get error:%s\n",strerror(errno));
111 break;
112 }
113 else if( 0==rv)
114 {
115 printf("Another side of FIFO get closed and programwillexitnow\n");
116 break;
117 }
118 printf("<--%s", buf);
119
120 if(FD_ISSET(STDIN_FILENO, &rdset))
121 {
122 memset(buf, 0, sizeof(buf));
123 fgets(buf, sizeof(buf), stdin);
124 write(fdw_fifo, buf, strlen(buf));
125 }
126 }
127 }
128 }
129
运行结果: