当从一个进程连接数据流到另一个进程时,我们使用术语管道(pipe)。我们通常是把一个进程的输出通过管道连接到另一个进程的输入。大多数Linux用户都使用过shell,不可避免地会使用管道进行一些数据的分析和处理,命令格式如下(通常通过符号“|"来使用管道):cmd1 | cmd2
shell负责安排两个命令的标准输入和标准输出,这样:
1. cmd1的标准输入来自终端键盘
2. cmd1的标准输出传递给cmd2,作为它的标准输入
3. cmd2的标准输出连接到终端屏幕
shell所做的工作从效果上看是对标准输出和标准输出流进行了重新连接,使数据流从键盘输出通过两个命令最终输出到屏幕上。
2.进程管道
最简单的两个程序之间的数据传递的方法就是使用popen和pclose函数。它们的原型如下:
#include <stdio.h>
FILE* popen (const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);
一、popen函数
popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command字符串要运行的程序名和相应的参数。open_mode必须是”r”或者”w”( 管道是被定义成单向的,只能定义成 只读或者 只写, 不能是 两者同时, 结果流也相应的 是只读 或者 只写.)。
如果open_mode是”r”,被调用程序的输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数(如fread)来读取被调用程序的输出。如果open_mode是”w”,调用程序就可以用fwrite调用向被调用命令发送数据,而被调用程序可以在自己的标准输入上读取这些数据。被调用的程序通常不会意识到自己正在从另一个进程读取数据,它只是简单在标准输入上读取数据,然后做出相应的操作。
每个popen调用都必须指定”r”或”w”,在popen函数的标准实现中不支持任何其他选项。这就意味着,我们不能调用另一个程序并同时对它进行读写操作。popen函数在失败时返回一个空指针。如果你想通过管道实现双向通信,最普通的解决方法是使用两个管道,每个管道负责一个方向的数据流。
二、pclose函数
用popen启动的进程结束时,我们可以用pclose函数关闭与之关联的文件流。pclose调用只在popen启动的进程结束后才返回。如果调用pclose时它仍在运行,pclose调用将等待该进程结束。
pclose调用的返回值通常是它所关闭的文件流所在的进程的退出码。如果调用进程在调用pclose之前执行一个wait语句,被调用进程的退出状态就会丢失,pclose将返回-1并设置errno为EHILD。
现在来看一个简单的popen和pclose示例程序:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
FILE *read_fp = NULL;
FILE *write_fp = NULL;
FILE *fp;
char buffer[BUFSIZ + 1];
int chars_read = 0;
//初始化缓冲区
memset(buffer, '\0', sizeof(buffer));
//打开ls和grep进程
read_fp = popen("ls -l", "r");
write_fp = popen("grep rwxr-xr-x", "w");
fp = fopen( "test_popen.txt", "w+"); //新建一个可写的文件
//两个进程都打开成功
if(read_fp && write_fp && fp)
{
//读取一个数据块
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
while(chars_read > 0)
{
//置字符串结束标记
buffer[chars_read] = '\0';
//printf("%s\n",buffer);
//把数据写入grep进程
fwrite(buffer, sizeof(char), chars_read, write_fp); //写入grep进程(作为grep进程的输入)
fwrite(buffer,sizeof(char), chars_read,fp);//将buffer中的数据写到FILE *fp对应的流中,也是写到文件中
//还有数据可读,循环读取数据,直到读完所有数据
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
}
//关闭文件流
fclose(fp);
pclose(read_fp);
pclose(write_fp);
}
else
{
printf("error\n");
}
return 0;
}
三、pipe调用(匿名管道)
匿名管道只能用于有亲缘关系的进程,如父进程和子进程,以及兄弟进程间的通信。
1.匿名管道的创建
在看过高级的popen函数之后,我们再来看看底层的pipe函数。通过这个函数在两个程序之间传递数据不需要启动一个shell解释请求命令(创建一个匿名管道)。它同时还提供了对读写数据更多的控制。
pipe函数原型如下所示:
#include <unistd.h>
int pipe(int file_descriptor[2]);
pipe函数的参数是一个由两个整数类型的文件描述符组成的数组指针。该函数在数组中填上两个新的文件描述符后返回0,如果失败则返回-1并设置errno以表明失败原因。
管道的两端一般使用文件描述符fd[0],fd[1]来表示,两端的任务是固定的,其中一端只能进行读操作,称为管道的读端,用文件符fd[0]表示;另一端只能进行写操作,称为管道的写端,用文件描述符fd[1]来表示。如果进程试图从一个管道的读端写数据,或者向写端读数据都会发生错误。
2.管道的读写#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#define BUFSIZE 256
int main()
{
pid_t pid;
int fd[2]; //定义管道描述符
int status;
char send[BUFSIZE]="Hello World\n";
char rec[BUFSIZE];
if(pipe(fd)<0) //创建匿名管道
{
printf("pipe Error\n");
exit(1);
}
pid=fork(); //创建子进程
if(pid<0) //如果子进程创建失败,输入错误信息并退出
{
printf("fork error\n");
exit(1);
}
if(pid==0) //子进程
{
close(fd[0]);//关闭管道的读端
write(fd[1], send, sizeof(send));//向管道写数据
exit(0);
}
else //父进程
{
sleep(1);
close(fd[1]); //关闭管道的写端
read(fd[0], rec,sizeof(rec));//从管道读数据
printf("Received message from child procedd:%s\n",rec);
if(pid!=wait(&status)) //等待子进程结束
{
printf("Wait error\n");
exit(1);
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
FILE *fp;
int fd[2]; //定义管道描述符
int status;
char buf[256]; //定义缓冲区
if(pipe(fd)<0) //创建匿名管道
{
printf("pipe error\n");
exit(1);
}
pid=fork(); //创建子进程
if(pid<0)
{
printf("fork error\n");
exit(1);
}
if(pid==0) //子进程
{
fp=fopen("1.txt", "r");
if(fp==NULL)
{
perror("open source file error");
exit(1);
}
while (fgets(buf, sizeof(buf), fp)!=NULL) //逐行读取源文件内容
{
close(fd[0]); //关闭管道的读端
write(fd[1], buf, sizeof(buf)); //将源文件内容写入管道
}
fclose(fp); //关闭源文件
strcpy(buf, "@"); //设置结束字符
close(fd[0]);
write(fd[1], buf, sizeof(buf));
}
else //父进程
{
sleep(1);
fp=fopen("2.txt", "w");
if(fp==NULL)
{
perror("open destinationg file failed\n");
exit(1);
}
close(fd[1]); //关闭管道写端
read(fd[0], buf, sizeof(buf)); //从管道读数据
while (buf[0]!='@')
{
fputs(buf, fp); //将读出的内容写入文件
close(fd[1]);
read(fd[0], buf, sizeof(buf));
}
fclose(fp);
if(pid!=wait(&status)) //等待子进程结束
{
printf("wait error\n");
exit(1);
}
printf("Done\n");
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
FILE *fp;
int fd[2]; //定义管道描述符
char buf[256]; //定义缓冲区
if(pipe(fd)<0) //创建匿名管道
{
printf("pipe error\n");
exit(1);
}
pid=vfork(); //创建子进程(用vfork)
if(pid<0)
{
printf("fork error\n");
exit(1);
}
if(pid==0) //子进程
{
char buf2[256]; //通过buf,buf2向子进程传递fd[0],fd[1]
sprintf(buf, "%d", fd[0]);
sprintf(buf2, "%d", fd[1]);
execl("2", "2",buf,buf2,NULL);
}
else //父进程
{
fp=fopen("2.txt", "w");
if(fp==NULL)
{
perror("open destinationg file failed\n");
exit(1);
}
close(fd[1]); //关闭管道写端
read(fd[0], buf, sizeof(buf)); //从管道读数据
while (buf[0]!='@')
{
fputs(buf, fp); //将读出的内容写入文件
close(fd[1]);
read(fd[0], buf, sizeof(buf));
}
fclose(fp);
printf("Done\n");
}
return 0;
}
//读源文件并写入管道的程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
FILE *fp;
int fd[2];
char buf[256]; //定义缓冲区
fp=fopen("1.txt", "r");
if(fp==NULL)
{
perror("open source file error");
exit(1);
}
sscanf(argv[1], "%d", &fd[0]);
sscanf(argv[2], "%d", &fd[1]);
while (fgets(buf, sizeof(buf), fp)!=NULL) //逐行读取源文件内容
{
close(fd[0]); //关闭管道的读端
write(fd[1], buf, sizeof(buf)); //将源文件内容写入管道
}
fclose(fp); //关闭源文件
strcpy(buf, "@"); //设置结束字符
close(fd[0]);
write(fd[1], buf, sizeof(buf));
return 0;
}
//提供一个双向数据流的两个管道
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include <string.h>
#include <fcntl.h>
#include<sys/stat.h>
#include <errno.h>
#define MAXLEN 128
void client(int ,int);
void server(int,int);
int main()
{
int pipe1[2],pipe2[2];
pid_t pid;
pipe(pipe1);
pipe(pipe2);
if ((pid=fork())==0) //子进程
{
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0], pipe2[1]);
exit(1);
}
close(pipe1[0]);
close(pipe2[1]);
client(pipe2[0], pipe1[1]);
waitpid(pid, NULL, 0); //等待子进程结束
exit(1);
}
void client(int readfd,int writefd)
{
size_t len;
ssize_t n;
char buf[MAXLEN];
fgets(buf, MAXLEN, stdin); //输入文件路径
len=strlen(buf);
if (buf[len-1]=='\n')
len--;
write(writefd, buf, len); //发送
while ((n=read(readfd, buf, MAXLEN))>0) //接收文件内容
{
write(STDOUT_FILENO, buf, n); //输出
}
}
void server(int readfd,int writefd)
{
int fd;
ssize_t n;
char buf[MAXLEN];
if ((n=read(readfd, buf, MAXLEN))==0) //读取客服端发过来的“文件路径”
{
write(STDERR_FILENO, "error", 5);
}
buf[n]=0;
//以只读的方式打开文件
if ((fd=open(buf, O_RDONLY))<0)
{
//打开失败,得到失败信息字符串存到 buf
snprintf(buf+n, sizeof(buf)-n, "can't open,%s\n",strerror(errno));
n=strlen(buf);
//发到客户端
write(writefd, buf, n);
}
else
{
//打开成功,循环读取文件内容
while ((n=read(fd, buf, MAXLEN))>0)
{
write(writefd, buf, n);
}
close(fd);
}
}
./main 运行后输入文件路径名即可
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数pathname为要创建的命名管道的全路径名;
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
mode_t mode=0750; //定义文件的访问权限
int status;
status=mkfifo("ztgreat", mode); //创建命名管道
if (status<0)
{
perror("mkfifo error");
exit(0);
}
else
{
printf("fifo creat success\n");
}
// unlink("ztgreat"); //命名管道的删除
return 0;
}
//进程通信--命名管道(非亲缘关系的进程通信)发消息进程(阻塞方式)
//发消息进程(阻塞方式)
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#define BUFSIZE 256
int main()
{
int status;
int fd;
char buf[BUFSIZE]; //定义缓冲区
status=mkfifo("zt", 0750); //创建命名管道
if (status<0)
{
perror("mkfifo error\n");
exit(0);
}
fd=open("zt", O_WRONLY); //打开命名管道,默认为阻塞方式
if (fd<0)
{
perror("open error\n");
exit(0);
}
printf("Server:\n");
printf("input the message:");
fgets(buf, sizeof(buf),stdin); //从键盘输入要发送的消息
write(fd, buf, strlen(buf)-1); //将消息写入命名管道中
printf("send!\n");
unlink("zt"); //删除命名管道
return 0;
}
接收消息进程:
//进程通信--命名管道(非亲缘关系的进程通信)收消息进程(阻塞方式)
//收消息进程(阻塞方式)
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 256
int main()
{
int status;
int fd;
char buf[BUFSIZE]; //定义缓冲区
fd=open("zt", O_RDONLY); //打开命名管道,默认为阻塞方式
if (fd<0)
{
perror("open error\n");
exit(0);
}
printf("Clint:\n");
read(fd, buf, sizeof(buf)); //读管道里面的数据
printf("received message:%s\n",buf);
return 0;
}
//进程通信--命名管道(非亲缘关系的进程通信)收消息进程(非阻塞方式)
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFSIZE 256
int main()
{
int fd;
int num;
char buf[BUFSIZE];
fd=open("zt", O_RDONLY|O_NONBLOCK); //以非阻塞方式打开命名管道
if (fd<0)
{
perror("open error\n");
exit(0);
}
printf("Client:\n");
while (1)
{
num=read(fd, buf, sizeof(buf)); //从命名管道读出数据
if (num==-1)
{
if (errno==EAGAIN)
{
printf("NO data avlaible\n");
}
}
else
{
printf("real read bytes:%d\n",num); //输出实际读到的字节数
printf("received message:%s",buf);
break;
}
sleep(1);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include <string.h>
#include <fcntl.h>
#include<sys/stat.h>
#include <errno.h>
#define MAXLEN 128
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
#define FIFL_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH
void client(int ,int);
void server(int,int);
int main()
{
int readfd,writefd;
pid_t pid;
if ((mkfifo(FIFO1, FIFL_MODE)) && errno!=EEXIST)
{
perror("can't create FIFO1");
}
if ((mkfifo(FIFO2, FIFL_MODE)) && errno!=EEXIST)
{
unlink(FIFO1);
perror("can't create FIFO2");
}
if ((pid=fork())==0)
{
readfd=open(FIFO1, O_RDONLY,0);
writefd=open(FIFO2, O_WRONLY,0);
server(readfd, writefd);
exit(0);
}
//注意下面两个open顺序
writefd=open(FIFO1, O_WRONLY,0);
readfd=open(FIFO2, O_RDONLY,0);
client(readfd, writefd);
waitpid(pid, NULL, 0);
close(readfd);
close(writefd);
unlink(FIFO1);
unlink(FIFO2);
return 0;
}
void client(int readfd,int writefd)
{
size_t len;
ssize_t n;
char buf[MAXLEN];
fgets(buf, MAXLEN, stdin); //输入文件路径
len=strlen(buf);
if (buf[len-1]=='\n')
len--;
write(writefd, buf, len); //发送
while ((n=read(readfd, buf, MAXLEN))>0) //接收文件内容
{
write(STDOUT_FILENO, buf, n); //输出
}
}
void server(int readfd,int writefd)
{
int fd;
ssize_t n;
char buf[MAXLEN];
if ((n=read(readfd, buf, MAXLEN))==0) //读取客服端发过来的“文件路径”
{
write(STDERR_FILENO, "error", 5);
}
buf[n]=0;
//以只读的方式打开文件
if ((fd=open(buf, O_RDONLY))<0)
{
//打开失败,得到失败信息字符串存到 buf
snprintf(buf+n, sizeof(buf)-n, "can't open,%s\n",strerror(errno));
n=strlen(buf);
//发到客户端
write(writefd, buf, n);
}
else
{
//打开成功,循环读取文件内容
while ((n=read(fd, buf, MAXLEN))>0)
{
write(writefd, buf, n);
}
close(fd);
}
}
#define FIFL_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH
这允许用户读,用户写,组成员读和其他用户读,这些权限为会被当前进程的文件模式创建掩码修正。