【UNIX網絡編程】進程間通訊之管道

      管道是最先的Unix進程間通訊形式,它存在於所有的Unix實現中。關於管道,有例如如下幾點需要知道:shell

一、它是半雙工的,即數據僅僅能在一個方向上流動。雖然在某些Unix實現中管道可以是全雙工的。但需要對系統進行某些設置。在Linux系統中,它是半雙工的。編程

二、它沒有名字。所以僅僅能在具備公共祖先的進程之間使用。數組

通常常使用在父子進程間。雖然這一點隨着「有名管道FIFO」的增長獲得改正了。但應該把它們看做是兩種不一樣的進程間通訊方式。網絡

三、它由pipe函數建立,read和write函數訪問,提供單向數據流。除了pipe外。在C函數庫裏,還有另一個函數popen完畢一個新管道的建立、一個新進程的啓動、關閉管道的不使用端、運行shell命令、等待命令終止等一系列操做。函數


       管道使用演示樣例:post

       在shell命令中,咱們經常常使用到"cmd1 | cmd2"這一類的命令,cmd1和cmd2之間就是經過管道來進行鏈接的。spa

shell負責兩個命令的標準的輸入好標準輸出:.net

cmd1的標準輸入來自終端鍵盤。設計

cmd1的標準輸出傳遞給cmd2,做爲它的標準輸入。指針

cmd2的標準輸出鏈接到終端屏幕。


知識點1:pipe函數

#include <unistd.h>
int pipe(int fd[2]);			//返回值:若成功則返回0,若出錯則返回-1

       進程調用pipe函數建立一個管道。pipe函數的參數是一個由兩個整數類型的文件描寫敘述符組成的數組的指針。該函數在數組中成功填入兩個新的文件描寫敘述符後返回0,假設失敗則返回-1並設置errno來代表失敗的緣由。

errno的值有下面三種可能:

       EMFILE:進程使用的文件描寫敘述符過多。

       ENFILE:系統的文件表已滿。

       EFAULT:文件描寫敘述符無效。

兩個新填入的文件描寫敘述符:fd[0]爲讀而打開。fd[1]爲寫而打開。fd[1]的輸出是fd[0]的輸入。也就是說利用write函數寫到fd[1]的所有數據都可以從fd[0]讀出來。示比例如如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


#define MAXLINE 2048


int
main(int argc, char **argv)
{
	int fd[2];
	int data;
	char buff[MAXLINE];
	const char some_data[] = "123";


	memset(buff, '\0', sizeof(buff));


	if(pipe(fd) == -1){
		exit(EXIT_FAILURE);
	}else{
		data = write(fd[1], some_data, strlen(some_data));	
		data = read(fd[0], buff, data);
		printf("read %d bytes : %s\n", data, buff);
		exit(EXIT_SUCCESS);
	}
}

程序執行結果:

book@book-desktop:/work/tmp/unp$ ./a.out 
read 3 bytes : 123

上面的樣例是在同一進程中使用管道的樣例,但實際使用過程當中很是少這樣使用。通常都是在兩個不一樣進程(通常是父子進程)間進程通訊的。兩個進程間使用管道的演示樣例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MAXLINE 2048

int
main(int argc, char **argv)
{
	int fd[2];
	int data;
	pid_t child_pid;
	char buff[MAXLINE];
	const char some_data[] = "123";

	memset(buff, '\0', sizeof(buff));

	if(pipe(fd) == 0){
		child_pid = fork();
		if(child_pid == -1){
			fprintf(stderr, "fork error.");	
			exit(EXIT_FAILURE);
		}else if(child_pid == 0){
			close(fd[1]);  //關閉子進程的寫入端
			data = read(fd[0], buff, MAXLINE);//從子進程的讀取段讀取數據	
			printf("read %d bytes: %s\n", data, buff);
			exit(EXIT_SUCCESS);
		}else{
			close(fd[0]);//關閉父進程的讀取段
			data = write(fd[1], some_data, strlen(some_data));//從父進程的寫入端寫入數據
			printf("wrote %d bytes\n", data);
		}	
	}	
	exit(EXIT_SUCCESS);
}

程序執行結果是:

book@book-desktop:/work/tmp/unp$ ./a.out 
wrote 3 bytes
read 3 bytes: 123

book@book-desktop:/work/tmp/unp$ ./a.out 
wrote 3 bytes
book@book-desktop:/work/tmp/unp$ read 3 bytes: 123
這是因爲。假設父進程先於子進程結束。就會看到shell提示符了。由上面的演示樣例可以看出,要經過管道完畢父子進程間的通訊,先由父進程建立一個管道後調用fork派生一個自身的副本,接着,父進程關閉這個管道的讀出端。子進程關閉同一管道的寫入端。這就在父子進程間提供了一個單向數據流。

       當管道的一端被關閉後,下列兩條規則起做用:

一、當讀一個寫端已被關閉的管道時,在所有數據都被讀取後,read返回0,以指示達到了文件結束處。

二、假設寫一個讀端已被關閉的管道,則產生信號SIGPIPE。

假設忽略該信號或者捕捉該信號並從其處理程序返回,則write返回-1,errno設置爲EPIPE。


上面的兩個樣例都是半雙工的即單向的,僅僅提供一個方向的數據流。

當需要一個雙向數據流時,必須建立兩個管道。每個方向一個。實際過程例如如下:

一、建立管道1和管道2

二、fork

三、父進程關閉管道1的讀出端、關閉管道2的寫入端

三、子進程關閉管道1的寫入端、關閉管道2的讀出端。

建立兩個管道後就可以完畢一個簡單的client-server樣例。相關演示樣例請點此連接


知識點2:popen和pclose函數

       popen和pclose函數不是Unix實現的。它們時標準I/O庫提供的,它們實現的操做時:建立一個管道,調用fork產生一個子進程,關閉管道的不使用端。執行一個shell以執行命令,而後等待命令終止。

#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);//返回值:若成功則返回文件指針,若出錯則返回NULL
int     pclose(FILE *fp);//返回值:cmdstring的終止狀態,若出錯則返回-1
函數popen先運行fork,而後調用exec以運行cmdstring,並且返回一個標準I/O文件指針。

假設type是「r」,則文件指針鏈接到cmdstring的標準輸出。返回的文件指針時可讀的。
假設type是「w」,則文件指針鏈接到cmdstring的標準輸入,返回的文件指針時可寫的。
圖示popen例如如下:


假設type是"r",被調用程序的輸出就可以由調用程序使用,調用程序利用popen函數返回的文件指針,就可以經過常常使用的stdio庫函數(如fread)來讀取被調用程序的輸出;假設type是"w",調用程序就可以用fwrite調用向被調用程序發送數據,而被調用程序可以在本身的標準輸入上讀取這些數據。


type是r的演示樣例程序例如如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int
main(int argc, char **argv)
{
	FILE *read_fp;
	char buff[BUFSIZ + 1];
	int  chars_read;
	memset(buff, '\0', sizeof(buff));
	read_fp = popen("uname -a", "r");//打開鏈接到uname命令的管道。把管道設置爲可讀方式並讓read_fp指向該命令輸出
	if(read_fp != NULL){
		chars_read = fread(buff, sizeof(char), BUFSIZ, read_fp);	
		if(chars_read > 0)
				printf("Output was : -\n%s\n", buff);
		pclose(read_fp);
		exit(EXIT_SUCCESS);
	}
	exit(EXIT_SUCCESS);
}
執行結果:
Output was : -
Linux book-desktop 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux
type是w的演示樣例程序例如如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int
main(int argc, char **argv)
{
	FILE *fp;
	char buffer[BUFSIZ + 1];

	sprintf(buffer, "Once upon a time there was ...\n");
	fp = popen("od -c", "w");
	if(fp != NULL){
		fwrite(buffer, sizeof(char), strlen(buffer), fp);	
		pclose(fp);
		exit(EXIT_SUCCESS);
	}
	exit(EXIT_SUCCESS);
}
程序結果例如如下:
0000000   O   n   c   e       u   p   o   n       a       t   i   m   e
0000020       t   h   e   r   e       w   a   s       .   .   .  \n
0000037
也可以上上面的pipe函數同樣實現client-server程序。演示樣例代碼 請點此連接

參考:
一、上面解說的圖示部分:http://blog.csdn.net/to_be_it_1/article/details/28138063
二、《Linux程序設計》 Neil Matthew&&Richard Stones
三、《UNIX環境高級編程》Richard Stevenson
四、《UNIX網絡編程 卷2》 Richard Stevenson
相關文章
相關標籤/搜索