首页 > 代码库 > Linux环境编程之IPC进程间通信(二):管道
Linux环境编程之IPC进程间通信(二):管道
管道作为最古老的进程间通信方法,它有以下几个特点:
1、在所有的UNIX实现中都存在。
2、没有名字,因此只能由有亲缘关系的进程使用。
3、它由函数pipe创建,read和write函数访问,但只提供单路(单向)数据流。
<span style="font-size:14px;">#include <unistd.h> int pipe(int fd[2]); 返回:若成功则为0,若出错则为-1</span>
经由参数fd返回两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。
宏S_ISFIFO可用于确定一个描述符或文件是管道还是FIFO。它的唯一参数是stat结构的st_mode成员。对于管道来说,这个stat结构是由fstat函数填写的。对于FIFO来说,这个结构是由fstat、lstat或stat函数填写的。
单进程使用管道的情况一般如图所示:
图 单个进程中的管道
尽管管道是由单个进程创建的,它却很少在单个进程内使用。通常,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程或反向的IPC通道。如图所示;
图 单个进程内的管道,刚刚fork后
对于从父进程到子进程的管道,父进程关闭管道的读端fd[0],子进程关闭写端fd[1]。结果如图所示:
图 从父进程到子进程的管道
从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据。程序示例如下:
<span style="font-size:14px;">#include <unistd.h> #define MAXLINE 4096 int main(void) { int n; int fd[2]; pid_t pid; char line[MAXLINE]; if(pipe(fd) < 0){ printf("pipe error"); return -1; } if((pid = fork()) < 0){ printf("fork error"); return -1; }else if(pid > 0){ /*parent 父进程*/ close(fd[0]); write(fd[1], "hello world", 12); }else{ /*child 子进程*/ close(fd[1]); n = read(fd[0], line, MAXLINE); write(STDOUT_FILENO, line, n); } exit(0); }</span>
<span style="font-size:14px;">编译: gcc pipe.c -o pipe 测试:./pipe 结果:hello world</span>
上面所说的管道都是半双工的即单向的,只提供一个方向的数据流。当需要一个双向数据流时,必须创建两个管道,每个方向一个。实际步骤如下:
1、创建管道1和管道2
2、fork
3、父进程关闭管道1的读出端、关闭管道2的写入端
3、子进程关闭管道1的写入端、关闭管道2的读出端。
完成上述步骤后的管道布局如下图所示。
图 提供一个双向数据流的两个管道
有了双向数据流后,我们就可以完成客户-服务器的程序例子了。其实现如下图所示:
图 使用两个管道实现的客户-服务器例程
根据上面提到的创建两个管道的步骤以及客户-服务器的图示,可以利用管道编写客户-服务器例程代码如下:
mainpipe.c
<span style="font-size:14px;">#include "pipe.h" extern void client(int, int); extern void server(int, int); int main(int argc, char *argv[]) { int pipe1[2], pipe2[2]; pid_t pid; pipe(pipe1); pipe(pipe2); if((pid = fork()) < 0){ printf("fork error.\n"); return -1; }else if(pid == 0){ /*child*/ close(pipe1[1]); close(pipe2[0]); server(pipe1[0], pipe2[1]); exit(0); }else{ /*parent*/ close(pipe1[0]); close(pipe2[1]); client(pipe2[0], pipe1[1]); waitpid(pid, NULL, 0); /*wait for child to terminate*/ exit(0); } }</span>server.c
<span style="font-size:14px;">#include "pipe.h" void server(int readfd, int writefd) { int fd; ssize_t n; char buff[MAXLINE+1]; /*read pathname frome IPC channel*/ if((n = read(readfd, buff, MAXLINE)) == 0){ printf("end-of-file while reading pathname.\n"); return ; } buff[n] = '\0'; /*null terminate pathname*/ if((fd = open(buff, O_RDONLY)) < 0){ /*error:must tell client*/ snprintf(buff+n, sizeof(buff)-n, ":can't open, %s\n", strerror(errno)); n = strlen(buff); write(writefd, buff, n); }else{ /*open succeeded:copy file to IPC channel*/ while((n = read(fd, buff, MAXLINE)) > 0) write(writefd, buff, n); close(fd); } }</span>client.c
<span style="font-size:14px;">#include "pipe.h" int client(int readfd, int writefd) { size_t len; ssize_t n; char buff[MAXLINE]; /*read pathname*/ fgets(buff, MAXLINE, stdin); len = strlen(buff); if(buff[len-1] == '\n') len--; /*write pathname to IPC channel*/ write(writefd, buff, len); /*read from IPC, write to standard output*/ while((n = read(readfd, buff, MAXLINE)) > 0) write(STDOUT_FILENO, buff, n); }</span>pipe.h
<span style="font-size:14px;">#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <fcntl.h> #include <errno.h> #define MAXLINE 4096</span>编译:gcc server.c client.c mainpipe.c -o mainpipe
可得可执行文件mainpipe。
上面给出的例程中,都是先创建管道,调用fork启动另外一个进程,然后读其输出或向其输入端发送数据,比较麻烦。为此,标准I/O库提供了两个函数popen和pclose函数。其实现的操作包括:创建一个管道,调用fork产生一个子进程,关闭管道的不适用端,执行一个shell以运行命令,然后等待命令终止。
函数原型:
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);//返回值:若成功则返回文件指针,若出错则返回NULL
int pclose(FILE *fp);//返回值:cmdstring的终止状态,若出错则返回-1
函数popen先执行fork,然后调用exec以执行cmdstring,并且返回一个标准I/O文件指针。
如果type是“r”,则文件指针连接到cmdstring的标准输出。
如果type是“w”,则文件指针连接到cmdstring的标准输入。
图解函数popen如下:
使用popen函数重写客户-服务器程序代码如下:
<span style="font-size:14px;">#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXLINE 4096 int main(int argc, char **argv) { size_t n; char buff[MAXLINE], command[MAXLINE]; FILE *fp; /*read pathname*/ fgets(buff, MAXLINE, stdin); n = strlen(buff); /*fgets() guarantees null byte at end*/ if(buff[n-1] == '\n') n--; /*delete newline from fgets()*/ snprintf(command, sizeof(command), "cat %s", buff); fp = popen(command, "r"); /*copy from pipe to standard output*/ while(fgets(buff, MAXLINE, fp) != NULL) fputs(buff, stdout); pclose(fp); exit(0); }</span>对比两个代码可以发现,使用popen函数要简单许多。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。