Linux/Unix系統IPC是各種程序間通信方式的統稱,但是其中極少能在所有Linux/Unix系統實作中進行移植。随着POSIX和Open Group(X/Open)标準化的推進呵護影響的擴大,情況雖已得到改善,但差别仍然存在。一般來說,Linux/Unix常見的程序間通信方式有:管道、消息隊列、信号、信号量、共享記憶體、套接字等。部落客将在《程序間通信方式總結》系列博文中和大家一起探讨學習程序間通信的方式,并對其進行總結,讓我們共同度過這段學習的美好時光。這裡我們就以其中一種方式管道展開探讨,管道是Linux/Unix系統IPC的最古老形式,并且所有Linux/Unix系統都提供此種通信機制。管道又分為無名管道和有名管道,無名管道隻能用于有親緣關系的程序間通信,有名管道則可以用于非親緣程序間的通信。本篇就以管道中的有名管道(又稱命名管道)為例,希望對你有所幫助。
命名管道也被稱為FIFO檔案,它是一種特殊類型的檔案,它在檔案系統中以檔案名的形式存在,但是它的行為卻和之前所講的沒有名字的管道(匿名管道)類似。
由于Linux中所有的事物都可被視為檔案,是以對命名管道的使用也就變得與檔案操作非常的統一,也使它的使用非常友善,同時我們也可以像平常的檔案名一樣在指令中使用它。
int mkfifo(const char *path, int mode);
與打開其他檔案一樣,FIFO檔案也可以使用open調用來打開。注意,mkfifo函數隻是建立一個FIFO檔案,要使用命名管道需要使用open将其打開。但是有兩點要注意:
①程式不能以O_RDWR模式打開FIFO檔案進行讀寫操作,如果你非要這麼做,其行為未明确定義,因為如果一個管道以讀/寫方式打開,程序就會讀回自己的輸出,我們通常使用FIFO隻是為了單向的資料傳遞。
②傳遞給open調用的是FIFO的路徑名,而不是正常的檔案。
在open函數的調用的第二個參數中,你看到一個陌生的選項O_NONBLOCK,選項O_NONBLOCK表示非阻塞,加上這個選項後,表示open調用是非阻塞的,如果沒有這個選項,則表示open調用是阻塞的。
open(const char *path, int mode);
open調用的阻塞是怎麼一回事呢?很簡單,對于以隻讀方式(O_RDONLY)打開的FIFO檔案,如果open調用是阻塞的(即第二個參數為O_RDONLY),除非有一個程序以寫方式打開同一個FIFO,否則它不會傳回;如果open調用是非阻塞的的(即第二個參數為O_RDONLY | O_NONBLOCK),則即使沒有其他程序以寫方式打開同一個FIFO檔案(前提是FIFO檔案存在),open調用将成功并立即傳回。
對于以隻寫方式(O_WRONLY)打開的FIFO檔案,如果open調用是阻塞的(即第二個參數為O_WRONLY),open調用将被阻塞,直到有一個程序以隻讀方式打開同一個FIFO檔案為止;如果open調用是非阻塞的(即第二個參數為O_WRONLY | O_NONBLOCK),open總會立即傳回,但如果沒有其他程序以隻讀方式打開同一個FIFO檔案,open調用将傳回-1,并且FIFO也不會被打開。哈哈,是不是很繞啊,沒關系,舉個栗子你就明白了,下面我們來看個簡單的栗子吧:
fifowrite.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h> //PIPE_BUF
int main()
{
int fifo_fd = -1;
int data_fd = -1;
const char *filename = "/home/tangf/.vimrc";
const char *fifoname = "./fifo";
char buffer[PIPE_BUF + 1] = {0};
//檢查調用程序是否可以對指定的檔案執行某種操作(成功傳回0,錯誤傳回-1)
//R_OK 測試讀許可權
//W_OK 測試寫許可權
//X_OK 測試執行許可權
//F_OK 測試檔案是否存在
if (-1 == access(fifoname, F_OK))
{
//建立fifoname管道檔案,權限為0666
int ret = mkfifo(fifoname, 0666);
//建立失敗處理
if (-1 == ret)
{
perror("mkfifo");
exit(EXIT_FAILURE);
}
}
//以阻塞方式打開fifoname管道檔案
if (-1 == (fifo_fd = open(fifoname, O_WRONLY)))
{
perror("open fifo_fd");
exit(EXIT_FAILURE);
}
//打開讀檔案
if (-1 == (data_fd = open(filename, O_RDONLY)))
{
close(fifo_fd);
perror("open data_fd:");
exit(EXIT_FAILURE);
}
int read_len = read(data_fd, buffer, PIPE_BUF);
buffer[read_len] = '\0';
while (read_len > 0)
{
//将讀取的資料寫入管道
int ret = write(fifo_fd, buffer, read_len);
//寫入失敗,做異常處理
if (-1 == ret)
{
close(data_fd);//關閉檔案描述符
close(fifo_fd);
perror("write");//列印錯誤資訊
exit(EXIT_FAILURE);//退出程式
}
//從檔案中讀取資料
read_len = read(data_fd, buffer, PIPE_BUF);
buffer[read_len] = '\0';
}
//關閉檔案描述符
close(data_fd);
close(fifo_fd);
exit(EXIT_SUCCESS);
}
fiforead.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <string.h>
#include<limits.h> //PIPE_BUF
int main()
{
int fifo_fd = -1;
int data_fd = -1;
const char *filename = "./vimrc";
const char *fifoname = "./fifo";
char buffer[PIPE_BUF + 1] = {0};
//檢查調用程序是否可以對指定的檔案執行某種操作(成功傳回0,錯誤傳回-1)
//R_OK 測試讀許可權
//W_OK 測試寫許可權
//X_OK 測試執行許可權
//F_OK 測試檔案是否存在
if (-1 == access(fifoname, F_OK))
{
//建立fifoname管道檔案,權限為0666
int ret = mkfifo(fifoname, 0666);
//建立失敗處理
if (-1 == ret)
{
perror("mkfifo");
exit(EXIT_FAILURE);
}
}
}
//以非阻塞方式打開fifoname管道檔案
if (-1 == (fifo_fd = open(fifoname,O_RDONLY | O_NONBLOCK)))
{
perror("open fif_fd");
exit(EXIT_FAILURE);
}
//以隻寫方式打開存儲檔案,不存在則建立
if (-1 == (data_fd = open(filename,O_WRONLY | O_CREAT, 0666)))
{
close(fifo_fd);
perror("open data_fd");
exit(EXIT_FAILURE);
}
//從管道中讀取資料
int read_len = read(fifo_fd, buffer,PIPE_BUF);
buffer[read_len] = '\0';
while (read_len > 0)
{
//将讀取的資料寫入檔案
int ret = write(data_fd, buffer,read_len);
if (-1 == ret)
{
close(data_fd);//關閉描述符
close(fifo_fd);
perror("write");//列印錯誤資訊
exit(EXIT_FAILURE);//退出程式
}
//從管道中讀取資料
read_len = read(fifo_fd, buffer,PIPE_BUF);
buffer[read_len] = '\0';
}
//關閉檔案描述符
close(data_fd);
close(fifo_fd);
exit(EXIT_SUCCESS);
}
程式運作結果:

上面的栗子是兩個程序之間的通信問題,一個程序向FIFO檔案寫資料,而另一個程序則從FIFO檔案中讀取資料。試想這樣一個問題,隻使用一個FIFO檔案,如果有多個程序同時向同一個FIFO檔案中寫入資料,而且隻有一個讀FIFO程序在同一個FIFO檔案中讀取資料時,會發生怎麼樣的情況呢,會發生資料塊的互相交錯麼?而且個人認為多個不同程序向一個FIFO讀程序發送資料是很普通的情況。
為了解決這一問題,就是讓寫操作原子化。怎樣才能使寫操作原子化呢?答案很簡單,系統規定:在一個以O_WRONLY(即阻塞方式)打開的FIFO中,如果寫入的資料長度小于等于PIPE_BUF,那麼或者寫入全部位元組,或者一個位元組都不寫入。如果所有的寫請求都是發往一個阻塞的FIFO的,并且每個寫入請求的資料長度小于等于PIPE_BUF位元組,系統就可以確定資料塊不會交錯在一起。
我們在《程序間通信方式總結——管道(一)》中已經對無名管道(又稱匿名管道)的使用進行的簡單的總結,這裡我們又對有名管道(又稱命名管道)的使用進行的簡單的總結,下面就讓我們一起簡單的聊聊它們之間的差別吧!
使用匿名管道,則通信的程序之間需要一個父子關系,通信的兩個程序一定是由一個共同的祖先程序啟動。但是匿名管道沒有上面說到的資料交叉的問題。
與使用匿名管道相比,我們可以看到write和read這兩個程序是沒有什麼必然的聯系的,如果硬要說他們具有某種聯系,就隻能說是它們都通路同一個FIFO檔案。它解決了之前在匿名管道中出現的通信的兩個程序一定是由一個共同的祖先程序啟動的問題。但是為了資料的安全,我們很多時候要采用阻塞的FIFO,讓寫操作變成原子操作。
關于管道的學習我們就到此結束了,相信大家都有所收獲,希望小夥伴們都已經了解并掌握了管道的常用方法。如果你覺得對程序間通信的方式不勝了解,還有些許疑惑,請關注部落客《程序間通信方式總結》系列博文,相信你在那裡能找到答案。