Nothing is ever a setback. If anghing, it just motivate you for what is next.--------沒有挫折這回事,他若具有任何意義,那只是激勵你再下一個挑戰中表現更好.
文章目錄
1,前景回顧
進程間的通信肯定是與進程相關,小黑在之前寫過相關的博客,有興趣的或者對這方面這知識有點生疏的同學可以移步:
https://blog.csdn.net/weixin_46027505/article/details/104812719
https://blog.csdn.net/weixin_46027505/article/details/105141592
2,管道真的就是管道
- 在介紹管道前有必要了解一下3種通信方式。
1,
單工通信:傳輸方向只有一個方向,單工通信只有一根數據線,它也只在一個方向上進行,如打印機、電視機等。
比如:電視機,廣播
2,半雙工通信:可以雙向通信,但只能輪流傳輸,也只有一根數據線,不同於單工通信的是這根數據線即可作爲發送又可作爲接收,雖數據可在兩個方向上傳送,但通信雙方不能同時收發數據。
比如:對講機
3,全雙工通信:可以同時雙向傳輸數據,數據的發送和接收用兩根不同的數據線,通信雙方在同一時刻都能進行發送和接收,發送和接收同時進行,沒有延遲。
比如視頻聊天
2.1 管道通俗理解
- 爲什麼這種通信機制叫做管道呢? 因爲它的功能真的就是生活中管道(水道)的作用。
我們把供水商比作一個進程空間,我們自己家是另外一個進程空間,管道就是用來送水的,水就是我們進程間通信傳輸的數據,往水管裏注水就相當於向文件寫入數據
這裏需要記住,管道是單向的、先進先出的,這個容易理解,就像供水商給你送水時,你不能向這個水管注水。
2.2 爲什麼管道是半雙工通信?
由於Linux一個命令只能完成一個功能,所以一個複雜點的任務需要好幾個進程協同完成,第一個進程處理結果需交給第二個進程,然後一次交給第三個,等等,像流水線完成某個商品的生產一樣,這個過程只需要數據單向往下傳輸,所以設計的時候做成了半雙工。
當然也可以使用管道實現雙工通信,需要兩個管道。
2.3 管道特點
管道是,它把一個進程的輸出和另一個進程的輸入連接在一起。
1, 一個進程(寫進程)在管道的尾部寫入數據,另一個進程(讀進程)從管道的頭部讀出數據。
管道是固定讀端和寫端的。寫入的內容每次都添加在管道緩衝區的末尾,並且每次都是從緩衝區的頭部讀出數據。
2, 數據被一個進程讀出後,將被從管道中刪除,其它讀進程將不能再讀到這些數據。
3, 管道提供了簡單的流控制機制,進程試圖讀空管道時,進程將阻塞。同樣,管道已經滿時,進程再試圖向管道寫入數據,進程將阻塞
4,管道對於管道兩端的進程而言,就是一個文件,但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,單獨構成一種文件系統,並且只存在與內存中。
5, 向管道中寫入數據時,linux將不保證寫入的原子性,管道緩衝區一有空閒區域,寫進程就會試圖向管道寫入數據。如果讀進程不讀走管道緩衝區中的數據,那麼寫操作將一直阻塞。
6, 只有在管道的讀端存在時,向管道中寫入數據纔有意義。否則,向管道中寫入數據的進程將收到內核傳來的SIFPIPE信號,應用程序可以處理該信號,也可以忽略(默認動作則是應用程序終止)。
- 當然管道有兩種類型。
1,無名管道:這個只能用於父子進程,這個你可以理解成,你和你爸爸分家了,這個管道就是用來你們兩家送水用的。因爲這個管道你和爸爸
2,命名管道:可以用於同一系統中,任何進程間通信。
後面馬上會介紹到。
3,無名管道(有時會直接叫做管道,不要混淆)
管道是UNIX系統IPC的最古老的形式,所有的UNIX系統都提供此種通信機制。管道的實質是一個內核緩衝區,進程以先進 先出(FIFO, First In First Out)的方式從緩衝區存取數據:管道一端的進程順序地將進程數據寫入緩衝區,另一端的進程則順 序地讀取數據,該緩衝區可以看做一個循環隊列,讀和寫的位置都是自動增加的,一個數據只能被讀一次,讀出以後再緩衝區都 不復存在了。當緩衝區讀空或者寫滿時,有一定的規則控制相應的讀進程或寫進程是否進入等待隊列,當空的緩衝區有新數據寫 入或慢的緩衝區有數據讀出時,就喚醒等待隊列中的進程繼續讀寫。
3.1 侷限性
(1)半雙工,數據只能在一個方向流動,現在有些系統可以支持全雙工管道,但是爲了最佳的可移植性,應認爲系統 不支持全雙工管道;
(2)無名管道只能用於具有父子進程關係通信;
#3 3.2 無名管道的創建
管道可以看成是一種特殊的文件,對於它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,並不 屬於其他任何文件系統,並且只存在於內存中。
管道是通過調用pipe函數創建的。
#include <unistd.h>
int pipe(int fd[2]);
- 返回值:若成功,返回0,若出錯,返回-1.
經由參數fd返回的兩個文件描述符:
fd[0]只爲讀而打開(用來在管道里取水)
fd[1]只爲寫而打開 (用來在管道里注水)
fd[1]的輸出是fd[0]的輸入
4,用無名管道實現單進程通信
其實管道就是文件,下面的示例代碼就是自己給自己發送信息然後讀出來。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#define MSG "my name is xuxiaohei"
int main(int argc, char **argv)
{
int pipe_fd[1];
int rv;
char buf[512];
if (pipe(pipe_fd) < 0)
{
printf("Create pipe failure: %s\n", strerror(errno));
return -1;
}
memset(buf, 0, sizeof(buf));
if (write(pipe_fd[1], MSG, strlen(MSG)) < 0)
{
printf(" write data to pipe failure: %s\n", strerror(errno));
return -2;
}
rv = read(pipe_fd[0], buf, sizeof(buf));
if (rv < 0)
{
printf(" read from pipe failure: %s\n", strerror(errno));
return -3;
}
printf("process read %d bytes data from pipe: \"%s\"\n", rv, buf);
return 0;
}
5,用無名管道實現父子進程通信
下面編寫一個例程,用於父進程給子進程方向發送數據。首先父進程創建管道之後fork(),這時子進程會繼承父進程所有打開的 文件描述符(包括管道),這時對於一個管道就有4個讀寫端(父子進程各有一對管道讀寫端),如果需要父進程往子進程裏寫 數據,則需要在父進程中關閉讀端,在子進程中關閉寫端;而如果需要子進程往父進程中寫數據,則可以在父進程關閉寫端,然 後子進程中關閉讀端。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MSG_STR "This message is from father: Hello, my son!"
int main(int argc, char **argv)
{
int pipe_fd[1];
int rv;
int pid;
char buf[512];
int wstatus;
printf("before creat pipe\n");
if (pipe(pipe_fd) < 0)
{
printf("Create pipe failure: %s\n", strerror(errno));
return -1;
}
printf("before fork\n");
if ((pid = fork()) < 0)
{
printf("Create child process failure: %s\n", strerror(errno));
return -2;
}
else if (pid == 0)
{
printf("son start read message from father\n");
close(pipe_fd[1]);
memset(buf, 0, sizeof(buf));
rv = read(pipe_fd[0], buf, sizeof(buf));
if (rv < 0)
{
printf("Child process read from pipe failure: %s\n", strerror(errno));
return -3;
}
printf("Child process read %d bytes data from pipe: \"%s\"\n", rv, buf);
return 0;
}
close(pipe_fd[0]);
if (write(pipe_fd[1], MSG_STR, strlen(MSG_STR)) < 0)
{
printf("Parent process write data to pipe failure: %s\n", strerror(errno));
return -3;
}
printf("father finish write and wait son read \n");
wait(&wstatus);
return 0;
}
關閉管道的一端
(1)當讀一個寫端被關閉的管道時,在所有數據都被讀取後,read返回0,表示文件結束;
(2)當寫一個讀端被關閉的管道時,則產生信號SIGPIPE,如果忽略該信號或者捕捉該信號並從其處理程序返回,則 wirte返回-1.
如果想要子進程給父進程發信息
則
6,有名管道FIFO(或者叫做命名管道)
當然,有名管道同樣是半雙工的。
前面講到的未命名的管道只能在兩個具有親緣關係的進程之間通信,
通過命名管道(Named PiPe)FIFO,不相關的進程也能 交換數據。
它提供一個路徑名與之關聯,以FIFO的文件形式存在於文件系統中。這樣,即使與FIFO的創建進程不存在親緣關係的進程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信(能夠訪問該路徑的進程以及FIFO的創建進程之間),它在磁盤上有對應的節點,但 沒有數據塊——換言之,只是擁有一個名字和相應的訪問權限,通過mknode()系統調用或者mkfifo()函數來建立的。一旦建 立,任何進程都可以通過文件名將其打開和進行讀寫,而不侷限於父子進程,當然前提是進程對FIFO有適當的訪問權。當不再被 進程使用時,FIFO在內存中釋放,但磁盤節點仍然存在。
因此,通過FIFO不相關的進程也能交換數據。值得注意的是,FIFO嚴格遵循先進先出(first in first out),對管道及FIFO的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如lseek()等文件定位操作。
7,無名管道和有名管道的區別
兩者之間最大的區別就是:
有名在任意兩個進程間可以通信
無名只能在父子進程間通信
關於命名管道小黑另外寫一篇博客實現一個小應用。