進程間通訊之管道(pipe、fifo)

咱們先來講說進程間通訊(IPC)的通常目的,大概有數據傳輸、共享數據、通知事件、資源共享和進程控制等。可是咱們知道,對於每個進程來講這個進程看到屬於它的一塊內存資源,這塊資源是它所獨佔的,因此進程之間的通訊就會比較麻煩,原理就是須要讓不一樣的進程間可以看到一份公共的資源。因此交換數據必須經過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間 拷到內核緩衝區,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通訊。通常咱們採用的進程間通訊方式有數組

  1. 管道(pipe)和有名管道(FIFO)
  2. 信號(signal)
  3. 消息隊列
  4. 共享內存
  5. 信號量
  6. 套接字(socket)

咱們先來從最簡單的通訊方式來講起;socket

匿名管道 pipe函數

---------------------------------------------------------------------------------------------------------------------------------spa

管道的建立3d

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

調用pipe函數,會在內核中開闢出一塊緩衝區用來進行進程間通訊,這塊緩衝區稱爲管道,它有一個讀端和一個寫端。blog

pipe函數接受一個參數,是包含兩個整數的數組,若是調用成功,會經過pipefd[2]傳出給用戶程序兩個文件描述符,須要注意pipefd [0]指向管道的讀端, pipefd [1]指向管道的寫端,那麼此時這個管道對於用戶程序就是一個文件,能夠經過read(pipefd [0]);或者write(pipefd [1])進行操做。pipe函數調用成功返回0,不然返回-1..繼承

那麼再來看看經過管道進行通訊的步驟:生命週期

》父進程建立管道,獲得兩個文件描述符指向管道的兩端隊列

》利用fork函數建立出子進程,則子進程也獲得兩個文件描述符指向同一管道

》父進程關閉讀端(pipe[0]),子進程關閉寫端pipe[1],則此時父進程能夠往管道中進行寫操做,子進程能夠從管道中讀,從而實現了經過管道的進程間通訊。

     

 

示例代碼:

#include<stdio.h>
#include<unistd.h>
 #include<string.h>
int main()
{
int _pipe[2];
int ret=pipe(_pipe);
    if(ret<0)
    {
         perror("pipe\n");
    }
  pid_t id=fork();
  if(id<0)
{
       perror("fork\n");
   }
   else if(id==0)  // child
    {
        close(_pipe[0]);
        int i=0;
        char *mesg=NULL;
       while(i<100)
       {
           mesg="I am child";
           write(_pipe[1],mesg,strlen(mesg)+1);
           sleep(1);
           ++i;
        }
     }
    else  //father
   {
       close(_pipe[1]);
         int j=0;
        char _mesg[100];
         while(j<100)
        {
          memset(_mesg,'\0',sizeof(_mesg ));
          read(_pipe[0],_mesg,sizeof(_mesg));
          printf("%s\n",_mesg);
          j++;
        }
    }
   return 0;
}

結果演示:

    

 

pipe的特色:

1. 只能單向通訊

2. 只能血緣關係的進程進行通訊

3. 依賴於文件系統

四、生命週期隨進程

5. 面向字節流的服務

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

說明:由於管道通訊是單向的,在上面的例子中咱們是經過子進程寫父進程來讀,若是想要同時父進程寫而子進程來讀,就須要再打開另外的管道;

管道的讀寫端經過打開的文件描述符來傳遞,所以要通訊的兩個進程必須從它們的公共祖先那裏繼承管道的件描述符。 上面的例子是父進程把文件描述符傳給子進程以後父子進程之 間通訊,也能夠父進程fork兩次,把文件描述符傳給兩個子進程,而後兩個子進程之間通訊, 總之 須要經過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通訊。

四個特殊狀況:

》 若是全部指向管道寫端的文件描述符都關閉了,而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾同樣

》 若是有指向管道寫端的文件描述符沒關閉,而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了纔讀取數據並返回。

》 若是全部指向管道讀端的文件描述符都關閉了,這時有進程指向管道的寫端write,那麼該進程會收到信號SIGPIPE,一般會致使進程異常終止。

》 若是有指向管道讀端的文件描述符沒關閉,而持有管道寫端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再write會阻塞,直到管道中有空位置了才寫入數據並返回。

 

命名管道FIFO

---------------------------------------------------------------------------------------------------------------------------------

在管道中,只有具備血緣關係的進程才能進行通訊,對於後來的命名管道,就解決了這個問題。FIFO不一樣於管道之處在於它提供一個路徑名與之關聯,以FIFO的文件形式存儲於文件系統中。命名管道是一個設備文件,所以,即便進程與建立FIFO的進程不存在親緣關係,只要能夠訪問該路徑,就可以經過FIFO相互通訊。值得注意的是, FIFO(first input first output)老是按照先進先出的原則工做,第一個被寫入的數據將首先從管道中讀出。

命名管道的建立

建立命名管道的系統函數有兩個: mknod和mkfifo。兩個函數均定義在頭文件sys/stat.h,
函數原型以下:
#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);

函數mknod參數中path爲建立的命名管道的全路徑名: mod爲建立的命名管道的模指
明其存取權限; dev爲設備值,該值取決於文件建立的種類,它只在建立設備文件時纔會用到。這兩個函數調用成功都返回0,失敗都返回-1。下面使用mknod函數建立了一個命名管道:


umask(0);
if (mknod("/tmp/fifo",S_IFIFO | 0666) == -1)
{
      perror("mkfifo error");
      exit(1);
}
函數mkfifo前兩個參數的含義和mknod相同。下,面是使用mkfifo的示例代碼:
   umask(0);
   if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)
   {
       perror("mkfifo error!");
        exit(1);
   }
        "S_IFIFO|0666"指明建立一個命名管道且存取權限爲0666,即建立者、與建立者同組的
用戶、其餘用戶對該命名管道的訪問權限都是可讀可寫( 這裏要注意umask對生成的
管道文件權限的影響) 。
命名管道建立後就可使用了,命名管道和管道的使用方法法基本是相同的。只是使用命
名管道時,必須先調用open()將其打開。由於命名管道是一個存在於硬盤上的文件,而管道
是存在於內存中的特殊文件。
         須要注意的是,調用open()打開命名管道的進程可能會被阻塞。但若是同時用讀寫方式
( O_RDWR)打開,則必定不會致使阻塞;若是以只讀方式( O_RDONLY)打開,則調
用open()函數的進程將會被阻塞直到有寫方打開管道;一樣以寫方式( O_WRONLY)打開
也會阻塞直到有讀方式打開管道。

 

運行示例

-----------------------------------------------------------

那麼此時咱們早server.c中建立命名管道並打開,對管道中進行寫操做,在client.c中進行讀操做,把讀到的內容進行打印,就實現了咱們的使用命名管道通訊。

 

Server.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
#define _PATH_NAME_ "/tmp/file.tmp"
#define _SIZE_ 100
   
 int main()
 {
      int ret=mkfifo(_PATH_NAME_,S_IFIFO|0666);
      if(ret==-1){
           printf("make fifo error\n");
           return 1;
      }
     char buf[_SIZE_];
     memset(buf,'\0',sizeof(buf));
     int fd=open(_PATH_NAME_,O_WRONLY);
     while(1)
     {
         //scanf("%s",buf);
         fgets(buf,sizeof(buf)-1,stdin);
         int ret=write(fd,buf,strlen(buf)+1);
         if(ret<0){
         printf("write error");
         break;
         }
     }
      close(fd);
      return 0;
 }                    

Client.c:
#include<stdio.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<string.h>
#define _PATH_NAME "/tmp/file.tmp"
#define _SIZE_ 100 
int main()
{
    int fd=open(_PATH_NAME,O_RDONLY);
    if(fd<0){
        printf("open file error");
        return 1;
    }
   char buf[_SIZE_];
   memset(buf,'\0',sizeof(buf));
   while(1)
   {
       int ret=read(fd,buf,sizeof(buf)); 
       if(ret<0){
           printf("read end or error\n");
           break;
       }
   printf("%s",buf);
   }
   close(fd);
   return 0;
 }  

結果演示:

   

          能夠看到我實在服務端發送了「Hello」 「I am aerver」,在客戶端能夠收到此內容,完成簡單的進程間通訊。

相關文章
相關標籤/搜索