管道是最早的Unix进程间通信形式,它存在于所有的Unix实现中。关于管道,有如下几点需要知道:
1、它是半双工的,即数据只能在一个方向上流动。尽管在某些Unix实现中管道可以是全双工的,但需要对系统进行某些设置。在Linux系统中,它是半双工的。
2、它没有名字,因此只能在具有公共祖先的进程之间使用。通常用在父子进程间。尽管这一点随着“有名管道FIFO”的加入得到改正了,但应该把它们看作是两种不同的进程间通信方式。
3、它由pipe函数创建,read和write函数访问,提供单向数据流。除了pipe外,在C函数库里,还有另外一个函数popen完成一个新管道的创建、一个新进程的启动、关闭管道的不使用端、执行shell命令、等待命令终止等一系列操作。
管道使用示例:
在shell命令中,我们经常用到"cmd1 | cmd2"这一类的命令,cmd1和cmd2之间就是通过管道来进行连接的。shell负责两个命令的标准的输入好标准输出:
cmd1的标准输入来自终端键盘。
cmd1的标准输出传递给cmd2,作为它的标准输入。
cmd2的标准输出连接到终端屏幕。
知识点1:pipe函数
#include <unistd.h> int pipe(int fd[2]); //返回值:若成功则返回0,若出错则返回-1
进程调用pipe函数创建一个管道。pipe函数的参数是一个由两个整数类型的文件描述符组成的数组的指针。该函数在数组中成功填入两个新的文件描述符后返回0,如果失败则返回-1并设置errno来表明失败的原因。errno的值有以下三种可能:
EMFILE:进程使用的文件描述符过多。
ENFILE:系统的文件表已满。
EFAULT:文件描述符无效。
两个新填入的文件描述符:fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。也就是说利用write函数写到fd[1]的所有数据都可以从fd[0]读出来。示例如下:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #define MAXLINE 2048 int main(int argc, char **argv) { int fd[2]; int data; char buff[MAXLINE]; const char some_data[] = "123"; memset(buff, '\0', sizeof(buff)); if(pipe(fd) == -1){ exit(EXIT_FAILURE); }else{ data = write(fd[1], some_data, strlen(some_data)); data = read(fd[0], buff, data); printf("read %d bytes : %s\n", data, buff); exit(EXIT_SUCCESS); } }
程序运行结果:
book@book-desktop:/work/tmp/unp$ ./a.out read 3 bytes : 123
上面的例子是在同一进程中使用管道的例子,但实际使用过程中很少这样使用,一般都是在两个不同进程(通常是父子进程)间进程通信的。两个进程间使用管道的示例:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define MAXLINE 2048 int main(int argc, char **argv) { int fd[2]; int data; pid_t child_pid; char buff[MAXLINE]; const char some_data[] = "123"; memset(buff, '\0', sizeof(buff)); if(pipe(fd) == 0){ child_pid = fork(); if(child_pid == -1){ fprintf(stderr, "fork error."); exit(EXIT_FAILURE); }else if(child_pid == 0){ close(fd[1]); //关闭子进程的写入端 data = read(fd[0], buff, MAXLINE);//从子进程的读取段读取数据 printf("read %d bytes: %s\n", data, buff); exit(EXIT_SUCCESS); }else{ close(fd[0]);//关闭父进程的读取段 data = write(fd[1], some_data, strlen(some_data));//从父进程的写入端写入数据 printf("wrote %d bytes\n", data); } } exit(EXIT_SUCCESS); }
程序运行结果是:
book@book-desktop:/work/tmp/unp$ ./a.out wrote 3 bytes read 3 bytes: 123
或
book@book-desktop:/work/tmp/unp$ ./a.out wrote 3 bytes book@book-desktop:/work/tmp/unp$ read 3 bytes: 123这是因为,如果父进程先于子进程结束,就会看到shell提示符了。由上面的示例可以看出,要通过管道完成父子进程间的通信,先由父进程创建一个管道后调用fork派生一个自身的副本,接着,父进程关闭这个管道的读出端,子进程关闭同一管道的写入端。这就在父子进程间提供了一个单向数据流。
当管道的一端被关闭后,下列两条规则起作用:
1、当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,以指示达到了文件结束处。
2、如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1,errno设置为EPIPE。
上面的两个例子都是半双工的即单向的,只提供一个方向的数据流。当需要一个双向数据流时,必须创建两个管道,每个方向一个。实际步骤如下:
1、创建管道1和管道2
2、fork
3、父进程关闭管道1的读出端、关闭管道2的写入端
3、子进程关闭管道1的写入端、关闭管道2的读出端。
创建两个管道后就可以完成一个简单的客户端-服务器例子。相关示例请点此链接。
知识点2:popen和pclose函数
popen和pclose函数不是Unix实现的,它们时标准I/O库提供的,它们实现的操作时:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止。
#include <stdio.h> FILE *popen(const char *cmdstring, const char *type);//返回值:若成功则返回文件指针,若出错则返回NULL int pclose(FILE *fp);//返回值:cmdstring的终止状态,若出错则返回-1
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv) { FILE *read_fp; char buff[BUFSIZ + 1]; int chars_read; memset(buff, '\0', sizeof(buff)); read_fp = popen("uname -a", "r");//打开连接到uname命令的管道,把管道设置为可读方式并让read_fp指向该命令输出 if(read_fp != NULL){ chars_read = fread(buff, sizeof(char), BUFSIZ, read_fp); if(chars_read > 0) printf("Output was : -\n%s\n", buff); pclose(read_fp); exit(EXIT_SUCCESS); } exit(EXIT_SUCCESS); }运行结果:
Output was : - Linux book-desktop 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linuxtype是w的示例程序如下:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { FILE *fp; char buffer[BUFSIZ + 1]; sprintf(buffer, "Once upon a time there was ...\n"); fp = popen("od -c", "w"); if(fp != NULL){ fwrite(buffer, sizeof(char), strlen(buffer), fp); pclose(fp); exit(EXIT_SUCCESS); } exit(EXIT_SUCCESS); }程序结果如下:
0000000 O n c e u p o n a t i m e 0000020 t h e r e w a s . . . \n 0000037也可以上上面的pipe函数一样实现客户端-服务器程序。示例代码 请点此链接。