程序間的通訊-------管道
管道是程序間的通訊方式之一,在學習管道之前,首先了解一下程序間為什麼要通訊?
- 程序間為什麼要進行通訊?
因為程序的獨立性,使得程序的交流變得困難、複雜,因此就產生了程序間的各種通訊方式。
- 程序間通訊的目的:
- 資料傳輸:一個程序需要將它的資料傳送給另外一個程序
- 資源共享:多個程序之間共享同樣的資源
- 通知事件:一個程序需要向另一個或另一組程序傳送訊息,通知它(它們)發生了某事件
- 程序控制:有些程序希望完全控制另一個程序的執行,此時控制程序希望能夠攔截另一個程序的所有陷入和異常並能夠及時知道它的狀態改變。
- 什麼是管道?
我們把從一個程序連線到另一個程序的一個數據流稱為一個“管道”,管道是Unix中最古老的程序間通訊的形式,管道的本質是核心的一塊緩衝區。
- 管道的分類:
- 匿名管道:建立的緩衝區是沒有名字的,不可見於檔案系統,僅用於具有親緣關係的程序間的通訊。
- 命名管道:可見於檔案系統,是一個特殊檔案(管道型別檔案),可用於同一主機上的任意程序間的通訊。
- 管道的建立:
因為Linux下一切接檔案,作業系統為管道提供的操作的方法:檔案操作
a).匿名管道的建立: pipe(int fd[2])
匿名管道建立成功後悔返回兩個檔案描述符供我們對管道進行操作,這個操作也就是我們的io操作: fd[0]:用於管道讀取資料;fd[1]:用於向管道寫入資料
匿名管道的原理其實就是建立一個子程序,子程序複製了父程序的描述附表,因此也有兩個描述符,並且它們指向的是同一管道,這時候它們連個都能訪問到這個管道,因此他們之間就可以通訊了。
- 命名管道的建立:
i)命令建立: mkfifo pipe_filename
ii)程式碼建立: int mkfifo(const char*pathname, mode_t mode)
一個命名管道開啟之後,則所有特性和匿名管道完全相同
- 管道的讀寫規則:
(1)管道無資料:讀取
如果描述符是預設的阻塞屬性,讀取會掛起等待,直到管道有資料
如果描述符被設定為非阻塞屬性,讀取操作將不具備條件,直接報錯返回EAGAIN。
(2)管道資料寫滿了:寫入
如果描述符是預設的阻塞屬性,寫入將會掛起等待,直到有資料被讀取
如果描述符被設定為非阻塞屬性,寫入操作將不具備條件,直接報錯回EAGIN。
- 如果寫入端全部關閉,這時候如果讀取資料,讀取完管道中的資料,然後返回0.
- 如果讀端全部關閉,這時候寫入資料,則會觸發異常,作業系統會給程序傳送SIGPIPE訊號,程序收到這個訊號會退出。
- 當寫入的資料大小超過PIPE_BUFFER,那麼這個操作是一個非原子操作,有可能被打斷。
- 管道的開啟特性
匿名管道在建立後就直接開啟返回描述符
命名管道開啟特性:
(1)如果以只讀開啟命名管道,那麼open函式將阻塞等待,直到有其他程序以寫的方式開啟這個命名管道
(2)如果以只寫開啟命名管道,那麼open函式將阻塞等待,直到有其他的程序以讀的方式開啟這個命名管道
(3)如果命名管道以讀寫方式開啟,將不會阻塞
8.管道的特點:
(1)管道的通訊方式是半雙工,單向通訊,需要雙方通訊時,應建立兩個管道。
(2)管道提供面向位元組流的資料傳輸服務(傳輸的資料無邊界,無規則,收發靈活)。
(3)程序退出,管道便被釋放,所以管道的生命週期隨程序
(4)管道自帶同步與互斥
同步:臨界資源訪問的時序可控性
互斥:對臨界資源同一時間的唯一訪問性
9.匿名管道與命名管道的區別:
(1)匿名管道由pipe函式建立開啟,命名管道由mkfifo函式建立,且開啟方式與匿名管道的開啟方式不同,一旦這些工作完成後,它們具有相同的語義。
(2)匿名管道僅用於具有親緣關係間的程序通訊,而命名管道可用於統一主機上任意程序間的通訊。
匿名管道的建立程式碼:
//這是一個匿名管道的實現,功能:父程序寫如資料,子程序讀取資料
//匿名管道僅能用於具有親緣關係的程序間的通訊
//int pipe(int pipefd[2]);
//pipefd :用於接收匿名管道建立成功後返回的兩個描述符
//pipefd[0]:用於從管道讀取資料
//pipefd[1]:用於向管道寫入資料
//pipe 函式呼叫成功,就會返回一對描述符
//
//成功返回0,失敗返回-1
//
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int main()
{
int fd[2];
//管道需要在建立子程序之前建立好,這樣才能複製
if(pipe<0){
perror("pipe error");
return -1;
}
int pid =-1;
pid = fork ();
if (pid < 0){
return -1;
}else if(pid ==0){
//child read
close(fd[1]);
char buff[1024]={0};
sleep(3);
read(fd[0],"buff",1024);
printf("child: %s",buff);
close(fd[0]);
}else{
//parent write
//父程序寫入資料,因此需要將管道的讀取端關閉
close(fd[0]);
write(fd[1],"hello",5);
write(fd[1],"world",5);
close(fd[1]);
}
return 0;
}
命名管道的建立程式碼:
//這是一個命名管道的操作程式碼,從命名管道中讀取資料
//1.建立一個命名管道:
// int mkfifo(const char* pathname , mode_t mode);
// pathname :管道檔案的路徑名
// mode : 管道檔案的許可權
// 成功 :0 失敗:-1
// 2.開啟管道open
// 3.從管道讀取資料 read
// 4.關閉管道檔案 close
//
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
umask(0);
//1.建立命名管道
//為了防止因為管道檔案已經存在,每次建立失敗
if(mkfifo("./test.fifo",0664)<0){
if(errno == EEXIST){
}else{
perror("mkfifo error");
}
return -1;
}
//2.開啟管道檔案
int fd = open ("./test.fifo‘, O_RDONLY ");
if(fd < 0){
perror("open fifo error");
return -1;
}
printf("open fifo file sucess!! read start!!");
while(1){
char buff[1024] = {0};
int ret=read(fd,buff,1023);
if(ret >0){
printf("client say:[%s]\n",buff);
}else if(ret == 0){
//管道特性:如果所有的寫段都關閉,那麼讀取時返回0
printf("all write point close!!\n");
sleep(1);
}
}
close(fd);
return 0;
}