linux程式間通訊-----管道總結例項

readyao發表於2015-12-20

主要內容:匿名管道,命令管道。(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

相關文章