1. 程式人生 > >程序間通訊-管道通訊

程序間通訊-管道通訊

兩個程序的通訊,每個程序各自有不同的地址空間,每個地址空間的資料資訊是獨立的,任何一個程序的全域性變數在另一個程序中都看不到。例如,父程序中有一個全域性變數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;
}

由上面程式碼可以證實,子程序關閉寫,父程序讀完管道的資料,會一直阻塞等待直到有資料寫入,才讀取資料並返回;

程序間通訊並沒有結束,這只是個開始;