程序間通訊-管道通訊
兩個程序的通訊,每個程序各自有不同的地址空間,每個地址空間的資料資訊是獨立的,任何一個程序的全域性變數在另一個程序中都看不到。例如,父程序中有一個全域性變數a = 0;在子程序中改變a的值不會影響父程序中a值的結果,因為子程序所有的資料資訊都拷貝(寫時拷貝)自父程序,兩個程序有各自不同的地址空間。
#include<stdio.h> #include<stdlib.h> #include<unistd.h> int a = 0; int main() { pid_t id = fork(); if(id == 0){ //child a = 10; printf("Is child time,a:%d\n",a); }else{ //father printf("Is father time,a:%d\n",a); sleep(1); } return 0; }
由上面程式碼和執行截圖可清晰的看出,父子程序之間沒有共享資料,所以兩個程序是不能直接通訊的。
要通訊就必須要進過核心,在核心中開闢一塊緩衝區(第三方資源),讓不同的程序看到一份公共的資源。具體實現是程序1把資料從使用者空間拷到核心緩衝區,程序2再從核心緩衝區把資料讀走,這就實現了程序之間的通訊。
程序通訊--管道
先看管道的建立,由pipe函式建立:
#include <unistd.h>
int pipe(int pipefd[2]);
返回值:
呼叫成功返回0,呼叫失敗返回-1;
作用:
呼叫pipe函式時在核心中開闢一塊緩衝區(又稱管道)用於通訊,有一個讀端和一個寫端,然後通過輸出型引數pipefd[2]傳出給程序兩個檔案描述符,pipefd[0]指向的是管道的讀端,pipefd[1]指向的是管道的寫端。向管道中寫的話就直接呼叫write(pipefd[1],msg,strlen(msg));從管道中讀直接調read(pipefd[0],buf,sizeof(buf) - 1);所以管道看來就像一個開啟的檔案。兩個程序分別向管道(資源共享區)讀寫,就完成了程序的通訊。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int a = 0;
int main()
{
int fd[2] = {0,0};
if(pipe(fd) < 0){
perror("pipe error\n");
return 1;
}
printf("fd[0]:%d,fd[1]:%d\n",fd[0],fd[1]);
return 0;
}
這個程式可以看出,讀端的檔案描述符fd[0] = 3;寫端的檔案描述符fd[1] = 4;
一個程序建立的管道:
因為子程序的資料資源都是來自與父程序,所以子程序中也有和父程序一樣的檔案描述符表,兩個程序指向的是同一個管道,如下圖:
實現父子程序通訊的程式碼:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2] = {0,0};
if(pipe(fd) < 0){
perror("pipe error\n");
return 1;
}
pid_t id = fork();
if(id == 0){
//child --->write
close(fd[0]);
const char *msg = "I am child,hello father\n";
while(1){
write(fd[1],msg,strlen(msg));
// printf("child\n");
sleep(1);
}
}else{
//father--->read
char buf[1024];
close(fd[1]);
while(1){
ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
// printf("father\n");
if(s > 0){
buf[s] = 0;
printf("%s",buf);
}
}
}
return 0;
}
在程式碼中,"I am child,hello father"這句話是子程序寫在管道中的,父程序從管道中讀出來了管道中的內容。這就完成了父子程序之間的通訊。
從上面程式碼中,看到子程序在寫之前關閉了讀的檔案描述符fd[0],父程序在讀之前關閉了寫檔案描述符fd[1];這是為了預防發生錯誤,這就要涉及到管道的使用限制了,兩個程序通過一個管道只能實現單向通訊,父程序讀,子程序寫。要完成父程序寫子程序讀的話就需要重新開闢一個管道了。若在父程序讀的情況下,沒有關閉父程序的寫fd[1],在父程序中去寫,就會導致某些未知的錯誤(在父程序中不去寫就不會出錯,關閉是為了防止錯誤的出現);關閉fd[1],在父程序想寫進管道時就不會成功。管道是一種半雙工方式,即對於程序來說,要麼只能讀管道,要麼只能寫管道。不允許對管道又讀又寫。
這種管道是匿名管道,它的特點是:
1.單向通訊;
2.管道依賴於檔案系統,其生存週期結束是程序退出時結束,稱為隨程序;
3.資料讀寫時是按照資料流進行的;
4.只適用於有血緣關係的程序(父子程序,兄弟程序)通訊;
5.自帶同步機制,若寫時滿,讀的時候要匹配寫的速度;
使用管道需要注意以下4中情況:(讀情況都是父程序,寫情況都是子程序)
1.子程序寫的慢,父程序讀的快;父程序從管道中讀資料,管道中資料全部被讀完後,父程序再次去read會阻塞,等待子程序寫,直到管道中有資料可讀了才讀取資料並返回,例子就是上面實現的程式碼。
2.子程序寫的快,父程序都得慢;導致管道中資料全被寫滿,再次write會被阻塞,直到管道中有空位置了才寫入資料並返回。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2] = {0,0};
if(pipe(fd) < 0){
perror("pipe error\n");
return 1;
}
pid_t id = fork();
if(id == 0){
//child --->write
close(fd[0]);
int count = 0;
const char *msg = "I am child,hello father\n";
while(1){
write(fd[1],msg,strlen(msg));
// printf("child\n");
// sleep(1);
printf("count:%d\n",++count);
}
}else{
//father--->read
char buf[1024];
close(fd[1]);
while(1){
ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
// printf("father\n");
if(s > 0){
buf[s] = 0;
printf("%s",buf);
}
sleep(5);
}
}
return 0;
}
寫到管道的最大容量,等待讀取之後在進行寫入;
再次write,管道中有空餘,則繼續寫入,直到滿,下次寫入阻塞,直到管道中有空位置了才寫入資料並返回。
3.父程序中讀fd[0]關閉,子程序寫;子程序向管道中一寫,作業系統會終止該程序,程序異常終止。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2] = {0,0};
if(pipe(fd) < 0){
perror("pipe error\n");
return 1;
}
pid_t id = fork();
if(id == 0){
//child --->write
close(fd[0]);
int count = 0;
const char *msg = "I am child,hello father\n";
while(1){
write(fd[1],msg,strlen(msg));
// printf("child\n");
sleep(1);
//printf("count:%d\n",++count);
}
}else{
//father--->read
char buf[1024];
close(fd[1]);
int count = 0;
while(1){
ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
// printf("father\n");
printf("count:%d\n",count);
if(s > 0){
buf[s] = 0;
printf("%s",buf);
}
if(count++ > 5)
{
close(fd[0]);
break;
}
}
int status = 0;
pid_t ret = waitpid(id,&status,0);
printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff);
}
return 0;
}
4.子程序關閉寫,父程序讀;子程序沒有向管道中寫資料,父程序從管道中讀資料,管道中所以資料都被讀取後,在次read會阻塞,直到管道中有資料可讀了才讀取資料並返回。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
int fd[2] = {0,0};
if(pipe(fd) < 0){
perror("pipe error\n");
return 1;
}
pid_t id = fork();
if(id == 0){
//child --->write
close(fd[0]);
int count = 0;
const char *msg = "I am child,hello father\n";
while(1){
write(fd[1],msg,strlen(msg));
if(count++ > 5)
{
close(fd[1]);
break;
}
sleep(1);
printf("count:%d\n",count);
}
}else{
//father--->read
char buf[1024];
close(fd[1]);
int count = 0;
while(1){
ssize_t s = read(fd[0],buf,sizeof(buf) - 1);
if(s > 0){
buf[s] = 0;
printf("%s",buf);
}
}
int status = 0;
pid_t ret = waitpid(id,&status,0);
printf("ret:%d,sig:%d,exitCode:%d\n",ret,status&0xff,(status>>8)&0xff);
}
return 0;
}
由上面程式碼可以證實,子程序關閉寫,父程序讀完管道的資料,會一直阻塞等待直到有資料寫入,才讀取資料並返回;
程序間通訊並沒有結束,這只是個開始;