概述html
管道通訊分爲無名管道、有名管道數組
管道通訊的本質緩存
不論是有名管道,仍是無名管道,它們的本質其實都是同樣的,它們都是內核所開闢的一段緩存空間。進程間經過管道通訊時,本質上就是經過共享操做這段緩存來實現,只不過操做這段緩存的方式,是以讀寫文件的形式來操做的。ide
無名管道函數
如何操做無名管道post
以讀寫文件的方式操做無名管道ui
1)有讀寫用的文件描述符(API部分講)
2)讀寫時會用write、read等文件IO函數。url
爲何叫無名管道spa
既然能夠經過「文件描述符」來操做管道,那麼它就是一個文件(管道文件),可是無名管道文件比較特殊,它沒有文件名,正是由於沒有文件名,全部被稱爲無名管道。指針
看下open的原型
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags);
返回值是文件描述符,或者-1(此時errno被設置)
無名管道的例子說明獲取文件描述符未必非得使用open函數
注意⚠️:
man手冊查詢pipe函數的時候,形參多是int *pipefd。可是這種寫法不太直觀,因此通常寫成int pipe(int pipefd[2])。那麼問題來了,int[2]類型和int*類型同樣嗎?
這要分狀況,對於函數參數,他倆沒區別
其餘狀況是有區別的,舉個數組指針的例子。
int ar[10]={0} √
int (*p)[10]=&ar √
int **p=&ar ✘
最後一句話是錯的,緣由就是int*和int[10]類型是不同的。
無名管道特色
無名管道只能用於親緣進程之間通訊。
因爲沒有文件名,所以進程沒辦法使用open打開管道文件,從而獲得文件描述符,因此只有一種辦法,那就是父進程先調用pipe建立出管道,並獲得讀寫管道的文件描述符。而後再fork出子進程,讓子進程經過繼承父進程打開的文件描述符,父子進程就能操做同一個管道,從而實現通訊。
API
PIPE原型
#include <unistd.h> int pipe(int pipefd[2]);
功能
建立一個用於親緣進程(父子進程)之間通訊的無名管道(緩存),並將管道與兩個讀寫文件描述符關聯起來。無名管道只能用於親緣進程之間通訊。
參數
緩存地址,緩存用於存放讀寫管道的文件描述符。從這個參數的樣子能夠看出,這個緩存就是一個擁有兩個元素的int型數組。
1)元素[0]:裏面放的是讀管道的讀文件描述符
2)元素[1]:裏面放的是寫管道的寫文件描述符。
特別須要注意的是,這裏的讀和寫文件描述符,是兩個不一樣的文件描述符。
從這裏你們也能夠看出,並非全部的文件描述符,都是經過open函數打開文件獲得的。這裏無名管道的讀、寫文件描述符,就是直接在建立管道時獲得的,與open沒有任何關係。並且這裏也根本沒辦法使用open函數,由於open函數須要文件路徑名,無名管道連文件名都沒有,因此說根本就沒辦法使用open來打開文件,返回文件描述符。
返回值
成功返回0,失敗則返回-1,而且errno被設置。
父子進程 藉助無名管道 單向通訊
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <strings.h> 5 #include <signal.h> 6 7 void print_err(char *estr) 8 { 9 perror(estr); 10 exit(-1); 11 } 12 13 int main(void) 14 { 15 int ret = 0; 16 int pipefd[2] = {0};//用於存放管道的讀寫文件描述符 17 18 ret = pipe(pipefd); 19 if(ret == -1) print_err("pipe fail"); 20 21 ret = fork(); 22 if(ret > 0) 23 { 24 close(pipefd[0]); 25 while(1) 26 { 27 write(pipefd[1], "hello", 5); 28 sleep(1); 29 } 30 } 31 else if(ret == 0) 32 { 33 close(pipefd[1]); 34 while(1) 35 { 36 char buf[30] = {0}; 37 bzero(buf, sizeof(buf)); 38 read(pipefd[0], buf, sizeof(buf)); 39 printf("child, recv data:%s\n", buf); 40 } 41 } 42 43 return 0; 44 }
父子進程 藉助無名管道 雙向通訊
雙向通訊使用一個管道行不行?
不行,因爲繼承關係,父子進程都有讀文件描述符,父進程發給子進程的消息,子進程不必定能收到,由於可能被父進程搶讀了。
解決辦法
使用2個管道,每一個管道負責一個方向的通訊
父進程建立2個管道,有4個文件描述符。子進程繼承父進程的文件描述符,父子進程加起來有8個文件描述符。
實現代碼
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <strings.h> 5 #include <signal.h> 6 7 void print_err(char *estr) 8 { 9 perror(estr); 10 exit(-1); 11 } 12 13 int main(void) 14 { 15 int ret = 0; 16 //[0]:讀文件描述符 17 //[1]:寫文件描述符 18 int pipefd1[2] = {0};//用於存放管道的讀寫文件描述符 19 int pipefd2[2] = {0};//用於存放管道的讀寫文件描述符 20 21 ret = pipe(pipefd1); 22 if(ret == -1) print_err("pipe fail"); 23 ret = pipe(pipefd2); 24 if(ret == -1) print_err("pipe fail"); 25 26 ret = fork(); 27 if(ret > 0) 28 { 29 close(pipefd1[0]); 30 close(pipefd2[1]); 31 char buf[30] = {0}; 32 while(1) 33 { 34 write(pipefd1[1], "hello", 5); 35 sleep(1); 36 37 bzero(buf, sizeof(buf)); 38 read(pipefd2[0], buf, sizeof(buf)); 39 printf("parent, recv data:%s\n", buf); 40 } 41 } 42 else if(ret == 0) 43 { 44 close(pipefd1[1]); 45 close(pipefd2[0]); 46 char buf[30] = {0}; 47 while(1) 48 { 49 sleep(1); 50 write(pipefd2[1], "world", 5); 51 52 bzero(buf, sizeof(buf)); 53 read(pipefd1[0], buf, sizeof(buf)); 54 printf("child, recv data:%s\n", buf); 55 } 56 } 57 58 return 0; 59 }
代碼裏,父子進程中write都寫在了read前面。write是非阻塞函數,父子進程中只須要保證至少一個write在前就不會使父子進程阻塞。 若是父子進程read都在write前,則父子進程都會因read而阻塞
有名管道
無名管道由於沒有文件名,被稱爲了無名管道,一樣的道理,有名管道之因此叫「有名管道」,是由於它有文件名。也就是說當咱們調用相應的API建立好「有名管道」後,會在相應的路徑下面看到一個叫某某名字的「有名管道文件」。
有名管道特色
①可以用於非親緣進程之間的通訊
由於有文件名,因此進程能夠直接調用open函數打開文件,從而獲得文件描述符,不須要像無名管道同樣,必須在經過繼承的方式才能獲取到文件描述符。因此任何兩個進程之間,若是想要經過「有名管道」來通訊的話,無論它們是親緣的仍是非親緣的,只要調用open函數打開同一個「有名管道」文件,而後對同一個「有名管道文件」進行讀寫操做,便可實現通訊。
②讀管道時,若是管道沒有數據的話,讀操做一樣會阻塞(休眠)
③當進程寫一個全部讀端都被關閉了的管道時,進程會被內核返回SIGPIPE信號
有名管道使用步驟
①進程調用mkfifo建立有名管道
②open打開有名管道
③read/write讀寫管道進行通訊
對於通訊的兩個進程來講,建立管道時,只須要一我的建立,另外一個直接使用便可。爲了保證管道必定被建立,最好是兩個進程都包含建立管道的代碼,誰先運行就誰先建立,後運行的發現管道已經建立好了,那就直接open打開使用。
API
mkfifo原型
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
功能
建立有名管道文件,建立好後即可使用open打開。
若是是建立普通文件的話,咱們可使用open的O_CREAT選項來建立,好比:open("./file", O_RDWR|O_CREAT, 0664);
可是對於「有名管道」這種特殊文件,這裏只能使用mkfifo函數來建立。
參數
1)pathname:被建立管道文件的文件路徑名。
2)mode:指定被建立時原始權限,通常爲0664(110110100),必須包含讀寫權限。
參考:umask、setuid、setgid、sticky bit、chmod、chown 中umask
Linux——文件 中umask
返回值
成功返回0,失敗則返回-1,而且errno被設置。
有名管道單項通訊
單獨啓動2個進程通訊
p1.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <strings.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define FIFONAME1 "./fifo1" #define FIFONAME2 "./fifo2" void print_err(char *estr) { perror(estr); exit(-1); } int creat_open_fifo(char *fifoname, int open_mode) { int ret = -1; int fd = -1; ret = mkfifo(fifoname, 0664); //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤) if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail"); fd = open(fifoname, open_mode); if(fd == -1) print_err("open fail"); return fd; } void signal_fun(int signo) { //unlink(); remove(FIFONAME1); exit(-1); } int main(void) { char buf[100] = {0}; int ret = -1; int fd1 = -1; signal(SIGINT, signal_fun); fd1 = creat_open_fifo(FIFONAME1, O_WRONLY); while(1) { bzero(buf, sizeof(buf)); scanf("%s", buf); write(fd1, buf, sizeof(buf)); } return 0; }
p2.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <strings.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define FIFONAME1 "./fifo1" #define FIFONAME2 "./fifo2" void print_err(char *estr) { perror(estr); exit(-1); } int creat_open_fifo(char *fifoname, int open_mode) { int ret = -1; int fd = -1; ret = mkfifo(fifoname, 0664); //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤) if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail"); fd = open(fifoname, open_mode); if(fd == -1) print_err("open fail"); return fd; } void signal_fun(int signo) { //unlink(); remove(FIFONAME1); exit(-1); } int main(void) { char buf[100] = {0}; int ret = -1; int fd1 = -1; signal(SIGINT, signal_fun); fd1 = creat_open_fifo(FIFONAME1, O_RDONLY); while(1) { bzero(buf, sizeof(buf)); read(fd1,buf,sizeof(buf)); printf("%s\n", buf); } return 0; }
這裏須要注意一點把signal註冊信號處理函數放到creat_open_fifo函數以前的緣由是:先讓系統知道怎麼處理Ctrl+C硬件中斷。要否則creat_open_fifo在前的話,阻塞在mkfifo上,系統還不知道怎麼處理Ctrl+C這個硬件中斷信號。也就無法刪除有名管道文件。這裏其實OS應該是處理了,OS的處理就是默認處理方式。即乾死當前進程,可是沒有刪除管道文件。
若是creat_open_fifo在signal以前,會出現進程被幹死了,可是有名管道文件沒有被刪除的狀況。
有名管道雙向通訊
使用一個有名管道是沒法實現雙向通訊的,道理同無名管道,即存在搶讀問題。
單獨啓動2個進程
p1.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <strings.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define FIFONAME1 "./fifo1" #define FIFONAME2 "./fifo2" void print_err(char *estr) { perror(estr); exit(-1); } int creat_open_fifo(char *fifoname, int open_mode) { int ret = -1; int fd = -1; ret = mkfifo(fifoname, 0664); //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤) if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail"); fd = open(fifoname, open_mode); if(fd == -1) print_err("open fail"); return fd; } void signal_fun(int signo) { //unlink(); remove(FIFONAME1); remove(FIFONAME2); exit(-1); } int main(void) { char buf[100] = {0}; int ret = -1; int fd1 = -1; int fd2 = -1; fd1 = creat_open_fifo(FIFONAME1, O_WRONLY); fd2 = creat_open_fifo(FIFONAME2, O_RDONLY); while(1) { bzero(buf, sizeof(buf)); scanf("%s", buf); write(fd1, buf, sizeof(buf)); read(fd2, buf, sizeof(buf)); } return 0; }
p2.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <strings.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define FIFONAME1 "./fifo1" #define FIFONAME2 "./fifo2" void print_err(char *estr) { perror(estr); exit(-1); } int creat_open_fifo(char *fifoname, int open_mode) { int ret = -1; int fd = -1; ret = mkfifo(fifoname, 0664); //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤) if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail"); fd = open(fifoname, open_mode); if(fd == -1) print_err("open fail"); return fd; } void signal_fun(int signo) { //unlink(); remove(FIFONAME1); remove(FIFONAME2); exit(-1); } int main(void) { char buf[100] = {0}; int ret = -1; int fd1 = -1; int fd2 = -1; fd1 = creat_open_fifo(FIFONAME1, O_RDONLY); fd2 = creat_open_fifo(FIFONAME2, O_WRONLY); while(1) { bzero(buf, sizeof(buf)); scanf("%s", buf); read(fd1, buf, sizeof(buf)); printf("recv:%s\n", buf); write(fd2, buf, sizeof(buf)); } return 0; }
這2個代碼體驗及其糟糕,p2;裏面read在write以前,read會阻塞p2。這也是沒辦法避免的,read和write放一塊就會出問題。解決之道
代碼
p1.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <strings.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define FIFONAME1 "./fifo1" #define FIFONAME2 "./fifo2" void print_err(char *estr) { perror(estr); exit(-1); } int creat_open_fifo(char *fifoname, int open_mode) { int ret = -1; int fd = -1; ret = mkfifo(fifoname, 0664); //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤) if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail"); fd = open(fifoname, open_mode); if(fd == -1) print_err("open fail"); return fd; } void signal_fun(int signo) { //unlink(); remove(FIFONAME1); remove(FIFONAME2); exit(-1); } int main(void) { char buf[100] = {0}; int ret = -1; int fd1 = -1; int fd2 = -1; fd1 = creat_open_fifo(FIFONAME1, O_WRONLY); fd2 = creat_open_fifo(FIFONAME2, O_RDONLY); ret = fork(); if(ret > 0) { signal(SIGINT, signal_fun); while(1) { bzero(buf, sizeof(buf)); scanf("%s", buf); write(fd1, buf, sizeof(buf)); } } else if(ret == 0) { while(1) { bzero(buf, sizeof(buf)); read(fd2, buf, sizeof(buf)); printf("%s\n", buf); } } return 0; }
p2.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <strings.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #define FIFONAME1 "./fifo1" #define FIFONAME2 "./fifo2" void print_err(char *estr) { perror(estr); exit(-1); } int creat_open_fifo(char *fifoname, int open_mode) { int ret = -1; int fd = -1; ret = mkfifo(fifoname, 0664); //若是mkfifo函數出錯了,可是這個錯誤是EEXIST,不報這個錯誤(忽略錯誤) if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail"); fd = open(fifoname, open_mode); if(fd == -1) print_err("open fail"); return fd; } void signal_fun(int signo) { //unlink(); remove(FIFONAME1); remove(FIFONAME2); exit(-1); } int main(void) { char buf[100] = {0}; int ret = -1; int fd1 = -1; int fd2 = -1; fd1 = creat_open_fifo(FIFONAME1, O_RDONLY); fd2 = creat_open_fifo(FIFONAME2, O_WRONLY); ret = fork(); if(ret > 0) { signal(SIGINT, signal_fun); while(1) { bzero(buf, sizeof(buf)); read(fd1, buf, sizeof(buf)); printf("recv:%s\n", buf); } } else if(ret == 0) { while(1) { bzero(buf, sizeof(buf)); scanf("%s", buf); write(fd2, buf, sizeof(buf)); } } return 0; }
注意:父子進程的buf是不同的,這得益於子進程繼承父進程。
處理Ctrl+C硬件中斷,只有父進程作了掃尾工做(即刪除管道文件),而後父進程正常終止(調用exit(-1))。子進程採用默認處理方式,即被OS直接乾死。
網狀通訊
每個節點想象成一個進程
不論是無名管道、仍是有名管道,實現網狀通訊都很困難