Liunx進程間通信—管道

  管道

管道,顧名思義你可以把它想成一根數據線,連接了兩個進程,使他們可以互相通信。更嚴謹來說,它是一個文件或者一塊共享區,一個進程往裏面寫數據,另一個進程從裏面拿數據,以此種方式完成進程間通信。 
管道是UNIX系統IPC最古老的形式,所有的UNIX系統都提供此種通信機制(UNIX系統IPC是各種進程通信方式的統稱)。 

管道在進行通信時,基於字節流管道是單向的、先進先出的。它將一個程序的輸入和另一個程序的輸出連接起來。數據被一個進程讀出後,將被從管道中刪除。

分位兩種 :匿名管道:只能完成具有血緣關係間進程(或者說具有公共祖先的進程)的通信。如父子、兄弟進程。

                 命名管道:允許同一系統內任意兩個進程間通信。


  匿名管道(pipe)


1.創建函數pipe

匿名管道是通過調用函數pipe創建的:

#include<unistd.h>
int pipe(int fd[2]);
  • 1
  • 2
  • 1
  • 2

返回值:成功返回0,失敗返回-1。 
該函數會經過參數fd返回兩個文件描述符fd[0]和fd[1]。我們可以理解爲fd[0]是爲了從管道中讀數據而打開的;fd[1]是爲了向管道中寫數據而打開的。所以管道在用戶程序看了像是打開了一個文件。向這個文件讀寫數據其實就是在讀寫內核緩衝區。


2.原理


1.父進程調用pipe()開闢一個管道,使用open()得到兩個文件描述符指向這個管道兩端。

2.父進程調用fork()創建子進程,那麼子進程也有兩個相同的文件描述符指向這個管道的兩端。

3.父進程關閉讀端,子進程關閉寫端,父進程可以往管道里寫數據,子進程可以從管道里讀數據。管道是通過環形隊列實現的,數據從寫端流入,從讀端流出,這樣就實現了進程間通信。


3.代碼演示

#include<stdio.h>
#include<string.h>
#include<unistd.h>

int main()
{
    int fd[2];
    int ret = pipe(fd);//創建管道
    if(ret == -1)
    {
        perror("pipe");
        return 0;
    }     
    pid_t id = fork();
    if(id == -1)
    {
        perror("fork");
        return 2;
    }
    else if(id == 0)//child 從管道里面讀取
    {
        close(fd[1]);
        int i = 0;
        char read_buf[1024] = {0};
        while(i<10)
        {  
            size_t ret = read(fd[0],read_buf, sizeof(read_buf));
            if(ret< 0)
            {
                perror("write");
                return 1;
            }
            printf("child process read -->%s\n",read_buf);
            i++;
        }
    }
    else//father 往管道里面寫入
    {
        close(fd[0]);
        char *write_buf = "father process writes data to the pipe";
        int i = 0;
        while(i < 10)
        {
            size_t ret = write(fd[1],write_buf,strlen(write_buf)+1);
            if(ret< 0)
            {
                perror("write");
                return 1;
            }
            printf("father finished writing\n");
            sleep(2);
            i++;
        }
        wait();
    }
    return 0;
}


匿名管道有以下四種情況需要注意:

      1.當管道的寫端已被關閉,這時有一個進程去讀這個管道時,在管道內的剩餘數據都被讀取完之後就,read函數就會返回0,相           當於讀到文件末尾。

      2.當管道寫端沒有關閉,但在進程的讀速度大於另一個進程的寫速度時,在管道內的剩餘數據都被讀取後,read會阻塞,直到管         道內有新的數據。

     3.當管道的讀端已被關閉,這時有一個進程向管道寫數據時,這時進程就會收到SIGPIPE信號,導致進程異常終止。 

     4.當管道讀端沒有關閉,但在進程的寫速度大於另一個進程的讀速度時,在管道被寫滿之後,write被阻塞,直到管道有空位置。


  命名管道(FIFO) 


FIFO被成爲名名管道,命名管道不同於PIPE之處在於命名管道是一個設備文件,它真真在在的存在於硬盤之上,存在於文件系統中。而匿名管道存在於內存或者內核中,它對文件系統不可見。也正因爲如此,命名管道可以完成任意兩個進程間的通信。命名管道的創建有一個路徑名path與之關聯,以FIFO文件的形式存儲在文件系統中,所以只要可以訪問該路徑,就能夠通過FIFO互相通信。 
注意:FIFO是按照先入先出的規則進行數據讀寫,第一個被寫入的也將最先被讀出。


1.相關函數

(1)創建管道

在shell命令行上可以通過mknod或者mkfifo創建命名管道文件

在程序內通過調用mknod和mkfifo函數也可以創建命名管道文件。

#include<sys/types.h>
#include<sys/stat.h>
int mknod(const char* path,mode_t mod,dev_t dev)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

函數mknod的參數path即爲創建的命名管道的全路徑名(包括管道文件的文件名),如”/home/tmp/fifo”。第二個參數mod爲創建命名管道的模式(一般爲S_IFIFO|0666,0666表示管道IPC的默認權限,這裏要注意umask對生成管道文件權限的影響)。dev爲設備值,取決於文件創建的種類,它只在創建設備文件時纔會用到。 
返回值:成功返回0,失敗返回-1。

#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* path,mode_t mode)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

此函的的兩個參數與上相同,返回值也相同,成功返回0,失敗返回-1。 
mknod是比較老的函數,mkfifo函數更加的簡單規範,所以建議儘量使用mkfifo。

(2)獲取指向管道的文件描述符

當我們創建好管道之後並不能直接使用它,使用之前必須調用open函數將其打開,並獲取文件描述符。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

參數pathname即爲我們要打開的管道文件

參數flags爲打開方式,一般有以只讀方式打開O_RDONLY 或以只寫方式打開O_WRONLY。 
open函數的返回值爲一個文件描述符,我們通過這個文件描述符對管道進行讀寫操作。

2.代碼實例

server.c

#include<string.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PATH "/home/long/task/pipe/fifoname"

int main()
{
    int ret = mkfifo(PATH, S_IFIFO|0666);
    if(ret < 0)
    {
        perror("mkfifo");
        return 1;
    }
   
    int fd = open(PATH,O_WRONLY);
    if(fd < 0) 
    {
        perror("open");
        return 2;
    }
  
    char buf[100] = {0};
    while(1)
    {
        memset(buf,'\0',sizeof(buf)/sizeof(buf[0]));
        printf("server say:");
        scanf("%s",buf);
        int ret =  write(fd,buf,sizeof(buf)/sizeof(buf[0])+1);
        if(ret < 0)
        {  
            perror("write");
            break; 
        }
        if(strncmp(buf,"quit",4) == 0)
        {
            break; 
        }
    }
    close(fd);
    printf("server end...\n");
    return 0;
}

client.c

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PATH "/home/long/task/pipe/fifoname"

int main()
{
    int fd = open(PATH,O_RDONLY);
    if(fd < 0)
    {  
        perror("open");
        return 1;
    }
    char buf[1024] = {0};
    while(1)
    {
        size_t ret = read(fd,buf,sizeof(buf)/sizeof(buf[0]));
        if(ret < 0)
        { 
            perror("read");
            break; 
        }
        printf("client read --> %s\n",buf);
        if(strncmp(buf,"quit",4) == 0)
        {
            break;
        }
    }
    close(fd);
    printf("client end...\n");
    return 0;
}

  1. 文件系統中的路徑名是全局的,各個進程都可以訪問,因此可以用文件系統的路徑名來標識一個IPC通道。
  2. 命名管道FIFO是一個特殊的設備文件,它在文件系統中真實存在。
  3. 命名管道與匿名管道的使用方法是一致的,只是命名管道在使用前要調用open函數打開。因爲命名管道是存在於硬盤上的真實文件,而匿名管道是存在與內存中的特殊文件。
  4. 命名管道打開方式的不同也可能引起阻塞。如果同時以讀寫方式打開,則一定不會阻塞;若單單以讀方式打開,則調用open函數的進程會一直阻塞到有寫方式打開;同樣,單單以寫方式打開的進程會一直阻塞到有讀方式打開。當然在阻塞時我們可以異常終止程序。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章