1. 程式人生 > >程序通訊 (匿名管道)

程序通訊 (匿名管道)

程式:是有限指令的集合
程序:將程式執行一次的過程
注:分配資源的單位

程序間通訊(IPC):system IPC V由於程序間彼此隔離,故有下列方法來使用程序間通訊。

1、管道:匿名、命令管道
    Shell命令:  管道  command1 | command2
    系統API:  
        int pipe(int fd[2]);
           返回值:成功與否的狀態
           fd檔案的描述符
           pipe2
           注: 管道是一種最基本的IPC機制,作用於有血緣關係的程序之間,完成資料傳遞。呼叫pipe系統函式即可建立一個管道。有如下特質:            
            1. 其本質是一個偽檔案(實為核心緩衝區)
            2.由兩個檔案描述符引用,一個表示讀端,一個表示寫端。
            3. 規定資料從管道的寫端流入管道,從讀端流出。
            管道的原理: 管道實為核心使用環形佇列機制,藉助核心緩衝區(4k)實現。
            管道的侷限性:
            ① 資料自己讀不能自己寫。
            ② 資料一旦被讀走,便不在管道中存在,不可反覆讀取。
            ③ 由於管道採用半雙工通訊方式。因此,資料只能在一個方向上流動。
            ④ 只能在有公共祖先的程序間使用管道。
            常見的通訊方式有,單工通訊、半雙工通訊、全雙工通訊。    

2、管道的讀寫行為,使用管道需要注意以下4種特殊情況(假設都是阻塞I/O操作,沒有設定O_NONBLOCK標誌):
        1. 如果所有指向管道寫端的檔案描述符都關閉了(管道寫端引用計數為0),
           而仍然有程序從管道的讀端讀資料,那麼管道中剩餘的資料都被讀取後
           再次read會返回0,就像讀到檔案末尾一樣。
        2. 如果有指向管道

寫端的檔案描述符關閉(管道寫端引用計數大於0),
           而持有管道寫端的程序也沒有向管道中寫資料,這時有程序從管道讀端讀資料
           那麼管道中剩餘的資料都被讀取後,再次read會阻塞直到管道中有資料可讀
           了才讀取資料並返回。
        3. 如果所有指向管道讀端的檔案描述符都關閉了
(管道讀端引用計數為0),
           這時有程序向管道的寫端write,那麼該程序會收到訊號SIGPIPE,通常會
           導致程序異常終止。當然也可以對SIGPIPE訊號實施捕捉,不終止程序。
           具體方法訊號章節詳細介紹。
        4. 如果有指向管道讀端的檔案描述符沒關閉(管道讀端引用計數大於0),而
           持有管道讀端的程序也沒有從管道中讀資料,這時有程序向管道寫端寫資料
           ,那麼在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數
           據並返回。

總結:

        ① 讀管道:

        1. 管道中有資料,read返回實際讀到的位元組數。
        2. 管道中無資料:
        (1) 管道寫端被全部關閉,read返回0 (好像讀到檔案結尾) 
        (2) 寫端沒有全部被關閉,read阻塞等待(不久的將來可能有資料遞達,此時會讓出cpu)

       ② 寫管道:

        1. 管道讀端全部被關閉, 程序異常終止(也可使用捕捉SIGPIPE訊號,使程序不終止)
        2. 管道讀端沒有全部關閉:
        (1) 管道已滿,write阻塞。
        (2) 管道未滿,write將資料寫入,並返回實際寫入的位元組數。

 

work: 統計ls -l有多少行
         ls -l   | wc -l 
         command1 | command2 | command3 
注:【指令1】正確輸出1,作為【指令2】的輸入描述符0 然後【指令2】的輸出作為 
       【指令3】的輸入 ,【指令3】輸出就會直接顯示在螢幕上面了。
         通過管道之後【指令1】和【指令2】的正確輸出不顯示在螢幕上面 

實現如上功能 : 

#include<iostream>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
using namespace std;
int main()
{
	int fd[2];//fd[0]讀  fd[1]寫
	if(pipe(fd)==-1)	
	{
		perror("pipe");
		return -1;
	}
	int pid=fork();
	if(pid<0)
	{
		perror("pipe");
		exit(-1);
	}
	else if(0==pid)//子程序
	{
		dup2(fd[0],0);//重定向---讀
		close(fd[0]);
		close(fd[1]);	
		execlp("wc","wc","-l",NULL);		
		fprintf(stderr,"error execute wc\n");
		exit(0);
	}//父程序
	dup2(fd[1],1);//關閉描述符1,將寫入端fd[1]複製給1
	close(fd[0]);
	close(fd[1]);
	execlp("ls","ls","-l","/",NULL);//讀螢幕上讀取---螢幕已經重定向為管道
	fprintf(stderr,"error execute ls\n");
	wait(NULL);
	exit(0);
}

 

   管道間的讀寫通訊實現 .

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<limits.h>
using namespace std;
/*
#define PIPE_BUF 4096
char buf[PIPE_BUF];
*/
int main()
{
	//在建立子程序之前:建立管道
	int fd[2]={-1,-1};
	//IPC1:匿名管道
	pipe(fd);//返回兩個檔案描述符fd[0]只讀 fd[1]只寫

	//檢視匿名管道系統定義長度
	cout<<PIPE_BUF<<endl;

	int pid=fork();
	if(pid>0)
	{
		char buf[100];
		cin>>buf;
		//關閉讀取
		close(fd[0]);
		//重定向
		dup2(fd[1],1);
		//寫入
		write(fd[1],buf,strlen(buf));
		cout<<"父程序寫入完成"<<endl;
		//關閉寫入段
		close(fd[1]);
		//等待子程序結束
		wait(NULL);
	}
	else if(pid==0)
	{
		char buf1;
		//關閉寫入
		close(fd[1]);
		//重定向
		dup2(fd[0],0);
		while((read(fd[0],&buf1,1))>0)
			printf("%c",buf1);
		cout<<endl;
		
		close(fd[0]);
	}
}

 

   shell 命令 實現管道通訊的功能 , 實現解析字串 ,  ls -l  |  grep data.data .

#include<iostream>
#include<string.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>

#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
using namespace std;
int main()
{
	char title[]="[****[email protected] ~]";
	char cmd[255]="";
	int pid;
	char* matter[100]={NULL};	//100個地址

	int fd[2]={-1,-1};
	if(pipe(fd)==-1)
	{
		fprintf(stderr,"pipe fail\n");
		exit(-1);
	}
	int i=0;
	//父程序
	while(1)
	{
		cout<<title;	//輸出標題
		gets(cmd);	//輸入
		pid=fork();
		if(pid>0)
		{
			wait(NULL);
		}
		else if(0==pid)
		{
			//char a[]=" ";
			//解析字串
			matter[i]=strtok(cmd," ");
			while((matter[++i]=strtok(NULL," "))!=NULL)
			{
				//cout<<matter[i]<<endl;
				if('|'==*(matter[i]))
				{
					matter[i]=NULL;
				}
			}
			dup2(fd[1],1);
			close(fd[1]);//兩個指向管道寫
			close(fd[0]);//關閉讀
			//替換子程序資訊
			execvp(matter[0],matter);
			fprintf(stderr,"execute ls fail\n");
			exit(-1);
		}
		//解析字串
		//cout<<"2***"<<endl;
		matter[i]=strtok(cmd," ");
		while((matter[++i]=strtok(NULL," "))!=NULL)
		{
		}
		dup2(fd[0],0);
		close(fd[0]);//讀端兩個,所以關閉一次 0
		close(fd[1]);//關閉寫端
		execvp(matter[3],matter+3);
		//execlp("wc","wc","-l",NULL);
		fprintf(stderr,"execute ls fail\n");
		exit(-1);
	}
	return 0;
}

 

   製作一個簡單的 shell 終端 , 父程序來要求子程序去執行shell 命令,當子程序結束,父程序繼承下一個shell .

    使用到的 API:
   1、獲取使用者的資訊:
       struct getpwuid()
   2、獲取電腦主機電腦的別名
       gethostname
   3、每一個程序都有一個工作目錄    
       getcwd
   4、獲取當前程序所在使用者的UID
       getuid

#include<iostream>
#include<sys/types.h>
#include<pwd.h>
#include<string.h>
#include<sys/wait.h>
#include<stdio.h>

#include<fcntl.h>
#include<string.h>

#include<stdlib.h>
using namespace std;

/*顯示出使用者的標題名*/
bool title(void)
{
	char name[100]="";
	char cwd[256]="";
	//獲取使用者名稱
	struct passwd* pd=getpwuid( getuid() );
	if(NULL==pd)
		return false;
	//顯示出 [[email protected]
	cout<<"["<<pd->pw_name<<"@";
	//顯示出主機名
	gethostname(name,99);
	cout<<name<<" ";
	//顯示工作目錄
	getcwd(cwd,255);
	//判斷位置
	if(!strcmp(cwd,pd->pw_dir))
		cout<<"~]$ ";
	else//絕對路徑
	{
		//cout<<cwd<<"]$"<<endl;
		//查詢位置:最後一個/
		char* pos=strrchr(cwd,'/');
		if(NULL!=pos &&*(pos+1)=='\0')
			cout<<pos<<"]$ ";
		else if(NULL!=pos)
			cout<<pos+1<<"]$ ";
	}
	return true;
}

//解析字串:
void trans(char cmd[],char* argv[])
{
	int i=0;
	//先記錄首地址
	argv[i]=cmd;
	while((*cmd)!='\0')
	{
		//遇到空格,說一個單詞結束了
		if(' '==*cmd)
		{
			*cmd='\0';
			argv[++i]=++cmd;
		}
		else
		{
			++cmd;
		}
	}
	argv[i+1]=NULL;
}

/*輸入命令,讓子程序去執行shell,父程序解釋命令*/
void shell()
{
	char cmd[255]="";
	int pid=-1;
	int ilen=0;
	char* argv[64]={NULL};
	//char* pch=NULL;
	int i=0;
	
	while(1)
	{
		title();
		//讓系統完成輸出
		cout<<endl;
		//輸出:

		//輸入STDIN_FILENO
		ilen=read(STDIN_FILENO,cmd,sizeof(cmd));
		if(ilen>0)
			//新增結尾  因為後面會  ls -l\n 有個換行,需要去掉.
			cmd[ilen-1]='\0';
		//解析字串:execv函式
		trans(cmd,argv);

		pid=fork();
		if(pid>0)
		{
			wait(NULL);
		}
		else if(0==pid)
		{
			//cout<<argv[0]<<argv[1]<<endl;
			//argv[i]=strtok(cmd," ");
			while(argv[i]!=NULL)
			{
				if(!strcmp(argv[i],">"))
				{
					//cout<<i<<endl;
					//cout<<argv[i]<<endl;
					close(1);
					int fd=open("data",O_CREAT|O_TRUNC|O_WRONLY,0666);
					argv[i]=NULL;
					execvp(argv[0],argv);
				}
				else if(!strcmp(argv[i],">>"))
				{
					close(1);
					int fd=open("data",O_CREAT|O_WRONLY|O_APPEND,0644);
					argv[i]=NULL;
					execvp(argv[0],argv);
				}
				else if(!strcmp(argv[i],"|"))
				{
					int j=0;

					int fd[2]={-1,-1};
					if(pipe(fd)==-1)
					{
						fprintf(stderr,"pipe fail\n");
						exit(-1);
					}
					//cout<<fd[1]<<endl;    // 4
					//cout<<argv[i+1]<<endl;  // |   i+1=wc
					//cout<<i<<endl;	   // 2
					while(1)
					{
					//子程序
					int pid2=fork();
					if(pid2>0)
					{
						wait(NULL);
					}
					else if(0==pid2)
					{
						while(argv[++j]!=NULL)
						{
							//cout<<j<<endl;	// 1 2 3 4
							//cout<<argv[j]<<endl;	// -l | wc -l
							if(!strcmp(argv[j],"|"))
							{
							//cout<<argv[j]<<endl;	// -l
							argv[j]=NULL;
							}
						}
						dup2(fd[1],1);
						close(fd[1]);//兩個指向管道寫
						close(fd[0]);//關閉讀
						//替換子程序資訊
						execvp(argv[0],argv);
						fprintf(stderr,"execute ls fail\n");
						exit(-1);
					}
					//argv[i+3]=NULL;
					while(argv[++j]!=NULL)
					//cout<<argv[3]<<endl;	// wc
					{
						//cout<<argv[j]<<endl;
					}
					//strcpy(argv[j],"1");
					dup2(fd[0],0);
					close(fd[0]);//讀端兩個,所以關閉一次 0
					close(fd[1]);//關閉寫端
					execvp(argv[3],argv+3);
					//execlp("wc","wc","-l",NULL);
					fprintf(stderr,"execute ls fail\n");
					exit(-1);
					}
				}
				i++;
			}
			if(-1==execvp(argv[0],argv))
				perror("execut execvp fail");
		}
	}
}

//製作一個簡單的shell
//父程序來要求子程序去執行shell 命令,當子程序結束,父程序繼承下一個shell
int main()
{
	shell();
}