文章目錄
- 在Linux系統下,有名管道可由兩種方式建立:命令列方式mknod系統調用和函數mkfifo。
Linux處理序間通訊機制:
1.同一主機處理序間通訊機制:
Unix方式:有名管道FIFO、無名管道PIPE、訊號Signal
SystemV方式:訊號量、訊息佇列、共用記憶體
2.網路通訊:RPC(Remote Procedure Call)、Socket
管道
管道是處理序間通訊中最古老的方式,它包括無名管道和有名管道兩種,前者可用於具有親緣關係進程間的通訊,即可用於父進程和子進程間的通訊,後者額克服了管道沒有名字的限制,因此,除具有前者所具有的功能外,它還允許無親緣關係進程間的通訊,即可用於運行於同一台機器上的任意兩個進程間的通訊。
無名管道由pipe()函數建立:
#i nclude <unistd.h>
int pipe(int filedis[2]);
參數filedis返回兩個檔案描述符:filedes[0]為讀而開啟,filedes[1]為寫而開啟。filedes[1]的輸出是filedes[0]的輸入。 無名管道佔用兩個檔案描述符, 不能被非血緣關係的進程所共用, 一般應用在父子進程中.
特點:
半雙工單向、父子進程間、一端寫一段讀、沒有名字、緩衝區有限、資料無格式
無名管道常用於父子進程中, 可簡單分為單向管道流模型和雙向管道流模型. 其中, 單向管道流根據流向分為從父進程流向子進程的管道和從子進程流向父進程的管道.
下面設計一個執行個體, 資料從父進程流向子進程:父進程向管道寫入一行字元, 子進程讀取資料並列印到
螢幕上.
[bill@billstone Unix_study]$ cat pipe1.c
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <assert.h>
int main()
{
int fildes[2];
pid_t pid;
int i,j;
char buf[256];
assert(pipe(fildes) == 0); // 建立管道
assert((pid = fork()) >= 0); // 建立子進程
if(pid == 0){ // 子進程
close(fildes[1]); // 子進程關閉管道輸出
memset(buf, 0, sizeof(buf));
j = read(fildes[0], buf, sizeof(buf));
fprintf(stderr, "[child] buf=[%s] len[%d]\n", buf, j);
return;
}
close(fildes[0]); // 父進程關閉管道輸入
write(fildes[1], "hello!", strlen("hello!"));
write(fildes[1], "world!", strlen("world!"));
return 0;
}
[bill@billstone Unix_study]$ make pipe1
cc pipe1.c -o pipe1
[bill@billstone Unix_study]$ ./pipe1
[child] buf=[hello!world!] len[12] // 子進程一次就可以讀出兩次父進程寫入的資料
[bill@billstone Unix_study]$
從上面可以看出, 在父進程中關閉 fildes[0], 向 fildes[1]寫入資料; 在子進程中關閉 filedes[1], 從
fildes[0]中讀取資料可實現從父進程流向子進程的管道.
在進程的通訊中, 我們無法判斷每次通訊中報文的位元組數, 即無法對資料流進行 自行拆分, 側耳發生了上例中子進程一次性讀取父進程兩次通訊的報文情況.
管道是進程之間的一種單向交流方法, 要實現進程間的雙向交流, 就必須通過兩個管道來完成. 雙向管道流的創立過程如下:
(1) 建立管道, 返回兩個無名管道檔案描述符 fildes1 和 fildes2:
(2) 建立子進程, 子進程中繼承管道 fildes1 和 fildes2.
(3) 父進程關閉唯讀檔案描述符 fildes1[0], 唯寫描述符 fildes2[1]
(4) 子進程關閉唯寫檔案描述符 fildes1[1], 唯讀描述符 fildes2[0]
建立的結果如下:
父進程 --寫--> fildes1[1] --管道--> fildes1[0] --讀--> 子進程
父進程 <--讀-- fildes2[0] <--管道-- fildes2[1] <--寫-- 子進程
這裡實現一個父子進程間雙向通訊的執行個體: 父進程先向子進程發送兩次資料, 再接收子進程傳送剛來
的兩次資料.
為了正確拆分時間留從父進程流向子進程的管道採用'固定長度'方法傳送資料; 從子進程流向
父進程的管道採用'顯式長度'方法傳回資料.
(1) 固定長度方式
char bufG[255];
void WriteG(int fd, char *str, int len){
memset(bufG, 0, sizeof(bufG));
sprintf(bufG, "%s", str);
write(fd, bufG, len);
}
char *ReadG(int fd, int len){
memset(bufG, 0, sizeof(bufG));
read(fd, bufG, len);
return(bufG);
}
在此設計中, 父子程式需要約定好每次發送資料的長度; 且長度不能超過 255 個字元.
(2) 顯式長度方式
char bufC[255];
void WriteC(int fd, char str[]){
sprintf(bufC, "%04d%s", strlen(str), str);
write(fd, bufC, strlen(bufC));
}
char *ReadC(int fd){
int i, j;
memset(bufC, 0, sizeof(bufC));
j = read(fd, bufC, 4);
i = atoi(bufC);
j = read(fd, bufC, i);
return(bufC);
}
父子進程約定在發送訊息前先指明訊息的長度.
(3) 主程式
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <sys/types.h>
int main()
{
int fildes1[2], fildes2[2];
pid_t pid;
char buf[255];
assert(pipe(fildes1) == 0);
assert(pipe(fildes2) == 0);
assert((pid = fork()) >= 0);
if(pid == 0){
close(fildes1[1]);
close(fildes2[0]);
strcpy(buf, ReadG(fildes1[0], 10));
fprintf(stderr, "[child] buf = [%s]\n", buf);
WriteC(fildes2[1], buf);
strcpy(buf, ReadG(fildes1[0], 10));
fprintf(stderr, "[child] buf = [%s]\n", buf);
WriteC(fildes2[1], buf);
return(0);
}
close(fildes1[0]);
close(fildes2[1]);
WriteG(fildes1[1], "hello!", 10);
WriteG(fildes1[1], "world!", 10);
fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0]));
fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0]));
return 0;
}
執行結果如下:
[bill@billstone Unix_study]$ make pipe2
cc pipe2.c -o pipe2
[bill@billstone Unix_study]$ ./pipe2
[child] buf = [hello!]
[child] buf = [world!]
[father] buf = [hello!]
[father] buf = [world!]
[bill@billstone Unix_study]$
dup dup2複製檔案描述符
在Linux系統下,有名管道可由兩種方式建立:命令列方式mknod系統調用和函數mkfifo。
下面的兩種途徑都在目前的目錄下產生了一個名為myfifo的有名管道:
方式一:mkfifo("myfifo","rw");
方式二:mknod myfifo p
產生了有名管道後,就可以使用一般的檔案I/O函數如open、close、read、write等來對它進行操作。
管道是 UNIX 中最古老的處理序間通訊工具, 它提供了進程之間的一種單向通訊的方法.
popen 模型
從前面的程式可以看出, 建立串連標準 I/O 的管道需要多個步驟, 這需要使用大量的代碼, UNIX 為了
簡化這個操作, 它提供了一組函數實現之. 原型如下:
#include <stdio.h>
FILE *popen(const char *command, char *type);
int pclose(FILE *stream);
函數 popen 調用成功時返回一個標準的 I/O 的 FILE 檔案流, 其讀寫屬性由參數 type 決定.
這裡看一個類比 shell 命令'ps -ef | grep init'的執行個體.
[bill@billstone Unix_study]$ cat pipe3.c
#include <stdio.h>
#include <assert.h>
int main()
{
FILE *out, *in;
char buf[255];
assert((out = popen("grep init", "w")) != NULL); // 建立寫管道流
assert((in = popen("ps -ef", "r")) != NULL); // 建立讀管道流
while(fgets(buf, sizeof(buf), in)) // 讀取 ps -ef 的結果
fputs(buf, out); // 轉寄到 grep init
pclose(out);
pclose(in);
return 0;
}
[bill@billstone Unix_study]$ make pipe3
cc pipe3.c -o pipe3
[bill@billstone Unix_study]$ ./pipe3
root 1 0 0 Apr15 ? 00:00:04 init
bill 1392 1353 0 Apr15 ? 00:00:00 /usr/bin/ssh-agent /etc/X11/xinit/Xclients
bill 14204 14203 0 21:33 pts/0 00:00:00 grep init
[bill@billstone Unix_study]$ ps -ef | grep init
root 1 0 0 Apr15 ? 00:00:04 init
bill 1392 1353 0 Apr15 ? 00:00:00 /usr/bin/ssh-agent /etc/X11/xinit/Xclients
bill 14207 1441 0 21:35 pts/0 00:00:00 grep init
[bill@billstone Unix_study]$
讀者可以從上面自行比較同 Shell 命令'ps -ef | grep init'的執行結果.
有名管道 FIFO
FIFO 可以在整個系統中使用.
在 Shell 中可以使用 mknod 或者 mkfifo 命令建立管道; 而在 C 程式中, 可以使用 mkfifo 函數建立有名管道.
要使用有名管道, 需要下面幾個步驟:
(1) 建立管道檔案
(2) 在某個進程中以唯寫方式開啟管道檔案, 並寫管道
(3) 在某個進程中以唯讀方式開啟管道檔案, 並讀管道
(4) 關閉管道檔案.
低級檔案編程庫和標準檔案編程庫都可以操作管道. 管道在執行讀寫操作之前, 兩端必須同時開啟, 否
則執行開啟管道某端操作的進程將一直阻塞到某個進程以相反方向開啟管道為止.
C代碼
- /*fifoserver.c:向FIFO中寫入資訊*/
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <errno.h>
- #include <fcntl.h>
- #define FIFO_SERVER "FIFO4"
- main(int argc,char** argv)
- {
- int fd=0;
- char w_buf[4096];
- int real_wnum;
- memset(w_buf,0,4096);
- if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL|0666)<0)&&(errno!=EEXIST))
- printf("cannot create fifoserver\n");
- /*此處存在著開啟依賴,即若沒有讀端開啟FIFO的話,寫端就阻塞在寫端*/
- fd=open(FIFO_SERVER,O_WRONLY);
- if(fd==-1)
- printf("open error; no reading process\n");
- printf("%d\n",fd);
-
- real_wnum=write(fd,w_buf,2048);
- if(real_wnum==-1)
- printf("write to fifo error; try later\n");
- else
- printf("real write num is %d\n",real_wnum);
- /*往FIFO寫入的資料都是原子的,如果沒有足夠的空間,則會等待,而不是一點一點的寫入。*/
- real_wnum=write(fd,w_buf,4096);
- if(real_wnum==-1)
- printf("write to fifo error; try later\n");
- else
- printf("real write num is %d\n",real_wnum);
- }
C代碼
- /*fifoclient.c:從FIFO中讀出資料*/
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <errno.h>
- #include <fcntl.h>
- #define FIFO_SERVER "FIFO4"
- main(int argc,char** argv)
- {
- char r_buf[4096];
- int fd;
- int r_size;
- int ret_size;
- r_size=atoi(argv[1]);
- memset(r_buf,0,sizeof(r_buf));
- fd=open(FIFO_SERVER,O_RDONLY);
- if(fd==-1)
- {
- printf("open %s for read error\n");
- exit(1);
- }
- printf("%d\n",fd);
-
- while(1)
- {
- ret_size=read(fd,r_buf,r_size);
- if(ret_size==-1)
- printf("no data avlaible\n");
- else
- printf("real read bytes %d\n",ret_size);
- sleep(1);
- }
- unlink(FIFO_SERVER);
- }
下面是一個簡單的執行個體.
首先是寫進程: 建立 FIFO 檔案, 再開啟寫連接埠, 然後讀取標準輸入並將輸入資訊發送到管道中, 當鍵
盤輸入'exit'或'quit'時程式退出.
[bill@billstone Unix_study]$ cat fifo1.c
#include <stdio.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/errno.h>
extern int errno;
int main()
{
FILE *fp;
char buf[255];
assert((mkfifo("myfifo", S_IFIFO|0666) > 0) || (errno == EEXIST));
while(1){
assert((fp = fopen("myfifo", "w")) != NULL);
printf("please input: ");
fgets(buf, sizeof(buf), stdin);
fputs(buf, fp);
fclose(fp);
if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0)
break;
}
return 0;
}
[bill@billstone Unix_study]$ make fifo1
cc fifo1.c -o fifo1
[bill@billstone Unix_study]$
然後是讀進程: 開啟管道的讀連接埠, 從管道中讀取資訊(以行為單位), 並將此資訊列印到螢幕上. 當讀
取到'exit'或者'quit'時程式退出.
[bill@billstone Unix_study]$ cat fifo2.c
#include <stdio.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
FILE *fp;
char buf[255];
while(1){
assert((fp = fopen("myfifo", "r")) != NULL);
fgets(buf, strlen(buf), fp);
printf("gets: [%s]", buf);
fclose(fp);
if(strncmp(buf, "quit", 4) == 0 || strncmp(buf, "exit", 4) == 0)
break;
}
return 0;
}
[bill@billstone Unix_study]$ make fifo2
cc fifo2.c -o fifo2
[bill@billstone Unix_study]$
在一個終端上執行 fifo1, 而在另一個終端上執行 fifo2.
我們先輸入'hello', 'world', 然後再輸入'exit'退出:
[bill@billstone Unix_study]$ ./fifo1
please input: hello
please input: world
please input: exit
[bill@billstone Unix_study]$
我們可以看到讀出結果如下:
[bill@billstone Unix_study]$ ./fifo2
gets: [hello
]gets: [world]gets: [exit][bill@billstone Unix_study]$
看到上面的輸出結果, 您可能認為是我寫錯了. 其實不是的, 讀出結果正是如此, 其實按照我們的本意,
正確的輸出結果應該是這樣的:
[bill@billstone Unix_study]$ ./fifo2
gets: [hello
]gets: [world
]gets: [exit