通過管道來實作程序間的通信的方法很經典,因為多個程序共享3-4G中的核心,是以在核心中存在一個管道(緩沖區),然後程序通過連接配接管道的兩端進而實作通信。假如說我們現在有一根管道,我們從左端放入一個小球,那麼它會從右端滾出來,那麼如果我們同時向兩端都放入一個小球,那麼就不可能實作交叉傳遞了,是以管道是半雙工通信(即雙方都可以發送資訊,但是雙方不能同時發送資訊),是以管道的兩端一端是讀端,一端是寫端。那麼要實作兩個程序的同時讀寫操作,就需要用兩個管道。
pipe
首先先來說一下pipe,這是一個匿名管道(為啥叫匿名呢,下面講命名管道的時候就知道了),實作方式是循環隊列,它隻能用于有血緣關系的程序間通信。首先我們先來看一下pipe函數的原型:
#include <unistd.h>
int pipe(int pipefd[2]);
複制
傳入的參數是一個大小為2的數組,然後就得到了兩個檔案描述符pipefd[0]和pipefd[1],前者用來指向管道的讀端,後者用來指向寫端。用一個父子程序來舉例,如果要實作父子程序間的通信,在fork前就需要建立一個pipe管道,如果建立成功傳回0,如果失敗傳回-1并設定errno,由于子程序複制了父程序的PCB,是以子程序也有父程序的檔案描述符表,是以父子程序的pipefd都指向了同一個pipe管道,然後我們要規定管道的傳輸方向,如果我們要求父寫子讀的話,我們就在父程序中close(pipefd[0]),在子程序中close(pipefd[1])就好了,建立好管道後我們通過write和read函數進行讀寫操作。
那麼在使用pipe通信的時候可能會遇到以下的幾種情況:
1. 當讀管道時,如果管道中沒有資料,則會阻塞,直到管道另一端寫入資料。
2. 當寫管道時,如果管道中已經滿了,則會阻塞,直到管道另一端讀出資料(可見讀出資料時,管道中将不會保留該資料)。
3. 當管道寫端關閉時,讀端讀完管道内的資料時,如果再次去讀沒有資料的管道會傳回0,相當于讀到了EOF。
4. 當管道讀端關閉時,如果寫端在寫入資料時,産生SIGPIPE信号,寫程序預設情況下會終止程序。
下面以父子程序的例子,來寫一個程式來實作一下,代碼如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char buf[1024] = "Hello Child\n";
char str[1024];
int fd[2];
if(pipe(fd) == -1){
perror("pipe");
exit(1);
}
pid_t pid = fork();
// 父寫子讀 0寫端 1讀端
if(pid > 0){
printf("parent pid\n");
close(fd[0]); // 關閉讀端
sleep(5);
write(fd[1], buf, strlen(buf)); // 在寫端寫入buf中的資料
wait(NULL);
close(fd[1]);
}
else if(pid == 0){
close(fd[1]); // 關閉寫端
int len = read(fd[0], str, sizeof(str)); // 在讀端将資料讀到str
write(STDOUT_FILENO, str, len);
close(fd[0]);
}
else {
perror("fork");
exit(1);
}
return 0;
}
複制
運作結果如下:
parent pid
Hello Child
複制
我們可以用fpathconf來檢視管道的緩沖區大小,通過傳入檔案描述符和_PC_PIPE_BUF兩個參數來獲得一個long整型的值。代碼如下:
printf("%ld\n", fpathconf(fd[0], _PC_PIPE_BUF));
複制
還有就是我們可以通過設定O_NONBLOCK參數來實作非阻塞的情況,也就是說當一個程序還沒有寫資料時,另一個讀程序就會阻塞在那裡,那麼如果設定了O_NONBLOCK參數,該程序就不會阻塞在那裡,會傳回-1,并設定errno為EAGAIN,可以用goto語句或者while循環實作,那麼設定O_NONBLOCK使用fcntl函數。
fifo
fifo用來建立一個命名管道,可以解決非血緣關系的程序間通信,它的底層的實作原理和匿名管道相同,隻不過是生成了一個可見的管道檔案。管道檔案用mkfifo指令來建立,如下圖所示:

這個管道檔案連接配接一個在核心中的管道,那麼這個管道檔案對于所有的程序都是可見的,那麼程序通過打開這個管道檔案就可以通過管道檔案所連接配接的管道來實作非血緣關系的程序間通信了。因為這個管道有一個所有程序都可以通路到的管道檔案,是以fifo叫做命名管道,那麼同理,pipe就隻能通過fork的方式來複制檔案描述符表來共享管道,而其他的程序卻通路不到,是以叫做匿名管道。
下面也通過代碼,來簡單的實作一下fifo的效果,這裡我提前用mkfifo的指令來建立了一個管道檔案,當然也可以在代碼中使用mkfifo函數來建立,先來看一下寫操作的程序的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
if(argc < 2){
printf("run error\n");
exit(1);
}
char buf[1024] = "Hello, I'm Charles\n";
int fd = open(argv[1], O_WRONLY);
if(fd == -1){
perror("open file");
exit(1);
}
write(fd, buf, strlen(buf));
close(fd);
return 0;
}
複制
下面是執行讀操作的代碼:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
if(argc < 2){
printf("run error\n");
exit(1);
}
char buf[1024];
int fd = open(argv[1], O_RDONLY);
int len = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
close(fd);
return 0;
}
複制
然後我們開兩個終端去分别運作這兩個程式,我們先運作寫操作的程式,然後再運作讀操作的程式(當你運作寫操作的程式時會阻塞在write,當運作讀操作的程式時才會往下執行,這個就是上面所說的四種情況中的一種),最終結果如下: