文章目錄
- 進程間通信
- 1.進程間通信的介紹
- 1.1目的和發展
- 2.進程間通信分類
- 3.管道
- 3.1匿名管道
- 3.1.1匿名管道的原理(文件角度)
- 3.1.2匿名管道的原理(內核角度)
- 3.1.3管道讀寫規則
- 3.1.4管道特點
- 3.2命名管道
- 3.2.1創建命名管道
- 3.2.2命名管道的打開規則
- 4.命名管道實現server&client通信
進程間通信
1.進程間通信的介紹
??進程間通信(IPC,Interprocess communication)是一組編程接口,讓程序員能夠協調不同的進程,使之能在一個操作系統里同時運行,并相互傳遞、交換信息。這使得一個程序能夠在同一時間里處理許多用戶的要求。因為即使只有一個用戶發出要求,也可能導致一個操作系統中多個進程的產生。進程間通信可以發生在同一臺機器上的不同進程間,也可以發生在不同機器上的進程間。
??
1.1目的和發展
??目的:
??(1)數據傳輸:一個進程需要將它的數據發送給另一個進程。
??(2)資源共享:多個進程之間共享同樣的資源。
??(3)通知事件:一個進程需要向另一個或一組進程發送消息,通知它(它們)發生了某種事件(如進程終止時要通知父進程)。
??(4)進程控制:有些進程希望完全控制另一個進程的執行(如Debug進程),此時控制進程希望能夠攔截另一個進程的所有陷入和異常,并能夠及時知道它的狀態改變。
??
??發展:
??管道 -> System V進程間通信 -> POSIX進程間通信。
??
2.進程間通信分類
??管道:匿名管道、命名管道
??System V IPC:System V 消息隊列、System V 共享內存、System V 信號量
??POSIX IPC:消息隊列、共享內存、信號量、互斥量、條件變量、讀寫鎖
??
3.管道
??管道是Unix中最古老的進程間通信的形式。我們把從一個進程連接到另一個進程的一個數據流稱為一個“管道”。
??
3.1匿名管道
??匿名管道的原理:
??匿名管道的原理是使用pipe函數創建管道,并在父進程中得到兩個文件描述符,一個用于從管道讀數據,另一個用于向管道寫數據。 子進程在創建時會自動繼承這兩個文件描述符,從而可以實現父子進程間的數據交換。
#include <unistd.h>//功能:創建一無名管道//原型
int pipe(int fd[2]);//參數
//fd:文件描述符數組,其中fd[0]表示讀端, fd[1]表示寫端
//返回值:成功返回0,失敗返回錯誤代碼
//例子:從鍵盤讀取數據,寫入管道,讀取管道,寫到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{int fds[2];char buf[100];int len;if ( pipe(fds) == -1 )perror("make pipe"),exit(1);// read from stdinwhile ( fgets(buf, 100, stdin) ) {len = strlen(buf);// write into pipeif ( write(fds[1], buf, len) != len ){perror("write to pipe");break;}memset(buf, 0x00, sizeof(buf));// read from pipeif ( (len=read(fds[0], buf, 100)) == -1 ) {perror("read from pipe");break;}// write to stdoutif ( write(1, buf, len) != len ) {perror("write to stdout");break;}}
}
??
3.1.1匿名管道的原理(文件角度)
??匿名管道的原理從文件角度來說,是利用內存中共享的一段緩沖區,以文件的方式對緩沖區實現。但因為該文件只存在于內存中,沒有唯一命名,所以稱為匿名管道。
??具體來說,父進程創建管道文件描述符,然后通過fork創建子進程。子進程繼承了父進程的文件描述符,這樣父子進程就可以通過這個文件描述符進行通信。 由于管道是半雙工方式,數據傳輸的方向是單向的,所以如果需要進行雙向通信,需要創建兩個管道。
??
3.1.2匿名管道的原理(內核角度)
??從內核角度來說,匿名管道的原理是利用內核緩沖區作為偽文件,這個緩沖區由讀端和寫端兩部分組成,對應兩個文件描述符。數據從寫端流入,從讀端流出。
??匿名管道的內部實現方式是隊列,而且是環形隊列。這種隊列的特性是先進先出,即一端入隊,另一端出隊,即只能從一端寫入,另一端讀出。緩沖區的大小默認是4k字節,但會根據實際情況做適當調整。
??由于用隊列實現,數據只能讀取一次,不能重復讀取。另外,匿名管道是半雙工方式,數據傳輸的方向是單向的。此外,匿名管道只適用于有血緣關系的進程,如父子進程、兄弟進程等。
這也符合了Linux的一切皆文件的思想
??
??父進程tast_struct中有指向file_struct的指針 *file,其中files_struct是一個struct file *fd_array[],這個array的數組中指向了各種的文件。同時創建子進程,子進程是父進程的一份拷貝,所以此時父進程指向的文件,子進程也同樣會指向該文件,進程間通信的前提完成:讓不同的文件看到同一份資源。
??
??
??接著,我們可以進行不同進程間的通信了,如果我們想要先讓子進程寫入,父進程讀取。則我們可以在父進程和子進程所指的同一個文件中,進行不同的操作:讓子進程在文件緩沖區中寫入數據,讓父進程在同樣的文件緩沖區中讀取數據。即可完成通過使用管道的通信操作(所以管道也是文件)。
??
3.1.3管道讀寫規則
??(1)當沒有數據可讀時
??O_NONBLOCK disable
:read調用阻塞,即進程暫停執行,一直等到有數據來到為止。
??O_NONBLOCK enable
:read調用返回-1,errno值為EAGAIN。
??(2)當管道滿的時候
??O_NONBLOCK disable
: write調用阻塞,直到有進程讀走數據。
??O_NONBLOCK enable
:調用返回-1,errno值為EAGAIN。
??(3)如果所有管道寫端對應的文件描述符被關閉, 則read返回0。
??(4)如果所有管道讀端對應的文件描述符被關閉, 則write操作會產生信號SIGPIPE,進而可能導致write進程退出。
??(5)當要寫入的數據量不大于PIPE_BUF時,linux將保證寫入的原子性。
??(6)當要寫入的數據量大于PIPE_BUF時,linux將不再保證寫入的原子性。
??
3.1.4管道特點
??(1)只能用于具有共同祖先的進程(具有親緣關系的進程)之間進行通信; 通常,一個管道由一個進程創建,然后該進程調用fork,此后父、子進程之間就可應用該管道。
??(2)管道提供流式服務。
??(3)一般而言,進程退出,管道釋放,所以管道的生命周期隨進程。
??(4)一般而言,內核會對管道操作進行同步與互斥。
??(5)管道是半雙工的,數據只能向一個方向流動; 需要雙方通信時,需要建立起兩個管道。
??
3.2命名管道
??命名管道的介紹:
??Linux命名管道是一種特殊的文件類型,它允許不具有親緣關系的進程之間進行通信。命名管道存在于文件系統中,但同時具有管道的優點,可以用于進程間通信。進程通過操作命名管道文件進行數據交換。
??命名管道的創建可以使用命令行工具(如mkfifo命令)或者在編程語言中的調用系統調用接口(如mkfifo函數)來創建。 創建好命名管道之后,可以使用open()和read/write()函數來讀取和寫入數據。
??在數據傳輸方面,命名管道的數據傳輸不會寫入磁盤,而是在內存中進行傳遞。命名管道允許多個進程通過使用相同的管道名稱進行通信,而不僅僅是兩個進程之間的通信。與普通管道一樣,命名管道中的數據也是臨時存儲在內存中的。
??
3.2.1創建命名管道
??命名管道可以從命令行上創建:
$ mkfifo filename
??
??命名管道也可以從程序里創建,相關函數有:
int mkfifo(const char *filename,mode_t mode);
??
??創建命名管道:
int main(int argc, char *argv[])
{mkfifo("p2", 0644);return 0;
}
??
??匿名管道與命名管道的區別:
??(1)匿名管道由pipe函數創建并打開。
??(2)命名管道由mkfifo函數創建,打開用open。
??(3)FIFO(命名管道)與pipe(匿名管道)之間唯一的區別在它們創建與打開的方式不同,一但這些工作完成之后,它們具有相同的語義。
??
3.2.2命名管道的打開規則
??(1)如果當前打開操作是為讀而打開FIFO時
??O_NONBLOCK disable
:阻塞直到有相應進程為寫而打開該FIFO。
??O_NONBLOCK enable
:立刻返回成功。
??(2)如果當前打開操作是為寫而打開FIFO時
??O_NONBLOCK disable
:阻塞直到有相應進程為讀而打開該FIFO。
??O_NONBLOCK enable
:立刻返回失敗,錯誤碼為ENXIO。
??
4.命名管道實現server&client通信
測試實現:
??
comm.hpp
#pragma once#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<cerrno>
#include<string>
#include <unistd.h>
#include <fcntl.h>#define FIFO_FILE "./myfifo"
#define MODE 0664enum{FIFO_CREATE_ERR=1,FIFO_DELETE_ERR=2,FIFO_OPEN_ERR=3
};
??
Makefile
.PHONY:all
all:server client
server:server.ccg++ -o $@ $^ -std=c++11
client:client.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f client server myfifo
??
server.cc
//#include<iostream>
#include"comm.hpp"using namespace std;//管理管道文件
int main()
{//創建信道int n=mkfifo(FIFO_FILE,MODE);if(n==-1){perror("mkfifo");exit(FIFO_CREATE_ERR);}//sleep(5);//打開信道int fd=open(FIFO_FILE,O_RDONLY);if(fd<0){perror("open");exit(FIFO_OPEN_ERR);}cout<<"server open file done"<<endl;//開始信道while(true){char buffer[1024]={0};int x=read(fd,buffer,sizeof(buffer));if(x>0){buffer[x]=0;cout<<"client say#"<<buffer<<endl;}else if(x==0){cout<<"clinet quit,me too!\n"<<endl;break;} else break;}close(fd);int m=unlink(FIFO_FILE);if(m==-1){perror("unlink");exit(FIFO_DELETE_ERR);}return 0;
}
??
client.cc
#include<iostream>
#include"comm.hpp"using namespace std;int main()
{int fd=open(FIFO_FILE,O_WRONLY);if(fd<0){perror("open");exit(FIFO_OPEN_ERR);}string line;while(true){cout<<"Please Enter@";//cin>>line;getline(cin,line);write(fd,line.c_str(),line.size());}close(fd);return 0;
}