一、管道的特性
管道是UNIX系统IPC的最古老形式,并且所有UNIX系统都提供此种通信机制。但是管道有以下两种局限性:
(1)半双工的(即数据只能在一个方向上流动),虽然有系统提供全双工的管道,但是为了可移植性,用户不能假设这种情况;
(2)管道只能在具有公共祖先的进程之间使用。
二、管道的创建
管道由pipe函数创建:
1 #include <unistd.h>
2 int pipe(int filedes[2]);
函数返回值:若成功则返回0,若出错返回-1。
经由参数filedes返回两个文件描述符,filedes[0]为读而打开,filedes[1]为写而打开,也就是说filedes[1]的输出作为filedes[0]的输入。
三、管道的使用
单个进程的管道几乎没有用处,一般情况下,用户创建父子两个进程,即父进程创建pipe后接着调用 fork()函数,这样就创建了父进程到子进程(或者反向)的管道。下面考虑父进程到子进程的管道:fork之后父进程所有打开的文件描述符都被复制给子进程,也就是说,父子进程每个打开的相同描述符共享一个文件表项,父进程关闭管道的读端(filedes[0]),子进程关闭管道的写端(filedes[1]),这样,对于管道,父进程负责写数据,子进程负责读数据。
当管道的一端被关闭后,下列两条规则起作用:
(1)当读一个写端已经关闭的管道,在所有数据都被读取后,read返回0,以表示达到文件末尾;
(2)当写一个读端已经关闭的管道,则产生信号SIGPIPE。
根据APUE,借用系统已经存在的分页程序(如more或者less),通过管道直接将文件内容送到分页程序,代码如下:
1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <sys/wait.h>
4 #include <string.h>
5
6 #define MAXLINE 1024
7 #define DEF_PAGER "/bin/more"
8
9 int main(int argc, char *argv[])
10 {
11 int count;
12 int fd[2];
13 pid_t pid;
14 char line[MAXLINE];
15 char *pager, *argv0;
16 FILE *fp;
17
18 if((fp = fopen(argv[1], "r")) == NULL)
19 perror("fopen");
20
21 if(pipe(fd) < 0)
22 perror("pipe");
23
24 if((pid = fork()) < 0)
25 perror("fork");
26 else if(pid > 0) {
27 close(fd[0]);
28 while(fgets(line, MAXLINE, fp) != NULL) {
29 count = strlen(line);
30 write(fd[1], line, count);
31 }
32 if(ferror(fp))
33 perror("fgets");
34 close(fd[1]);
35 if(waitpid(pid, NULL, 0) < 0)
36 perror("waitpid");
37 exit(0);
38 } else {
39 close(fd[1]);
40 /* 将管道复制为标准输入 */
41 if(fd[0] != STDIN_FILENO) {
42 if(dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
43 perror("dup2");
44 close(fd[0]);
45 }
46 if((pager = getenv("PAGER")) == NULL)
47 pager = DEF_PAGER;
48 if((argv0 = strrchr(pager, '/')) != NULL)
49 argv0++;
50 else
51 argv0 = pager;
52 if(execl(pager, argv0, NULL) < 0)
53 perror("execl");
54 }
55 exit(0);
56 }
在这个程序中,父进程创建管道后,调用fork产生子进程,父进程首先关闭读端,运用fgets将文件内容读入,并写入管道;子进程关闭写端,并将标准输入和fd[0]共享同一个文件表项,这样分页程序就能够利用管道中的数据。
注:dup2(filedes, filedes2)函数的作用是使得filedes和filedes2都指向filedes指向的文件表。一个比较特殊的例子:
1 int
2 main(void)
3 {
4 dup2(0, STDOUT_FILENO);
5 printf("hello\n");
6 return 0;
7 }
这里将STDOUT_FILENO dup到标准输入,而函数之所以有输出hello,是因为使用了/dev/pts或者/dev/tty*,而这两种设备的驱动程序在处理对它们的写入操作时,采用了连接显示器输出这样的机制(也就是回显机制),所以能看到终端显示的hello,验证如下:
1 elvis@elvis:~/program/test$ ./a.out 1>dup2.out
2 hello
3 elvis@elvis:~/program/test$ ./a.out 0>dup2.out
4 elvis@elvis:~/program/test$
四、FIFO
FIFO为有名管道,和无名管道的不同之处在于,无名管道需要两个拥有共同祖先的进程通信,而FIFO并不要求这样,FIFO依然是半双工管道。创建FIFO的函数如下:
1 #include <sys/stat.h>
2 int mkfifo(const char *name, mode_t mode);
成功返回0,失败返回-1。
建立一个服务器端和多个客户端程序,客户端通过共知的服务器管道向服务器发送数据,服务器通过客户端进程PID建立多个私有管道,转发来自客户端的数据。
服务器端:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <errno.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <signal.h>
10
11 #define FIFO_SERV "/tmp/serv"
12 #define bufSize 256
13
14 static void rm_fifo(void);
15
16 int
17 main(void)
18 {
19 int count;
20 int server_fd, client_fd;
21 pid_t pid;
22 char recv_buf[bufSize];
23 char send_buf[bufSize];
24 char *pstr;
25 struct sigaction action;
26
27 action.sa_handler = SIG_IGN;
28 sigemptyset(&action.sa_mask);
29
30 if(sigaction(SIGPIPE, &action, NULL) < 0) {
31 perror("sigaction failed");
32 exit(1);
33 }
34
35 if(unlink(FIFO_SERV) == -1) {
36 if(errno != ENOENT)
37 perror("unlink failed");
38 }
39
40 if(mkfifo(FIFO_SERV, S_IFIFO | 0777) == -1) {
41 perror("mkfifo failed");
42 exit(1);
43 }
44
45 if(atexit(rm_fifo) == -1) {
46 perror("atexit");
47 exit(1);
48 }
49
50 if((server_fd = open(FIFO_SERV, O_RDONLY)) == -1) {
51 perror("open server failed");
52 exit(1);
53 }
54
55 memset(recv_buf, 0, bufSize);
56 while((count = read(server_fd, recv_buf, bufSize)) > 0) {
57 recv_buf[count] = '\n';
58 printf("%s\n", recv_buf);
59
60 strcpy(send_buf, recv_buf);
61
62 /*
63 if(unlink(send_buf) == -1) {
64 if(errno != ENOENT) {
65 perror("can't open file");
66 exit(1);
67 }
68 }
69 */
70 if(mkfifo(send_buf, S_IFIFO | 0777) == -1) {
71 perror("mkfifo failed");
72 exit(1);
73 }
74
75 if((client_fd = open(send_buf, O_WRONLY)) == -1) {
76 perror("open client failed");
77 exit(1);
78 }
79
80 memset(send_buf, 0, bufSize);
81 pstr = recv_buf;
82 sprintf(send_buf, "serv2clie%s", pstr+10);
83 write(client_fd, send_buf, bufSize);
84 close(client_fd);
85 sleep(3);
86 }
87
88 close(server_fd);
89 exit(0);
90 }
91
92 static void
93 rm_fifo(void)
94 {
95 unlink(FIFO_SERV);
96 }
客户端:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <fcntl.h>
4 #include <unistd.h>
5 #include <errno.h>
6 #include <string.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9
10 #define FIFO_SERV "/tmp/serv"
11 #define bufSize 256
12
13 static void rm_fifo(void);
14
15 int
16 main(void)
17 {
18 int count;
19 int server_fd, client_fd;
20 pid_t pid;
21 char recv_buf[bufSize];
22 char send_buf[bufSize];
23
24 if((server_fd = open(FIFO_SERV, O_WRONLY)) == -1) {
25 perror("open server failed");
26 exit(1);
27 }
28
29 memset(send_buf, 0, bufSize);
30 sprintf(send_buf, "/tmp/clie_%d", getpid());
31
32 write(server_fd, send_buf, bufSize);
33
34 while((client_fd = open(send_buf, O_RDONLY)) == -1) {
35 if(errno != ENOENT) {
36 printf("can't open client fifo");
37 exit(1);
38 }
39 //否则,一直连续等待服务器创建
40 }
41 /*
42 if(unlink(send_buf) == -1) {
43 perror("unlink failed");
44 exit(1);
45 }
46 */
47 read(client_fd, recv_buf, bufSize);
48 printf("%s\n", recv_buf);
49
50 unlink(send_buf);
51
52 close(server_fd);
53 close(client_fd);
54 exit(0);
55 }