Linux进程间通信——管道
轉自:http://www.cnblogs.com/feisky/archive/2010/03/24/1693484.html
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);????????????????????????? //? 創建管道 可以把fildes看做記錄這個管道端口,譬如fildes[0]=3,fildes[1] =4
??????????????? 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!]?
也可能輸出:
[child] buf = [hello!]
[father] buf = [hello!]?
[child] buf = [world!]
[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代碼?
C代碼?
??? 下面是一個簡單的實例.?
??? 首先是寫進程:? 創建 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> #include <string.h> #include<fcntl.h> #include <stdlib.h> #include<unistd.h> #include <iostream> using namespace std; extern int errno; int main() { int fp; char buf[255]; assert((mkfifo("myfifo", O_CREAT|O_RDWR|0666) >= 0) || (errno == EEXIST)); while(1){fp = open("myfifo", O_WRONLY); //在其他進程打開myfifo的只讀之前,此進程堵塞到這句話printf("please input: "); fgets(buf, sizeof(buf), stdin); write(fp,buf,50); close(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 <stdlib.h> #include<unistd.h> int main() { int fp; char buf[255]; while(1){ fp = open("myfifo", O_RDONLY); read(fp,buf,50); printf("gets: [%s]", buf); close(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
總結
以上是生活随笔為你收集整理的Linux进程间通信——管道的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字符串相关处理kmp,前缀数,后缀树,后
- 下一篇: Linux进程间通讯之消息队列