主要内容:匿名管道,命令管道。(pipe, popen, mkfifo等函数)
管道简介:
管道是进程之间通信的一种方式,就相当于是两个进程之间有一条地道,可以通过地道来传递信息;
管道位于进程的地址空间之外;管道分为匿名管道和命名管道两种;匿名管道是由pipe来创建的,命名管道是由mkfifo来创建的;
#include <unistd.h>
int pipe(int pipefd[2]);
返回两个描述符:pipefd[0]是管道的读端,pipefd[1]是管道的写端;
下面是一个父子进程利用管道通信的实例:
main函数创建两个管道,并用fork生成一个子进程,客户端作为父进程运行,服务器则作为子进程运行。第一个管道用于从客户向服务器发送路径名, 第二个管道用于从服务器向客户发送该文件的内容。
客户端写pipe1[1]-----pipe1[0]服务器读
服务器写pipe2[1]-----pipe2[0]客户端读
客户端写pipe1[1]-----pipe1[0]服务器读
服务器写pipe2[1]-----pipe2[0]客户端读
/*************************************************************************
> File Name: mainpipe.c
> Author:
> Mail:
> Created Time: 2016年04月20日 星期三 22时25分15秒
************************************************************************/
/*
* main函数创建两个管道,并用fork生成一个子进程
* 客户端作为父进程运行,服务器则作为子进程运行
* 第一个管道用于从客户向服务器发送路径名
* 第二个管道用于从服务器向客户发送该文件的内容
*
* cin --客户端写pipe1[1]-----pipe1[0]服务器读
* 服务器写pipe2[1]-----pipe2[0]客户端读
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
//服务器从rfd中读取文件的名字,向wfd中写入文件的内容
void server(int rfd, int wfd)
{
char fileName[1024];
char fileContent[2048];
memset(fileName, 0, 1024);
memset(fileContent, 0, 2048);
//从rfd中读取文件的名字,
int n = read(rfd, fileName, 1024);
fileName[n] = 0;
printf("server receive the file name is %s\n", fileName);
int filefd = open(fileName, O_RDONLY);//打开文件
if(filefd < 0){
printf("open error\n");
return;
}
else{//读取文件的内容,并写入到wfd中
//读取文件的内容到fileContent中
int num = 0;
while((num = read(filefd, fileContent, 2048)) > 0){
printf("server read the fileContent is: %s", fileContent);
//将fileContent中的内容写入到wfd中
write(wfd, fileContent, num);
}
}
close(filefd);
close(rfd);
close(wfd);
}
//客户端从rfd中读取文件的内容,向wfd中写入文件的名字
void client(int rfd, int wfd)
{
char fileName[1024];
char fileContent[2048];
memset(fileName, 0, 1024);
memset(fileContent, 0, 2048);
printf("输入文件名字:");
//从标准输入输入文件的名字
fgets(fileName, 1024, stdin);
int len = strlen(fileName);
if(fileName[len-1] == '\n')
len--;
//向wfd中写入文件的名字
write(wfd, fileName, len);
printf("fileName = %s\n", fileName);
//从rfd中读取文件的内容
int n;
while((n = read(rfd, fileContent, 2048)) > 0){
printf("client receive the content is: %s", fileContent);
}
close(rfd);
close(wfd);
}
//主函数
int main()
{
//创建两个管道.
int pipe1[2], pipe2[2];
int ret = pipe(pipe1);
if(ret < 0){
printf("pipe error\n");
return -1;
}
ret = pipe(pipe2);
if(ret < 0){
printf("pipe error\n");
return -1;
}
//创建一个子进程,作为服务器,用于读取管道1(pipe1[0])中的文件名,并将文件的内容输出到管道2的pipe2[1]中
pid_t child_pid = fork();
if(child_pid < 0){
printf("fork error\n");
return -1;
}
else if(child_pid > 0){//父进程
//关闭管道1的写,关闭管道2的读, pipe1[1], pipe2[0]
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0], pipe2[1]);
}
else if(child_pid == 0){//子进程
//关闭管道1的读,关闭管道2的写, pipe1[0], pipe2[1]
close(pipe1[0]);
close(pipe2[1]);
client(pipe2[0], pipe1[1]);
}
waitpid(child_pid, NULL, 0);//等待子进程退出
return 0;
}
运行结果为:
root@linux_ever:~/linux_ever/process_thread/ch4# ./mainpipe
输入文件名字:./data
fileName = ./data
server receive the file name is ./data
server read the fileContent is: file content linuxever
client receive the content is: file content linuxever
有名管道(也称为FIFO):
有名管道用mkfifo函数创建。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
创建命名管道成功返回0;参数是一个pathname是创建的FIFO管道文件的名字;mode用户权限是标识位,比如是0644;
创建了一个管道文件之后,要用它和其它进程通信的时候需要再打开该文件open;
命名管道的打开规则
如果当前打开操作是为读而打开FIFO时1:O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
2:O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
1:O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
2:O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
//outfd = open("fifop", O_WRONLY | O_NONBLOCK);//非阻塞写
outfd = open("fifop", O_WRONLY ); //阻塞模式
例如:
1:一个进程打开命名管道写,默认是阻塞的方式打开;如果该管道没有另外的进程打开读,则该进程阻塞在写打开,直到有进程读打开;
2:一个进程打开命名管道读,默认是阻塞的方式打开;如果该管道没有另外的进程打开写,则该进程阻塞在读打开,直到有进程写打开;
一般以阻塞方式打开,如果是以非阻塞方式打开的话,每次从该文件读取数据的时候会立刻返回,哪怕是没有数据;
命名管道和匿名管道区别:
1:管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。2:如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
3:命名管道是一种特殊类型的文件;
管道可以在父子进程之间传递数据
管道可以在父子进程之间传递数据,利用的是fork调用之后两个管道文件描述符pipefd[0]和pipefd[1]都保持打开。这样就可以实现父进程通过管道向子进程传递数据,或者子进程通过管道向父进程传递数据。
如果是想双向传输数据的话,可以创建两个管道。也可以通过socketpair创建一个全双工的管道。
例子1:父进程通过匿名和子进程通信,父进程向匿名管道写入数据,子进程从匿名管道读出数据并输出到标准输出上面
/*************************************************************************
> File Name: pipe1.cpp
> Author:
> Mail:
> Created Time: 2015年12月20日 星期日 19时48分57秒
************************************************************************/
#include<iostream>
#include <unistd.h>
#include <cstdlib>
#include <fcntl.h>
#include <string>
#include <sys/wait.h>
#include <string.h>
using namespace std;
/*
1:父进程创建一个管道, 0-read, 1-write
2:父进程创建一个子进程
3:让父进程向管道中写入信息,子进程从管道读取信息;从标准输入写入到管道中
4:子进程将读取的信息输出到标准输出上面
*/
int main()
{
int pipefd[2];
char buff[1024];
memset(buff, 0, 1024);
int ret = pipe(pipefd);
if(ret != 0){
cerr << "pipe error..." << endl;
exit(-1);
}
pid_t pid = fork();
if(pid < 0){
cerr << "fork error..." << endl;
exit(-1);
}
else if(pid == 0){
//关闭管道的写端
close(pipefd[1]);
int ret = 0;
cout << "This is child process...." << endl;
while(1){
ret = read(pipefd[0], (void *)buff, 1024);
if(ret == 0){
cout << "end of read.." << endl;
exit(0);
}
cout << "child read: ";
cout << buff << endl;
memset(buff, 0, 1024);
}
close(pipefd[0]);
}
else if(pid > 0){
close(pipefd[0]);
cout << "This is parent process...." << endl;
while(cin.getline(buff, 1024)){
cout << "parent write: ";
cout << buff << endl;
write(pipefd[1], buff, 1024);
memset(buff, 0, 1024);
}
close(pipefd[1]);
wait(NULL);
}
exit(0);
}
例子2:使用命名管道实例
进程1创建命名管道myfifo,并以阻塞方式写打开,进程1从标准输入获得数据并写入到命名管道中;
进程2以阻塞方式读打开该命名管道文件myfifo,进程2从该命名管道中读取数据并输出到标准输出上;
进程1:write_fifo.cpp
/*************************************************************************
> File Name: write_fifo.cpp
> Author:
> Mail:
> Created Time: 2015年12月20日 星期日 20时53分29秒
************************************************************************/
#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
/*
1:创建命名管道
2:从标准输入输入内容到buffer中
3:打开命名管道,将buffer中的内容写入到命名管道中
*/
int main()
{
int ret = mkfifo("myfifo", 0666);
if(ret < 0){
cerr << "mkfifo error..." << endl;
exit(-1);
}
char buff[1024];
memset(buff, 0, 1024);
int wrfd;
cout << "wating for another process open the myfifo to reading..."<< endl;
wrfd = open("myfifo", O_WRONLY);
if(wrfd == -1){
cerr << "open error..." << endl;
exit(-1);
}
pid_t pid = getpid();
cout << "process " << pid << " write: ";
while(cin.getline(buff, 1024)){
write(wrfd, buff, strlen(buff));
memset(buff, 0, 1024);
cout << "process " << pid << " write: ";
}
close(wrfd);
exit(0);
}
进程2:read_fifo.cpp
/*************************************************************************
> File Name: write_fifo.cpp
> Author:
> Mail:
> Created Time: 2015年12月20日 星期日 20时53分29秒
************************************************************************/
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
/*
1:创建命名管道
2:从标准输入输入内容到buffer中
3:打开命名管道,将buffer中的内容写入到命名管道中
*/
int main()
{
char buff[1024];
memset(buff, 0, 1024);
int rdfd;
int ret = 0;
rdfd = open("myfifo", O_RDONLY);
if(rdfd < 0){
cout << "open error..." << endl;
exit(-1);
}
cout << "waiting for reading...\n";
while(1){
ret = read(rdfd, buff, 1024);
if(ret == 0){
cerr << "end of read..." << endl;
break;
}
cout << "process "<< getpid() << " read: " << buff << endl;
memset(buff, 0, 1024);
}
close(rdfd);
exit(0);
}
下面图中的myfifo文件就是创建的命名管道,可以看到它的文件类型为p开头;
popen函数介绍:
标准I/O函数库提供了popen函数,它创建一个管道并启动另一个进程,该进程要么从管道读出标准输入,要么向该管道写入标准输出。
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
如果type为w,那么调用该函数的进程写入command的标准输入。
实例:
/*************************************************************************
> File Name: popen.c
> Author:
> Mail:
> Created Time: 2016年04月22日 星期五 21时28分40秒
************************************************************************/
#include <stdio.h>
#include <string.h>
int main()
{
char buffer[1024];
char command[1024];
memset(buffer, 0, 1024);
memset(command, 0, 1024);
//输入命令
printf("Input the command: ");
fgets(command, 1024, stdin);
//去除换行符号
int n = strlen(command);
if(command[n] = '\n'){
//command[n] = 0;//一定要赋值为0,不能是'0'
command[n] = '\0';//一定要赋值为0,或是'\0', 不能是'0'
}
//调用popen执行命令
//type='r',命令的标准输出会输出到管道,以fp引出来
FILE * fp = popen(command, "r");
if(fp == NULL){
printf("popen error\n");
return -1;
}
printf("输出command的标准输出: ");
//从fp中读取命令的标准输出
while(fgets(buffer, 1024, fp) != NULL)
fputs(buffer, stdout);
pclose(fp);
return 0;
}
运行结果:
输出command的标准输出: root@linux_ever:~/linux_ever/process_thread/ch4# ./readPopen
Input the command: cat data
输出command的标准输出: file content linuxever
root@linux_ever:~/linux_ever/process_thread/ch4# ./readPopen
Input the command: ls
输出command的标准输出: data
mainpipe
mainpipe.c
readPopen
readPopen.c
root@linux_ever:~/linux_ever/process_thread/ch4# ./readPopen
Input the command: ls -l
输出command的标准输出: 总用量 40
-rw-r--r-- 1 root root 23 4月 22 21:08 data
-rwxr-xr-x 1 root root 13377 4月 21 22:12 mainpipe
-rw-r--r-- 1 root root 3499 4月 21 22:12 mainpipe.c
-rwxr-xr-x 1 root root 9063 4月 22 22:08 readPopen
-rw-r--r-- 1 root root 1146 4月 22 22:08 readPopen.c