1. 程式人生 > >管道和FIFO

管道和FIFO

jpg sca message main can 祖先 數據流 receive orm

  1. 管道(pipe)
管道在Unix及Linux進程間通信是最基礎的,很容易理解。管道就像一個自來水管,一端註入水,一端放出水,水只能在一個方向上流動,而不能雙向流動。管道是典型的單向通信,即計算機網絡中所說的“半雙工”。管道又名匿名管道,所以只能用在具有公共祖先的進程之間使用,通常使用在父子進程之間通信。通常是父進程創建一個管道,然後fork一個子進程,此後父子進程共享這個管道進行通信。   管道由pipe函數創建,函數原型如下: #include<unistd.h> int  pipe(int fd[2]); 成功返回0,否則返回-1;參數fd返回兩個文件描述符,fd[0]為讀,fd[1]為寫,fd[1]的輸入是fd[0]的輸出。即fd[0]對應讀端,fd[1]對應寫端。 舉例說明一下管道的用法:模擬client-server通信過程,父進程模擬client,子進程模擬server。server向client發送一個字符串,client接收到輸出到屏幕。
 1 #include <unistd.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <string.h>
 7 
 8 int main()
 9 {
10     int fd[2];
11     pid_t childpid;
12     char buf[100];
13 
14     memset(buf,0,100);
15     //創建一個管道
16     if(pipe(fd) == -1)
17     {
18         perror("pipe() error");
19         exit(-1);
20     }
21     //創建一個子進程
22     childpid = fork();
23     if(childpid == 0)
24     {
25         printf("server input a message : ");
26         scanf("%s",buf);
27         //關閉讀端
28         close(fd[0]);
29         write(fd[1],buf,strlen(buf));
30         exit(0);
31     }
32     if(childpid == -1)
33     {
34         perror("fork() error");
35         exit(-1);
36     }
37     //父進程關閉寫端
38     close(fd[1]);
39     read(fd[0],buf,100);
40     printf("client read a message: %s\n",buf);
41     waitpid(childpid,NULL,0);
42     return 0;
43 }

程序執行結果如下:

技術分享圖片

上面程序的細節問題在於子進程需要關閉讀端,父進程需要關閉寫端。因為管道最早提出時候是單向,雖然現在有些系統提供全雙工的管道。那麽如何采用管道實現雙向通信呢?很顯然我們需要兩個管道,控制兩個不同的數據流向。現在有模擬一個Client和Server雙向通信的過程,Client與Server之間可以相互發送和接收信息。此時需要兩個管道進行模擬,管道1模擬Server寫Client讀數據流向,管道2模擬Client寫Server讀數據流向。代碼如下所示:

 1 #include <unistd.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 #include <errno.h>
 6 #include <string.h>
 7 
 8 int main()
 9 {
10     int fd1[2],fd2[2];
11     pid_t childpid;
12     char buf[100];
13 
14     memset(buf,0,100);
15     
16     if(pipe(fd1) == -1)
17     {
18         perror("pipe() error");
19         exit(-1);
20     }
21     if(pipe(fd2) == -1)
22     {
23         perror("pipe() error");
24         exit(-1);
25     }
26     childpid = fork();
27     if(childpid == 0)
28     {
29         printf("Server input a message : ");
30         gets(buf);
31         close(fd1[0]);
32         close(fd2[1]);
33         write(fd1[1],buf,strlen(buf));
34         read(fd2[0],buf,100);
35         printf("Server received message from client:%s\n",buf);
36         exit(0);
37     }
38     if(childpid == -1)
39     {
40         perror("fork() error");
41         exit(-1);
42     }
43     close(fd1[1]);
44     close(fd2[0]);
45     read(fd1[0],buf,100);
46     printf("Client receive a message from server: %s\n",buf);
47     printf("Client input a message : ");
48     gets(buf);
49     write(fd2[1],buf,strlen(buf));
50     waitpid(childpid,NULL,0);
51     return 0;
52 }

程序執行結果如下:

技術分享圖片

  2 FIFO(first in first out)

  FIFO又名有名管道,相對於上述管道而言。管道沒有名字,因此只能在具有共同祖先進程的各個進程之間通信,無法在無親緣關系的兩個進程之間創建一個管道進行通信。為此有了FIFO,類似管道,也是一個單向(半雙工)數據流,每個FIFO有一個路徑名與之關聯,從而允許無親緣關系的進程訪問同一個FIFO。FIFO有mkfifo函數創建。

#include <sys/types.h>

#include <sys/stat.h>

int mkfifo(const char *pathname,mode_t mode); 成功返回0,出錯返回-1。pathname是一個普通的路徑名,是FIFO的名字,mode指定文件的權位。

在創建FIFO後,必須打開來讀或者打開來寫,不能打開來既讀既寫(因為FIFO是半雙工)。現在采用FIFO實現上面的第二個例子,代碼如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <errno.h>
 7 #include <string.h>
 8 #include <fcntl.h>
 9 
10 #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
11 
12 #define FIFO1   "/tmp/fifo.1"
13 #define FIFO2   "/tmp/fifo.2"
14 
15 int main()
16 {
17     int readfd,writefd;
18     pid_t   childpid;
19     char buf[100];
20     memset(buf,0,100);
21     //創建FIFO
22     if((mkfifo(FIFO1,FILE_MODE) < 0) && (errno != EEXIST))
23     {
24         perror("mkfifo() error");
25         exit(-1);
26     }
27     if((mkfifo(FIFO2,FILE_MODE) < 0) && (errno != EEXIST))
28     {
29         unlink(FIFO1);
30         perror("mkfifo() error");
31         exit(-1);
32     }
33      //創建子進程
34     childpid = fork();
35     if(childpid == 0)
36     {
37         readfd = open(FIFO1,O_RDONLY,0);
38         writefd = open(FIFO2,O_WRONLY,0);
39         printf("Server input a message: ");
40         gets(buf);
41         write(writefd,buf,strlen(buf));
42         read(readfd,buf,100);
43         printf("Server received a message from Client: %s\n",buf);
44         exit(0);
45     }
46     if(childpid == -1)
47     {
48         perror("frok() error");
49         exit(-1);
50     }
51      //防止死鎖,註意順序
52     writefd = open(FIFO1,O_WRONLY,0);
53     readfd = open(FIFO2,O_RDONLY,0);
54     read(readfd,buf,100);
55     printf("Client received a message form Server: %s\n",buf);
56     printf("Client input a mesage: ");
57     gets(buf);
58     write(writefd,buf,strlen(buf));
59     waitpid(childpid,NULL,0);
60     close(readfd);
61     close(writefd);
62     unlink(FIFO1);
63     unlink(FIFO2);
64     return 0;
65 }

運行結果如下:

技術分享圖片

上面的程序當中父進程打開FIFO的順序不能顛倒,否則會造成死鎖。因為在當前沒有任何進程打開某個FIFO來寫的時候,打開該FIFO來讀的進程將會阻塞。交換父進程中兩個open的調用順序後,父子進程都將打開同一個FIFO進行讀,而當前沒有任何進程來打開該文件進行寫,於是父子進程都阻塞,造成死鎖。

下面采用FIFO實現無親緣關系的兩個進程之間的通信。Client與Server是兩個獨立的進程。

 1 //公共同文件fifo.h
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/stat.h>
 7 #include <errno.h>
 8 #include <string.h>
 9 #include <fcntl.h>
10 
11 #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
12 
13 #define FIFO1   "/tmp/fifo.1"
14 #define FIFO2   "/tmp/fifo.2"
 1 //server進程 server.c 
 2 #include "fifo.h"
 3 
 4 int main()
 5 {
 6     int readfd,writefd;
 7     pid_t   childpid;
 8     char buf[100];
 9     memset(buf,0,100);
10     //創建FIFO
11     if((mkfifo(FIFO1,FILE_MODE) < 0) && (errno != EEXIST))
12     {
13         perror("mkfifo() error");
14         exit(-1);
15     }
16     if((mkfifo(FIFO2,FILE_MODE) < 0) && (errno != EEXIST))
17     {
18         unlink(FIFO1);
19         perror("mkfifo() error");
20         exit(-1);
21     }
22     readfd = open(FIFO1,O_RDONLY,0);
23     writefd = open(FIFO2,O_WRONLY,0);
24     printf("Server input a message: ");
25     gets(buf);
26     write(writefd,buf,strlen(buf));
27     read(readfd,buf,100);
28     printf("Server received a message from Client: %s\n",buf);
29     return 0;
30 }
 1 //client進程 client。c
 2 #include "fifo.h"
 3 
 4 int main()
 5 {
 6     int readfd,writefd;
 7     pid_t   childpid;
 8     char buf[100];
 9     memset(buf,0,100);
10     //創建FIFO
11     if((mkfifo(FIFO1,FILE_MODE) < 0) && (errno != EEXIST))
12     {
13         perror("mkfifo() error");
14         exit(-1);
15     }
16     if((mkfifo(FIFO2,FILE_MODE) < 0) && (errno != EEXIST))
17     {
18         unlink(FIFO1);
19         perror("mkfifo() error");
20         exit(-1);
21     }
22 
23      //防止死鎖,註意順序
24     writefd = open(FIFO1,O_WRONLY,0);
25     readfd = open(FIFO2,O_RDONLY,0);
26     read(readfd,buf,100);
27     printf("Client received a message form Server: %s\n",buf);
28     printf("Client input a mesage: ");
29     gets(buf);
30     write(writefd,buf,strlen(buf));
31     close(readfd);
32     close(writefd);
33     unlink(FIFO1);
34     unlink(FIFO2);
35     return 0;
36 }

先執行server進程,然後執行client進程:結果如下:

技術分享圖片

技術分享圖片

以上介紹了管道和FIFO的操作方法。

參考資料:

《Unix環境高級編程》

管道和FIFO