管道是UNIX系统IPC的最古老形式,并且所有的UNIX系统都提供此种通信机制。管道有下列两种局限性:
a.历史上,它们是半双工的,现在某些系统提供全双工管道,但是为了最佳的可移植性,我们决不应预先假定系统使用此
特性。
b.它们只能在具有公共祖先的进程之间使用。通常一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间可以
使用该管道。
(FIFO没有第二种局限性,UNIX域套接字和命名流没有这两种局限性)
管道是由调用pipe函数而创建的:
#include<unistd.h> int pipe(int filedes[2]); //成功则返回0,出错则返回-1.经由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
调用fork之后做什么取决于我们想要有的数据流的方向,对于从父进程到子进程的管道,父进程关闭管道的读端fd[0],子
进程则关闭写端fd[1]。
调用fork之后的半双工管道:
从父进程到子进程的管道:
为了构造从子进程到父进程的管道,父进程关闭fd[1],子进程关闭fd[0]。
当管道的一端被关闭后,下列两条规则其作用:
a.当读一个写端已经被关闭的管道时,在所有数据都被读取后,read返回0,指示达到了文件结束处。
b.如果写一个读端已经被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕获该信号并从其处理程序返回,则
write返回-1,errno设置为EPIPE。
在写管道是,常量PIPE_BUF规定了内核中管道缓冲区的大小,如果对管道调用write,而且要求写的字节数小于等于
PIPE_BUF,则此操作不会与其他进程对同一管道的write操作穿插进行。但是,若有多个进程同时写一个管道,而且
进程要求写的字节数超过PIPE_BUF字节数时,写操作的数据可能穿插。
实践:
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main(void){ int result,n; int fd[2]; pid_t pid; char line[256]; if(pipe(fd) < 0){ perror("pipe"); return -1; } if((pid = fork()) < 0){ perror("fork"); return -1; }else if(pid > 0){ //parent close(fd[0]); if(fd[1] != STDOUT_FILENO){ dup2(fd[1],STDOUT_FILENO); } execl("/bin/ls","ls",(char*)0); }else{ //child close(fd[1]); while((n =read(fd[0],line,256)) > 0){ if(write(STDOUT_FILENO,line,n) != n){ perror("write"); exit(-1); } } close(fd[0]); } return 0; }运行结果:
WinVNC.log
a.out
a.txt
b.txt
chlock
dtest
lock
lock.txt
在父进程中将管道描述符赋值为标准输出,所以在执行ls后,原本是写到标准输出的,现在写到fd[1]中。而子进程将读到
的内容显示到标准输出上。
常见的操作是创建一个管道连接到另一个进程,然后读取其输出或向其输入端发送数据,为此,标准IO提供了两个函数
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,并返回一个标准IO文件指针,如果type是r,则文件指针连接到
cmdstring的标准输出,如果type是w,则文件指针连接到cmdstring的标准输入。
pclose函数关闭标准IO流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose返回的终止状
态与shell已执行exit(127)一样。
实践:
#include<stdio.h> int main(void){ char line[256]; FILE* fpin; int n; if((fpin = popen("/bin/ls","r")) == NULL){ perror("popen"); return -1; } while(fgets(line, 256, fpin) != NULL){ if(fputs(line,stdout) == EOF){ perror("fputs"); return -1; } } return 0; }运行结果:
[root@yanPC apue]# ./a.out
a.out
abort.c
core.16354
core.16355
fork.c
popen.c
sigsetjmp.c
sigsuspend.c
sleep.c
template.c
vfork.c
wait.c
FIFO被称为命名管道。匿名管道只能由相关进程使用,这些相关进程的共同祖先进程创建了管道,但是通过FIFO,不相关的
进程也能交换数据。
FIFO是一种文件类型。创建FIFO类似于创建文件,确实,FIFO的路径名存在于文件系统中。
#include<sys/stat.h> int mkfifo(const char *pathname, mode_t mode); //成功则返回0,出错则返回-1.mkfifo函数中mode参数的规格说明与open函数中的mode相同,一旦已经用mkfifo创建一个FIFO,就可用open打开它,其实,
一般的文件IO函数都可用于FIFO(close,read,write,unlink)。
实践:
#include<stdio.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> #define MYFIFO "myfifo" int main(void){ char buffer[256]; pid_t pid; int fd; unlink(MYFIFO); if(mkfifo(MYFIFO,0666) < 0){ perror("mkfifo"); return -1; } if((pid = fork())<0){ perror("fork"); return -1; }else if(pid > 0){ char s[] = "hello world."; fd = open(MYFIFO,O_WRONLY); write(fd,s,sizeof(s)); close(fd); }else{ fd = open(MYFIFO,O_RDONLY); read(fd,buffer,256); printf("%s\n",buffer); close(fd); exit(0); } waitpid(pid,NULL,0); return 0; }运行结果:
[root@yanPC apue]# ./a.out
hello world.