linux进程间通信-----管道总结实例

主要内容:匿名管道,命令管道。(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]客户端读


/*************************************************************************
	> 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为r,那么调用该函数的进程读入command的标准输出。
如果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

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值