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

程序通訊———管道

 程序通訊之管道(pipe,fifo)

  首先我們先來了解一下程序通訊(IPC Interprocess Communication)的一般目的:大概有 資料傳輸,共享資源,通知事件,資源共享,和程序控制。

  但是我們知道,對於每個程序而言其所看到的記憶體資源,就是他所獨自佔有的,所以程序間通訊會比較麻煩,原理:讓不同的程序能夠看到一份公共的資源。所以呢資源必須是存放在核心上的,在核心開闢一塊緩衝區,程序1把資料從使用者空間拷貝到核心緩衝區,程序2再從核心緩衝區中將資料讀走,核心提供的這種機制稱之為:程序間通訊。

一般我們採用程序間通訊的方式有:

               1.管道(pipe)和有名管道(FIFO)

               2.訊號(signal)

               3.訊息佇列

               4.共享記憶體

               5.訊號量

               6.套接字(socket)

我們先從最簡單的通訊方式來說起

匿名管道:pipe

一:管道的介紹

管道是一種最基本的程序間通訊機制。管道由pipe函式來建立:

#include<unistd.h>

int pipe(int pipefd[2]);

呼叫pipe函式,會在核心中開闢出一塊緩衝區來進行程序間通訊,這塊緩衝區稱為管道,他有一個讀端和一個寫端

pipe函式接受一個引數,是包含兩個整數的陣列,如果呼叫成功,會通過pipefd[2]傳出給使用者程式兩個檔案描述符,分別為pipefd[0]指向管道的讀端,pipefd[1]指向管道的寫端,那麼此時這個管道對於使用者程式而言就是一個檔案,可以通過read(pipefd[0]);或者write(pipefd[1]);進行操作。

pipe函式呼叫成功返回0,否則返回 -1。

二:管道建立

》父程序建立管道,得到兩個檔案描述符指向管道的兩端

》利用fork函式創建出子程序,則子程序也得到兩個檔案描述符指向同一管道

》父程序關閉讀端(pipefd[0]),子程序關閉寫端pipefd[1],此時父程序可以往管道中進行寫操作,子程序可以從管道中讀取,從而實現了管道的程序間通訊。

程式碼示例:

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

int main()
{
    int fd[2];
    if( pipe(fd) < 0)
    {
        printf("pipe is error\n");
        exit(0);
    }
    
    pid_t pid = fork();
    if(pid < 0){

        printf("pid is error\n");
        exit(0);
    }
    if(pid == 0)//child
    {
        close(fd[0]);//關閉讀端

        int i = 0;
        char* mesg = NULL;
        while(i<10)
        {
            mesg = "my name is kebi";
            write(_pipe[1],mesg,strlen(mesg)+1);
            sleep(1);
            ++i;
        }
    }
    else//parent
    {
        close(fd[1]);

        int j = 0;
        char _mesg[12];
        while(j<10)
        {
            memset(_mesg,'\0',sizeof(_mesg));
            read(fd[0],_mesg,sizeof(_mesg));
            printf("%s\n",_mesg);
            j++;
        }
    }
return 0;
}
        

pipe的特點:

1.只能單向通訊(半雙工)

2.只能有血緣關係的程序之間通訊(父子程序)

3.依賴於檔案系統

4.生命週期隨程序

5.面向位元組流服務

6.管道內部提供了同步機制

說明:

  管道的讀寫端通過開啟檔案描述符來傳遞,因此要通訊的兩個程序必須從他們的公共祖先那裡繼承管道的檔案描述符。上面的例子是父程序把檔案描述符傳給子程序之後父子程序之間通訊。也可以父程序fork()兩次,把檔案描述符傳給兩個子程序,然後兩個子程序之間通訊。

  總之需要通過fork()傳遞檔案描述符使兩個程序都能訪問同一個管道,他們才能通訊。

四種特殊情況:

1.如果所有指向管道寫端的檔案描述符都關閉了,而仍然有程序從管道的讀端讀資料,那麼管道中剩餘的資料都被讀取後,再次read會返回0,就像讀到檔案末尾一樣。

2.如果指向管道寫端的檔案描述符沒關閉,而持有管道寫端的程序沒有向管道中寫資料,這時如果有程序從管道讀端讀取資料,那麼管道中剩餘的資料被讀取後,再次read會阻塞,直至管道中有資料可以讀取了才讀取資料並返回。

3.如果所有指向管道讀端的檔案描述符都關閉了,這時有程序指向管道的寫端write,那麼該程序會收到SIGPIPE訊號,通常會導致程序異常終止。

4.如果有指向管道讀端的檔案描述符沒關閉,而持有管道讀端的程序也沒有從管道讀端讀取資料,這時有程序向管道寫端寫資料,那麼管道再被寫滿再write會阻塞,知道管道中有空的位置了才寫入資料並返回。

有名管道FIFO

有名管道介紹:

  在管道中,只有具有血緣關係的程序才能進行通訊,對於後來的命名管道,就解決了這個問題,FIFO不同於匿名管道之處在於它提供了一個路徑名與之關聯,以FIFO的檔案形式儲存在檔案系統中。有名管道是一個裝置檔案,因此即使程序與建立FIFO的程序不存在親緣關係,只要可以訪問該路徑,就能通過FIFO相互通訊。值得注意的是,FIFO(first input first output)總是按照先進先出的原則工作,第一個被寫入的資料首先從管道中讀取。

有名管道的建立:

  命令建立:mkfifo + filename

  函式建立:

   #include<sys/types.h>

   #include<sys/stat.h>

   int mkfifo(const char* path ,mode_t mode);

   int mknod(const char* path,mode_t mod,dev_t dev);

函式mknod引數中path為建立的命名管道的全路徑名:mode為建立的命名管道的模,指明其存取許可權;dev為裝置值,該值取決於檔案穿件的種類,它只在建立裝置檔案時才會用到。這兩個函式呼叫成功返回0,失敗返回-1.

//mknod函式建立
umask(0);

if(mknod("/tmp/fifo",S_IFIFO | 0666) == -1)
{
        printf("mknod is error\n");
        exit(0);
}


//mkfif0函式建立
umask(0);
if(mkfifo("/tmp/fifo",S_IFIFO | 0666) == -1)
{
        printf("mkfifo is  error:\n");    
        exit(0);
}

"S_IFIFO   | 0666"指明建立一個命名管道且存取許可權為0666,即建立者,與建立者同組的使用者,其他使用者對該命名管道的訪問許可權都是可讀可寫的(注意umask對生成的管道檔案的影響)。

有名管道建立後就可以使用了,只是使用命名管道時,必須先呼叫open()將其開啟。

需要注意:呼叫open()開啟有名管道的程序可能會阻塞。但如果同時用讀寫方式(O_RDWR)開啟,則一定不會導致阻塞

如果以只讀方式( O_RDONLY)開啟,則呼叫open()函式的程序將會被阻塞直到有寫方開啟管道;同樣以寫方式( O_WRONLY)開啟 也會阻塞直到有讀方式開啟管道。

程式碼示例:

//fifo1.c

#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdlib.h>
#define STRLEN 128

int main()
{
        int fd = open("./FIFO",O_WRONLY);
        assert(fd != NULL);
        printf("FIFO open is success:\n");
        while(1)
        {
            char p[STRLEN] = {0};
            printf("please input the data:\n");
            scanf("%s",p);
            write(fd,p,strlen(p));
            if(strncmp(p,"end",3) == 0)
                break;
        }
        close(fd);
        exit(0);
}


//fifo2.c
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdlib.h>
#define STRLEN 128
int main()
{
    in fd = open("FIFO",O_RDONLY);
    assert(fd != NULL);
    printf("open is sucess:\n");
    while(1)
    {
        char buff[STRLEN] = {0};    
        int n = read(fd,buff,127);
        if(n == 0||(n == 3&&strncmp(buff,"end",3)==0))
        {
            printf("n is error:\n");
            break;
        }
        
        int i = 0;
        while(i<strlen(buff))
        {    
                printf("%d ",buff[i++]);
        }
        printf("\n");
    }
    close(fd);
    exit(0);
}
            

如何測試獲取匿名管道的核心緩衝區的大小?

  思路:我們通過現象四來實現這個檢測操作。當我們不斷的向管道總寫入資料時,當管道滿了之後會產生write阻塞。

#include"apue.h"


int main()
{
	int fd[2];
	if (pipe(fd) == -1)
	{
		printf("pipe is error:\n");
		return -1;
	}
	pid_t pid = fork();
	if (pid == 0)//child
	{
		int i = 0;
		close(fd[0]);//關閉讀端
		char* child = "i am child!";
		while (++i)
		{
			write(fd[1], child, strlen(child) + 1);
			printf("pipe capacity: %d\n", i*(strlen(child) + 1));
		}
		close(fd[1]);
	}
	else if (pid > 0)//parent
	{
		close(fd[1]);
		waitpid(id, NULL, 0);
	}
	else
	{
		printf("fork() is error:\n");
		return -1;
	}
	return 0;
}

大小為65536,即為64k大小。