歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux進程間通信(IPC)編程實踐(二)FIFO命名管道

Linux進程間通信(IPC)編程實踐(二)FIFO命名管道

日期:2017/3/1 12:24:26   编辑:關於Linux

在前一篇文章中,我們講解了如何使用匿名管道來在進程之間傳遞數據,同時也看到了這個方式的一個缺陷,就是這些進程都由一個共同的祖先進程啟動,這給我們在不相關的的進程之間交換數據帶來了不方便。這裡將會介紹進程的另一種通信方式——命名管道,來解決不相關進程間的通信問題。

什麼是命名管道 命名管道也被稱為FIFO文件,它是一種特殊類型的文件,它在文件系統中以文件名的形式存在,但是它的行為卻和之前所講的沒有名字的管道(匿名管道)類似。 由於Linux中所有的事物都可被視為文件,所以對命名管道的使用也就變得與文件操作非常的統一,也使它的使用非常方便,同時我們也可以像平常的文件名一樣在命令中使用。FIFO只是借用了文件系統(file system,命名管道是一種特殊類型的文件,因為Linux中所有事物都是文件,它在文件系統中以文件名的形式存在。)來為管道命名。寫模式的進程向FIFO文件中寫入,而讀模式的進程從FIFO文件中讀出。當刪除FIFO文件時,管道連接也隨之消失。FIFO的好處在於我們可以通過文件的路徑來識別管道,從而讓沒有親緣關系的進程之間建立連接 創建一個命名管道
#include   
#include   
int mkfifo(const char *pathname, mode_t mode);  
//實例
int main()  
{  
    if (mkfifo("p2", 0644) == -1)  
        err_exit("mkfifo error");  
}  
mknod函數也可以創建一個命名管道,但是mknod是比較老的函數,而使用mkfifo函數更加簡單和規范,所以建議在可能的情況下,盡量使用mkfifo而不是mknod。

FIFO與PIPE的區別:

1) 匿名管道由pipe函數創建並打開。

命名管道由mkfifo函數創建,打開用open。

2) FIFO(命名管道)與pipe(匿名管道)之間唯一的區別在它們創建與打開的方式不同,一但這些工作完成之後,它們具有相同的語義。

打開FIFO文件 與打開其他文件一樣,FIFO文件也可以使用open調用來打開。注意,mkfifo函數只是創建一個FIFO文件,要使用命名管道還是將其打開。 但是有兩點要注意,1、就是程序不能以O_RDWR模式打開FIFO文件進行讀寫操作,而其行為也未明確定義,因為如一個管道以讀/寫方式打開,進程就會讀回自己的輸出,同時我們通常使用FIFO只是為了單向的數據傳遞。2、就是傳遞給open調用的是FIFO的路徑名,而不是正常的文件。 打開FIFO文件通常有4種方式:
open(const char *path, O_RDONLY);//1  
open(const char *path, O_RDONLY | O_NONBLOCK);//2  
open(const char *path, O_WRONLY);//3  
open(const char *path, O_WRONLY | O_NONBLOCK);//4  
\
open調用的阻塞是什麼一回事呢?很簡單,對於以只讀方式(O_RDONLY)打開的FIFO文件,如果open調用是阻塞的(即第二個參數為O_RDONLY),除非有一個進程以寫方式打開同一個FIFO,否則它不會返回;如果open調用是非阻塞的的(即第二個參數為O_RDONLY | O_NONBLOCK),則即使沒有其他進程以寫方式打開同一個FIFO文件,open調用將成功並立即返回。
對於以只寫方式(O_WRONLY)打開的FIFO文件,如果open調用是阻塞的(即第二個參數為O_WRONLY),open調用將被阻塞,直到有一個進程以只讀方式打開同一個FIFO文件為止;如果open調用是非阻塞的(即第二個參數為O_WRONLY | O_NONBLOCK),open總會立即返回,但如果沒有其他進程以只讀方式打開同一個FIFO文件,open調用將返回-1,並且FIFO也不會被打開。

下面這個例子介紹 兩個進程通過FIFO對拷數據:利用管道,兩個進程間進行文件復制。

1.進程writefifo:

(1)讀文件(文件名從命令行參數中獲取)

(2)寫入管道myFifo(管道由該進程創建)

(3)關閉文件及管道

2.進程readfifo:

(1)讀管道myFifo

(2)寫入文件[該文件有進程創建並打開]

(3)關閉文件

(4)刪除管道

//1:writefifo  
int main(int argc, char *argv[])  
{  
    if (argc < 2)  
        err_quit("Usage: ./writefifo ");  
  
    // 創建管道  
    if (mkfifo("myFifo", 0644) == -1)  
        err_exit("mkfifo error");  
    int outfd = open("myFifo", O_WRONLY);   //打開FIFO  
    int infd = open(argv[1], O_RDONLY);     //打開文件  
    if (outfd == -1 || infd == -1)  
        err_exit("open file/fifo error");  
  
    char buf[BUFSIZ];  
    int readBytes;  
    while ((readBytes = read(infd, buf, sizeof(buf))) > 0)  
    {  
        write(outfd, buf, readBytes);  
    }  
    close(infd);  
    close(outfd);  
}  

//2:readfifo  
int main(int argc, char *argv[])  
{  
    if (argc < 2)  
        err_quit("Usage: ./writefifo ");  
  
    int outfd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0644);  //創建並打卡文件  
    int infd = open("myFifo", O_RDONLY);    //打開FIFO  
    if (infd == -1 || outfd == -1)  
        err_exit("open file/fifo error");  
  
    char buf[BUFSIZ];  
    int readBytes;  
    while ((readBytes = read(infd, buf, sizeof(buf))) > 0)  
    {  
        write(outfd, buf, readBytes);  
    }  
  
    close(outfd);  
    unlink("myFifo");   //刪除FIFO  
}  
分析:兩個程序都使用阻塞模式的FIFO,為了讓大家更清楚地看清楚阻塞究竟是怎麼一回事,首先我們運行writefifo,並把它放到後台去運行。這時調用jobs命令,可以看到它確實在後台運行著,過了5秒後,再調用jobs命令,可以看到進程writefifo還沒有結束,它還在繼續運行。因為writefifo進程的open調用是阻塞的,在readfifo還沒有運行時,也就沒有其他的進程以讀方式打開同一個FIFO,所以它就一直在等待,open被阻塞,沒有返回。然後,當我們進程readfifo運行時(為了查看性能,在time命令中運行),writefifo中的open調用返回,進程開始繼續工作,然後結束進程。而readfifo的open調用雖然也是阻塞模式,但是writefifo早已運行,即早有另一個進程以寫方式打開同一個FIFO,所以open調用立即返回。

前面的例子是兩個進程之間的通信問題,也就是說,一個進程向FIFO文件寫數據,而另一個進程則在FIFO文件中讀取數據。試想這樣一個問題,只使用一個FIFO文件,如果有多個進程同時向同一個FIFO文件寫數據,而只有一個讀FIFO進程在同一個FIFO文件中讀取數據時,會發生怎麼樣的情況呢,會發生數據塊的相互交錯是很正常的?

為了解決這一問題,就是讓寫操作的原子化。怎樣才能使寫操作原子化呢?答案很簡單,系統規定:在一個以O_WRONLY(即阻塞方式)打開的FIFO中, 如果寫入的數據長度小於等待PIPE_BUF,那麼或者寫入全部字節,或者一個字節都不寫入。如果所有的寫請求都是發往一個阻塞的FIFO的,並且每個寫記請求的數據長度小於等於PIPE_BUF字節,系統就可以確保數據決不會交錯在一起。
Copyright © Linux教程網 All Rights Reserved