管道是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的函数如下:
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 }