什么是進程通信?
進程通信是實現進程間傳遞數據信息的機制。要實現數據信息傳遞就要進程間共享資源——內存空間。那么是哪塊內存空間呢?進程間是相互獨立的,一個進程不可能訪問其他進程的內存空間,那么這塊空間只能由操作系統提供。進程通信的方式有多種,管道就是一種。
管道是一種最簡單的通信機制,管道分為匿名管道和命名管道。匿名管道通常用于父子進程之間。命名管道可實現任意兩個進程通信。
匿名管道
原理
父進程打開管道文件流(讀寫兩個流),分配描述符到文件描述符表。兩個新的文件描述符分別指向管道的讀端和寫端。父進程創建子進程時,子進程的文件描述符表和父進程指向相同。父子進程指向的文件流都是共享的。
父子進程通信時,如果子進程寫,父進程讀,子進程關閉pipe_r,父進程關閉pipe_w;如果子進程讀,父進程寫,子進程關閉pipe_w,父進程關閉pipe_r。
使用樣例
pipefd是一個輸出型參數,pipefd[0] 表示的是讀端的文件描述符,pipefd[1]表示的是寫端文件描述符
創建管道->創建子進程->子進程關閉讀端,寫;父進程關閉寫端,讀->父進程等待子進程?
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;
void Write(int fd)
{char buffer[1024] = {0};pid_t id = getpid();int n = 0;string s = "I am child";while(1){snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),id,n);n++;write(fd,buffer,sizeof(buffer));sleep(1);if(n == 5) break;}
}void Read(int fd)
{char buffer[1024] = {0};while(1){ssize_t s = read(fd,buffer,sizeof(buffer));if(s > 0){cout << buffer << endl;}else if(s == 0){cout << "read ending" << endl;break;}else{cout << "read fail" << endl;}}
}int main()
{int pipefd[2] = {0};pipe(pipefd);pid_t id = fork();if(id < 0) return -1;if(id == 0){//childclose(pipefd[0]);//關閉讀端Write(pipefd[1]);//close(pipefd[1]);exit(0);}//fatherclose(pipefd[1]);//關閉寫端Read(pipefd[0]);pid_t ret = waitpid(id,nullptr,0);if(ret==id) cout << "wait success" << endl;else cout << "wait fail" << endl;//close(pipefd[0]);return 0;
}
命名管道
命名管道原理和匿名管道一樣,區別就是命名管道會創建一個管道文件,兩個進程分別對文件讀寫就可以了
使用樣例
創建管道文件
?
銷毀管道文件
?
?
//communicate.hpp 創建銷毀管道進行封裝
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>class Init
{
public:Init(){int n = mkfifo("./myfifo", 0777);if (n == -1){perror("fifo:");exit(-1);}}~Init(){int n = unlink("./myfifo");if(n == -1){perror("unlink:");exit(-1);}}
};//sever.cpp 服務器負責維護管道,讀客戶端數據
#include "communicate.hpp"
using namespace std;
int main()
{// 創建管道Init init;// 打開管道int fd = open("./myfifo", O_RDONLY);if (fd == -1){cout << strerror(errno) << endl;return -1;}// 開始通信while (1){char buffer[1024] = {0};ssize_t ret = read(fd, buffer, sizeof(buffer));if (ret > 0){cout << "sever get inf:" << buffer << endl;}else if (ret == 0){cout << "client quit, sever quit too" << endl;break;}elsebreak;}// 結束通信close(fd);
}//client.cpp 客戶端負責向服務器寫數據
#include "communicate.hpp"using namespace std;int main()
{//打開管道int fd = open("./myfifo", O_WRONLY);if(fd < 0){cout << strerror(errno) << endl;return -1;}//進行通信while(1){string ord;cout << "Input:";getline(cin, ord);write(fd, ord.c_str(), ord.size());}close(fd);return 0;
}
管道特征
- 只有具有血緣關系的進程可以匿名管道通信
因為只有血緣關系的進程的可以指向同一個匿名管道,達成資源(內存空間)共享,這是通信的前提
- 管道只能單向通信
- 通信的進程會進行協同,同步與互斥,為了保證管道文件數據的安全
例如上面的樣例:寫端每寫一次都會等待1s,寫端沒有寫完時,讀端會等待進行協同
- 管道是面向字節流的
管道以字節為單位傳輸數據,而不是以消息或記錄為單位。這意味著數據在傳輸過程中沒有特定的結構或邊界,發送方可以連續寫入任意數量的字節,接收方則按照字節順序讀取數據。
- 管道是基于文件的,文件的生命周期是隨進程的
管道會隨著進程結束而關閉,例如基礎IO流并不需要用戶手動關閉。在上面的例子中,子進程是寫端,沒有close(pipdf[1])也不會有錯誤,同樣的父進程是讀端,沒有close(pipdf[0])不會有錯誤,因為進程結束這個管道流會自動關閉。
管道的4種情況
- 讀寫端正常,管道為空,讀端就要阻塞
- 讀寫端正常,管道為滿,寫端就要阻塞
- 寫端關閉,讀端正常,讀端讀到0,表明讀到了管道文件的結尾
- 讀端關閉,寫端正常,寫端會被異常終止