linux 程式間通訊之FIFO

Wu_XMing發表於2019-02-26

1.概述

FIFO與管道幾乎類似,所以FIFO也是一個位元組流,從FIFO讀取的順序也是與被寫入FIFO的順序一致,容量是也有限的,也是可以確保寫入不超過PIPE_BUF位元組的操作是原子的,FIFO的本質也是一個管道,但傳遞方向是可以雙向的,它們兩者之間的最大差別在於FIFO在檔案系統中擁有一個名稱,並且開啟方式與開啟一個普通檔案是一樣的(使用open),這樣就能夠將FIFO用於非相關程式之間的通訊(如客戶端和伺服器)。(不熟悉管道的可以看我的另一篇文章講述管道linux 程式間通訊之管道

2.建立FIFO

#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);//return 0 on success,or -1 on error
複製程式碼
  • mode 引數指定了新FIFO的許可權即檔案許可權(rw-rw----)
  • mode 引數會與程式中的umask值進行異或來指定最終的許可權數值(所以一般設定umask(0))

3.開啟FIFO

  • FIFO被建立成功,任何程式都能夠開啟它,只要它能夠通過常規的檔案許可權檢測即最初設定的mode。
readFd=open(pathname,O_RDONLY);//開啟只讀方式

複製程式碼
writeFd=open(pathname,O_WRONLY);//開啟只寫方式
複製程式碼
  • 開啟一個FIFO以便讀取資料(open() O_RDONLY標記)將會阻塞直到另一個程式開啟FIFO以寫入資料(open() O_WRONLY)為止。相應地,開啟一個FIFO以寫如資料將會堵塞知道另一個程式開啟FIFO以讀取資料為止。

4.使用FIFO唯一明智的做法是在兩端分別設定一個讀取程式和一個寫入程式的原因

  • 可以確保每次寫入不超過PIPE_BUF位元組的操作是原子的,當超過PIPE_BUF位元組,核心會對訊息進行拆分,那麼就有可能混淆與其他寫者傳送的訊息,如果只有一個寫者則不用擔心混淆即可以忽略這個限制。

  • 多個客戶端從FIFO中讀取資料時會相互競爭,這樣就可能會出某個客戶端讀取到其他客戶端的響應訊息。

  • 在單伺服器、多客戶端應用程式中使用FIFO

    linux 程式間通訊之FIFO

服務端程式核心

// we get the permissions we want
    umask(0);
    if(mkfifo(SERVER_FIFO,S_IRUSR|S_IWUSR|S_IWGRP)==-1&&errno!=EEXIST){
        ERR_EXIT("mkfifo");
    }
    serveFd=open(SERVER_FIFO,O_RDONLY);
    if(serveFd==-1){
        ERR_EXIT("open");
    }

for(;;){
        //Read requests and send responses
        if (read(serveFd, &req, sizeof(struct request)) != sizeof(struct request)) {
                errMsg("ERROR reading request;discarding\n");
                continue;
        }
        //Open client FIFO (previously created by client)
        snprintf(clientFifo,CLIENT_FIFO_NAME_LEN,CLIENT_FIFO_TEMPLATE,(long)req.pid);
        clientFd=open(clientFifo,O_WRONLY);
        if(clientFd==-1){
           errMsg("open\n");
            continue;
        }
        
        //send response and close FIFO
        if(write(clientFd,&resp, sizeof(struct response))!= sizeof(struct response)){
            errMsg("Error writing to FIFO");
        }
        if(close(clientFd)==-1){
            errMsg("close");
        }
      
    }

複製程式碼

客戶端程式核心

    //create our FIFO (before sending request,to avoid a race)
    umask(0);
    snprintf(clientFifo,CLIENT_FIFO_NAME_LEN,CLIENT_FIFO_TEMPLATE,(long)getpid());
    if(mkfifo(clientFifo,S_IRUSR|S_IWUSR|S_IWGRP)==-1&&errno!=EEXIST){
        ERR_EXIT("mkfifo");
    }
    serverFd=open(SERVER_FIFO,O_WRONLY);    
    if(serverFd==-1){
        ERR_EXIT("open");
    }   
    if (write(serverFd, &req, sizeof(struct request)) != sizeof(struct request)) {
            ERR_EXIT("write");
    }
    //open our FIFO,read and display response
    clientFd=open(clientFifo,O_RDONLY);
    if(clientFd==-1){
        ERR_EXIT("open");
    }
    if(read(clientFd,&resp, sizeof(struct response))!= sizeof(response)){
        ERR_EXIT("read");
    }
    if(close(clientFd)==-1){
        ERR_EXIT("close");
    }
複製程式碼

5.非阻塞I/O

當一個程式開啟一個FIFO的一端時,如果FIFO的另一端還沒有被開啟,則該程式會被阻塞。但有些時候阻塞並不是期望的行為,可以通過呼叫open()時指定O_NONBLOCK

fd=open("fifopath",O_RDONLY|O_NONBLOCK);
if(fd==-1){
    errExit("open");
}
複製程式碼

linux 程式間通訊之FIFO

5.1 開啟一個FIFO時使用O_NONBLOCK標記存在兩個目的

  • 它允許單個程式開啟一個FIFO的兩端。這個程式首先會在開啟FIFO時指定O_NONBLOCK標記以便讀取資料,接著開啟FIFO以便寫入資料。
  • 它防止開啟兩個FIFO的程式之間產生死鎖。

linux 程式間通訊之FIFO
開啟兩個FIFO的程式之間的死鎖

5.2 非阻塞read()和write()

  • O_NONBLOCK 標記不僅會影響open()的語義,還會影響後續的read()和write()呼叫語義。

  • 可以通過fcntl() 啟用或禁用開啟著的檔案的O_NONBLOCK狀態的標記。

啟用標記

int flags;
flags=fcntl(fd,F_GETFL);//Fetch open files status flags
flags|=O_NONBLOCK; // Enable O_NONBLOCK bit
fcntl(fd,F_SETFL,flags);// Update open files status flags
複製程式碼

禁用標記

flags=fcntl(fd,F_GETFL);
flags&=~O_NONBLOCK; //disable O_NONBLOCK bit
fcntl(fd,F_SETFL,flags);
複製程式碼

6.管道和FIFO中read和write的語義

從一個包含p位元組的管道或FIFO中讀取n位元組的語義

linux 程式間通訊之FIFO

向一個管道或FIFO寫入n位元組的語義

linux 程式間通訊之FIFO

相關文章