歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> 學習Linux >> linux進程間通信(一)之管道,linux進程通信管道

linux進程間通信(一)之管道,linux進程通信管道

日期:2017/3/6 8:55:46   编辑:學習Linux

linux進程間通信(一)之管道,linux進程通信管道

linux進程間通信(一)之管道,linux進程通信管道


我們先來說說進程間通信(IPC)的一般目的,大概有數據傳輸、共享數據、通知事件、資源共享和進程控制等。但是我們知道,對於每一個進程來說這個進程看到屬於它的一塊內存資源,這塊資源是它所獨占的,所以進程之間的通信就會比較麻煩,原理就是需要讓不同的進程間能夠看到一份公共的資源。所以交換數據必須通過內核,在內核中開辟⼀塊緩沖區,進程1把數據從⽤戶空間 拷到內核緩沖區,進程2再從內核緩沖區把數據讀⾛,內核提供的這種機制稱為進程間通信。一般我們采用的進程間通信方式有

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

匿名管道 pipe

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

管道的創建

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

調用pipe函數,會在內核中開辟出一塊緩沖區用來進行進程間通信,這塊緩沖區稱為管道,它有一個讀端和一個寫端。

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. 依賴於文件系統

4、生命周期隨進程

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()

運行示例

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

那麼此時我們早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”,在客戶端可以收到此內容,完成簡單的進程間通信。

http://xxx/Linuxjc/1157046.html TechArticle

Copyright © Linux教程網 All Rights Reserved