進程間通信介紹:
進程間通信的概念:
進程間通信簡稱IPC(Interprocess communication),進程間通信就是在不同進程之間傳播或交換信息。
進程間通信的目的:
數據傳輸: 一個進程需要將它的數據發送給另一個進程。
資源共享: 多個進程之間共享同樣的資源。
通知事件: 一個進程需要向另一個或一組進程發送消息,通知它(它們)發生了某種事件,比如進程終止時需要通知其父進程。
進程控制: 有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態改變。
進程間通信的本質:
進程間通信的本質就是讓不同的進程看到同一份資源。
進程間通信的發展:
管道
System V進程間通信
POSIX進程間通信
進程間通信的分類:?
管道
- 匿名管道
- 命名管道
System V IPC
- System V 消息隊列
- System V 共享內存
- System V 信號量
POSIX IPC
- 消息隊列
- 共享內存
- 信號量
- 互斥量
- 條件變量
- 讀寫鎖
管道:
認識管道:
由于進程間具有獨立性,想要實現進程間通信非常困難,想要實現進程間通信就必須借助第三方資源,讓兩個需要通信的進程都可訪問這個第三方資源,早期管道就是這樣的第三方資源來實現進程間通信。
管道是Unix中最古老的進程間通信的形式。
我們把從一個進程連接到另一個進程的一個數據流稱為一個“管道“
演示:
先來介紹兩個命令:
1.who
who指令可以用來顯示當前云服務器登錄的用戶數,一行顯示一個用戶。
如圖所示現在有2個用戶。?
?2.wc
wc指令可以查指定文件的計算文件的Byte數、字數、或是列數,若不指定文件名稱、或是所給予的文件名為"-",則wc指令會從標準輸入設備讀取數據
wc加上-l指令,計算指定文件的行數。
將上述兩個命令通過管道連接,就可以更準確地查出當前云服務器的登錄用戶:
who進程將數據寫入管道,wc從管道中讀取到數據,-l指令計算數據的行數,從而得出當前云服務器的登錄數。?
匿名管道:
匿名管道性質:?
匿名管道僅支持父子間進程通信。
當我們創建一個進程,在linux系統中它被如下圖進行管理:
我們再通過這個進程創建一個子進程,子進程繼承父進程的代碼和數據:
?沒錯,此時我們的父子進程能看到同一份資源,我們可以模擬一下通信,父進程往緩沖區寫入,子進程往緩沖區讀取,早期的工程師發現了這種現象,并且認為這是一種很好的進程間通信的方法,就在這種方法的基礎上進行了一下改動,創造了管道。
注意:
我們在進程間通信時,是沒必要對磁盤中的文件進行操作的,所以我們的管道沒必要與磁盤中的文件產生關聯。
文件級緩沖區是由操作系統來維護的,所以當父進程對其寫入時,是不會發生寫時拷貝的。
pipe函數:
int pipe(int pipefd[2]);
?pipe函數的參數是一個輸出型參數,數組pipefd中的兩個元素分別用來返回管道讀端和寫端的文件描述符:
數組元素 | 含義 |
pipefd[0] | 管道讀端文件描述符 |
pipefd[1] | 管道寫端文件描述符 |
?匿名管道的使用:
注意下圖中的fd均指pipefd。
1.父進程用pipe函數創建管道。
2.父進程通過fork函數創建子進程。
3.假設我們讓子進程寫,父進程讀,所以我們要關閉不用的文件描述符,父進程關閉寫端,子進程關閉讀端。
?我們再站在文件描述符的角度深入理解:
匿名管道測試:?
現在用下述代碼測試匿名管道,父進程進行一直讀取,子進程進行一直寫入:
#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>const int size = 1024;//子進程進行寫入
void SubProcessWrite(int wrd)
{std::string message = "father,i am your childen process! ";while(true){sleep(1);std::cout<<"childen begin write........."<<std::endl;static int cent = 0;pid_t id = getpid();//拼接消息std::string info = message;info += "my pid is ";info += std::to_string(id);info += ", cent: ";info += std::to_string(cent);//寫入write(wrd,info.c_str(),info.size());cent++;}
}
//父進程進行讀取
void FatherProcessReader(int rfd)
{char inbuffer[size];while(true){std::cout<<"father begin read,message:"<<std::endl;ssize_t n = read(rfd,inbuffer,sizeof(inbuffer) - 1);//讀取消息if(n > 0){inbuffer[n] = 0;//語言限制,在字符串最后加\0std::cout<<inbuffer<<std::endl;//打印消息}}
}
int main()
{int pipefd[2];int n = pipe(pipefd);//管道創建成功,返回0if(n != 0)//管道創建失敗{std::cerr<<"errno "<<errno<<"cerrstring: "<<strerror(errno)<<std::endl;}std::cout<<"讀端->pipefd[0]"<<pipefd[0]<<"寫端->pipefd[1]"<<pipefd[1]<<std::endl;sleep(1);pid_t id = fork();//創建子進程if(id == 0){//子進程進行寫入std::cout<<"子進程關閉不需要的fd了,準備寫消息了"<<std::endl;close(pipefd[0]);//關閉讀端SubProcessWrite(pipefd[1]);//子進程寫close(pipefd[1]);//任務完成關閉寫端exit(0);}//父進程std::cout<<"父進程關閉不需要的fd了,準備讀消息了"<<std::endl;close(pipefd[1]);//關閉寫端FatherProcessReader(pipefd[0]);//父進程讀close(pipefd[0]);//任務完成關閉讀端pid_t rid = waitpid(id,NULL,0);//父進程等待子進程,并回收return 0;
}
來看看運行結果:
?管道的4種情況:
1.寫端進程不寫,讀端進程一直讀,那么此時會因為管道里面沒有數據可讀,對應的讀端進程會被掛起,直到管道里面有數據后,讀端進程才會被喚醒。
2.讀端進程不讀,寫端進程一直寫,那么當管道被寫滿后,對應的寫端進程會被掛起,直到管道當中的數據被讀端進程讀取后,寫端進程才會被喚醒。
3.寫端進程將數據寫完后將寫端關閉,那么讀端進程將管道當中的數據讀完后,就會繼續執行該進程之后的代碼邏輯,而不會被掛起。
4.讀端進程將讀端關閉,而寫端進程還在一直向管道寫入數據,那么操作系統會將寫端進程殺掉。
管道的大小:
管道是有容量的,當管道被寫滿了,寫端將會阻塞或者失敗,查詢管道大小的方法有如下:
ulimit -a指令,查看當前資源限制。
從上圖可以算出管道的大小為512*8 = 4096字節。?
命名管道:
剛才介紹的匿名管道,只可用于父子進程間通信,如果兩個毫不相干的進程要實現通信該怎么辦呢?接下來就需要介紹一下命名管道了。
mkfifo函數:
mkfifo函數用于創建一個命名管道。
mkfifo的第一個參數表示要創建的命令管道文件,如果不帶路徑默認再當前文件夾下。
mkfifo的第二個參數表示管道的文件權限。
例如文件權限設置為0666,則理論創建的管道權限為
?但實際文件權限還會受文件默認掩碼umask影響,默認的umask是0002,我們實際的文件權限會先0666&(~umask),所以實際管道權限為0664:
mkfifo的返回值:?
管道創建成功返回0。
創建管道失敗返回-1,錯誤碼被設置。?
用命名管道實現serve&client通信
serve管理管道負責創建,銷毀和讀取消息,client負責往管道中寫入消息:
serve.cc:
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>const std::string comm_name = "./myfifo";int main()
{//服務端創建命名管道int res = mkfifo(comm_name.c_str(),0666);if(res != 0)//創建失敗{perror("mkfifo");}//serve端打開管道int fd = open(comm_name.c_str(),O_RDONLY);if(fd < 0){std::cout<<"open file"<<errno<<std::endl;}//serve接受消息并打印char buffer[1024];while(true){std::cout<<"server begin read:"<<std::endl;ssize_t n = read(fd,buffer,sizeof(buffer)-1);if(n > 0)//讀取成功{buffer[n] = 0;std::cout<<buffer<<std::endl;}else if( n == 0){std::cout<<"read done"<<std::endl;break;}else{std::cout<<"read fail"<<errno<<std::endl;break;}}int n = unlink(comm_name.c_str());if(n != 0){perror("unlink");}return 0;
}
client.cc:
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>const std::string comm_name = "./myfifo";int main()
{//client以寫打開管道int fd = open(comm_name.c_str(),O_WRONLY);if(fd < 0){std::cout<<"open fail"<<errno<<std::endl;}int cent = 100;sleep(5);while(cent--){sleep(1);std::cout<<"client begin write"<<std::endl;//消息拼接std::string message = "i sent a message ,cnet: ";message += std::to_string(cent);//寫入消息ssize_t n = write(fd,message.c_str(),sizeof(message));}return 0;
}
來看看運行結果: