Linux程式執行緒學習筆記:程式間通訊 之 管道

weixin_34262482發表於2010-10-13

                                           Linux程式執行緒學習筆記:程式間通訊 之 管道

                                               周銀輝 

 

中秋和國慶長假連起來耍了19天,平時學的點東西都快全忘了,那麼趕緊補習下Linux吧。

所謂“程式間通訊(IPC,inter-process communication)”,按照其目的講就是讓程式之間能夠“共享資料”,“傳輸資料”,“事件通知”,“程式通知”等,我所知道的一共有“管道” “訊號” “訊息(報文)” “共享記憶體” “套接字” 這幾種方式,我們會挨個挨個說,今天就說說管道。

所謂管道嘛,顧名思義類似於我們生活中的水管,只不過其中流動的是“資料”或者說一個一個位元組,只能單向流動的我們稱為“半雙工”,能雙向流動的稱為“全雙工”,其有兩個端點,資料流入的那一端稱為“寫端”,反之則稱為“讀端”,這兩個端點實際上是兩個“描述字”。這樣的管道可以連線在兩個程式之間,成為資料傳輸的通道。

按照是否有名字,管道分為“未命名管道”也就是你經常看到的pipe,以及“有名管道”也就是你經常看到的FIFO(first in first out, 和資料結構中的FIFO一樣,其也是按照“先進先出”的法則傳輸資料)。

 

1,pipe

pipe一詞雖然是“管道”二字的英文翻譯,但在這裡其不是管道的統稱,而是一種最基本和簡單的管道形式:未命名管道。由於其沒有名字(或者說id之類的),所以其無法在兩個毫無干系的兩個程式間使用。試想一下,程式A建立了一個管道,程式B無法去找到該管道並使用它,因為沒有任何可拿去查詢的憑據,連名字都沒有... 但在一種特殊情況下其是有用的,如下圖,假設程式A建立了一個管道:

  

其中的箭頭代表資料寫入和讀出,很明顯,程式A可以從管道的寫端將資料寫入,然後再從讀端讀出,但這似乎沒有什麼意義。
但,如果在管道建立以後,我們將程式A進行一次fork(),有意思的事情發生了, 子程式會複製父程式的大部分資訊,這些資訊裡當然包含了代表了管道讀端和寫端的兩個“描述字”(但管道仍然只有一份,就像被兩個程式讀寫的某個硬碟檔案只有一份一樣),所以其就演變成下圖這個樣子:

 如果此時,我們將上圖中左上角以及右下角的兩個箭頭拋棄掉(相當於是說將這兩個箭頭所對應的描述字關閉), 那麼就如下所示咯:
  

哈,注意到了嗎?我們建立了一個從程式A流向程式B的資料通道。

 

那麼,將上述過程寫成程式碼的形式就很簡單了。

首先是 int pipe(int f[2]) 這個函式,其需要標頭檔案<unistd.h>,這個函式將建立一個未命名管道,並將管道的讀端描述字包含在f[0]中,將寫端描述字放在f[1]中,然後你就可以像利用普通檔案描述字一樣來讀寫資料了。

然後是fork函式,不多講,不瞭解的同學一定要先搞清楚了,比如看這篇文章。
再次是int close(int fd) 函式, 其需要標頭檔案<unistd.h>,其用於關閉指定的檔案描述字。

最後是write和read函式, 其需要標頭檔案<unistd.h>,用於讀寫資料。

OK,上程式碼:

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

#define BUFF_SZ 256

int main()
{
    printf(
"app start...\n");
    
    pid_t          pid;
    
int            pipe_fd[2];
    
char           buf[BUFF_SZ];
    
const char     data[] = "hi, this is the test data";
    
int            bytes_read;
    
int            bytes_write;
    
    
//clear buffer, all bytes as 0
    memset(buf, 0sizeof(buf));
    
    
//creat pipe
    if(pipe(pipe_fd) < 0)
    {
        printf(
"[ERROR] can not create pipe\n");
        exit(
1);
    }
    
    
    
//fork an new process
    if(0 == (pid=fork()))
    {
        
//close the write-point of pipe in child process
        close(pipe_fd[1]);
        
        
//read bytes from read-point of pipe in child process
        if((bytes_read = read(pipe_fd[0], buf, BUFF_SZ)) > 0)
        {
            printf(
"%d bytes read from pipe : '%s'\n", bytes_read, buf);
        }
        
        
//close read-point of pipe in child process
        close(pipe_fd[0]);
        exit(
0);
    }
    
    
    
//close read-point of pipe in parent process
    close(pipe_fd[0]);
    
    
//write bytes to write-point of pipe in parent process
    if((bytes_write = write(pipe_fd[1], data, strlen(data))))
    {
        printf(
"%d bytes wrote to pipe : '%s'\n", bytes_write, data);
    }
    
    
//close write-point of pipe in parent process
    close(pipe_fd[1]);
    
    
//wait child process exit
    waitpid(pid, NULL, 0);
    
    printf(
"app end\n");
    
    
return 0;
}

 

執行輸出為:

app start...
25 bytes wrote to pipe : 'hi, this is the test data'
25 bytes read from pipe : 'hi, this is the test data'
app end

 

 

2,FIFO

與“無名管道”不同的是,FIFO擁有一個名稱來標誌它,所謂的名稱實際上就是一個路徑,比如“/tmp/my_pipe”,其對應到磁碟上的一個管道檔案,如果我們用file命令來檢視其檔案型別的話,會得到如下輸出:

ZHOU-YHmatoMacBook-Pro:tmp zhouyh$ file my_fifo 
my_fifo: fifo (named pipe)

 

為了簡化對FIFO的理解,我們可以這樣來假想:程式A在磁碟上建立了一個名為my_pipe的檔案,並向其中寫入一些資料,然後程式B開啟該檔案,並將資料從檔案中讀出,這樣我們便實現了程式A和程式B之間的通訊。大致原理如此,只不過FIFO做了更精細的一些操作,以便實現起來更可靠。

 

另外,我們需要知道的是,FIFO是單向(半雙工)傳輸資料的。

函式 int mkfifo (char* path, mode_t mode) 負責建立FIFO管道,其需要標頭檔案<sys/stat.h>,引數path即要建立的管道檔案存放位置,mode引數即檔案許可權,更多的參考這裡。 

FIFO管道建立完成以後,便可以使用open函式來開啟它,然後進行讀寫操作了。 

看下面這個簡單的demo,其將測試資料由程式A傳遞給程式B(為防止混淆視線,我將一些條件判斷和異常處理程式碼刪掉了): 

先建立一個程式A,其負責建立FIFO管道,並向其中寫入一些資料:

/*
 * process A: create FIFO and write data
 
*/

#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.h>
#include 
<fcntl.h>
#include 
<limits.h>
#include 
<sys/types.h>
#include 
<sys/stat.h>
#include 
<unistd.h>

#define FIFO_NAME "/tmp/my_fifo"

int main()
{
    
int pipe_fd;
    
    
//if the pipe file do not exist
    if (access(FIFO_NAME, F_OK) == -1)
    {
        
//creat FIFO pipe file
        mkfifo(FIFO_NAME, 0777);
    }
    
    
//open FIFO pipe file.
    
//this will be brocked until some one open another end point(read-point) of this pipe
    pipe_fd = open(FIFO_NAME, O_WRONLY);
    
    
//write data into pipe 
    write(pipe_fd, "hi, this is a test", PIPE_BUF);
    
    
//close FIFO pipe file descriptor
    close(pipe_fd);
    
    
return 0;
}

然後建立程式B,它從管道中讀取資料並顯示出來:

/*
 * process B: read data from FIFO
 
*/

#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<string.h>
#include 
<fcntl.h>
#include 
<limits.h>
#include 
<unistd.h>
#include 
<sys/types.h>
#include 
<sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF

int main()
{
    
int pipe_fd;
    
    
char buffer[BUFFER_SIZE + 1];
    
//reset all bytes in buffer as '\0' 
    memset(buffer, '\0'sizeof(buffer));
    
    
//open FIFO pipe file.
    
//this will be brocked until some one open another end point(write-point) of this pipe
    pipe_fd = open(FIFO_NAME, O_RDONLY);
    
    
if(read(pipe_fd, buffer, BUFFER_SIZE) > 0)
    {
        printf(
"data from FIFO : %s\n", buffer);
    }
    
    
//close pipe file descriptor
    close(pipe_fd);

    
return 0;
}

執行下程式便會發現,無論是先執行A或是B,先執行起來的都會等待另外一個,這時open函式第二個引數的原因,我們可以新增O_NONBLOCK選項來取消阻塞。關於open函式,更多的看這裡 

 

下面這個demo比較有意思,在程式A中敲入字元,其會立即傳遞給程式B(無需等待Enter鍵),然後程式B會將它顯示出來,關於如何取消控制檯對Enter鍵的等待,可以參考這裡。 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <termios.h>


#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF

static struct termios oldt;

//restore terminal settings
void restore_terminal_settings(void)
{
    tcsetattr(0, TCSANOW, &oldt);  /* Apply saved settings */
}

//make terminal read 1 char at a time
void disable_waiting_for_enter(void)
{
    
struct termios newt;
    
    
//Save terminal settings
    tcgetattr(0&oldt); 
    
//init new settings
    newt = oldt;  
    
//change settings
    newt.c_lflag &= ~(ICANON | ECHO);
    
//apply settings
    tcsetattr(0, TCSANOW, &newt);
    
//make sure settings will be restored when program ends
    atexit(restore_terminal_settings);
}

int main()
{
    
int pipe_fd;
    
int res;

    
char buffer[BUFFER_SIZE + 1];
    memset(buffer, '\0'sizeof(buffer));
    
    
//if the pipe file do not exist
    if (access(FIFO_NAME, F_OK) == -1)
    {
        
//creat FIFO pipe file
        res = mkfifo(FIFO_NAME, 0777);
        
if (res != 0)
        {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }
    
    
//open FIFO pipe file.
    
//this will be brocked until some one open another end point(read-point) of this pipe
    pipe_fd = open(FIFO_NAME, O_WRONLY);
    
    
//if FIFO pipe file open sucessfully
    if (pipe_fd != -1)
    {
        printf("input something and press RETURN\n");
        
char ch;
        
        disable_waiting_for_enter();
        
        
while ((ch = getchar()) != '\n'
        {
            buffer[0= ch;
            
            
//write data into pipe 
            res = write(pipe_fd, buffer, BUFFER_SIZE);
            
            
if (res == -1)
            {
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            
        }
        
        
//close FIFO pipe file descriptor
        close(pipe_fd);
    }
    
else
    {
        exit(EXIT_FAILURE);
    }
    
    printf("Process %d finish\n", getpid());
    exit(EXIT_SUCCESS);
}

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF

int main()
{
    
int pipe_fd;
    
int res;
    
    
char buffer[BUFFER_SIZE + 1];
    
//reset all bytes in buffer as '\0' 
    memset(buffer, '\0'sizeof(buffer));
    
    
//open FIFO pipe file.
    
//this will be brocked until some one open another end point(write-point) of this pipe
    pipe_fd = open(FIFO_NAME, O_RDONLY);
    
    
if (pipe_fd != -1)
    {
        printf("data read from buffer : \n");
        
        
//read all data from pipe file by BUFFER_SIZE each time
        do
        {
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            
if(res>0)
            {
                printf("%s", buffer);
                fflush(stdout);
            }
            
        }while(res > 0);
        
        
//close pipe file descriptor
        close(pipe_fd);
    }
    
else
    {
        exit(EXIT_FAILURE);
    }
    
    printf("\nProcess %d finish\n", getpid());
    exit(EXIT_SUCCESS);
}

 

 

相關文章