1. 程式人生 > >linux程式設計——對FIFO進行讀寫操作(第十三章)

linux程式設計——對FIFO進行讀寫操作(第十三章)

4.對FIFO進行讀寫操作

使用O_NONBLOCK模式會影響的對FIFO的read和write呼叫.
對一個空的,阻塞的FIFO
(即沒有用O_NONBLOCK標誌開啟)的read呼叫將等待,直到有資料可以讀時才繼續執行.與此相反,對一個空的,非阻塞的FIFO的read呼叫將立刻返回0位元組.
對一個完全阻塞的FIFO的write呼叫將等待,直到資料可以寫入時才繼續執行
.如果非阻塞的FIFO不能接收所有寫入的資料,它將按下面的規則執行.
如果請求寫入的資料長度小於等於PIPE_BUF位元組,呼叫失敗,資料不能寫入.
如果請求寫入的資料長度大於PIPE_BUF位元組,將寫入部分資料,返回實際寫入的位元組數,返回值也可能是0.
FIFO的長度是需要考慮的一個很重要的因素,
系統對任一時刻在一個FIFO中可以存在的資料長度是有限制的,它由#define PIPE_BUF語句定義,通常可以在標頭檔案limits.h中找到它.在linux和許多其他類UNIX系統中,它的值通常是4096位元組,但在某些系統中它可能會小到512位元組.系統規定:在一個以O_WRONLY方式(即阻塞方式)開啟的FIFO中,如果寫入的資料小於等於PIPE_BUF,那麼或者寫入全部位元組,或者一個位元組都不寫入.
雖然,對只有一個FIFO寫程序和一個FIFO讀程序的簡單情況來說,這個限制並不是非常重要的,但只使用一個FIFO並允許多個不同的程式向一個FIFO讀程序傳送請求的情況是很就常見的.如果幾個不同的程式嘗試同時向FIFO寫資料,能否保證來自不同程式的資料庫不相互交錯就非常關鍵了.也就是說,每個寫操作都必須是"原子化"的.怎樣才能做到這一點呢?

如果能保證所有的寫請求是發往一個阻塞的FIFO的,並且每個請求的資料長度小於等於PIPE_BUF位元組,系統就可以確定資料塊不會交錯在一起.通常將每次通過FIFO傳遞的資料長度限制為PIPE_BUF位元組是個好辦法,除非只使用一個寫程序和讀程序.
使用FIFO實現程序間通訊
為了演示不相關的程序是如何使用命名管道進行通訊的,需要用到兩個獨立的程式fifo3.c和fifo4.c
/*************************************************************************
 > File Name:    fifo3.c
 > Description:  fifo3.c程式是生產者程式,它在需要時建立管道,然後儘可能塊地向管道中寫入資料
 > Author:       Liubingbing
 > Created Time: 2015年07月14日 星期二 21時28分28秒
 > Other:        fifo3.c
 ************************************************************************/

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

#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)

int main()
{
	int pipe_fd;
	int res;
	int open_mode = O_WRONLY;
	int bytes_sent = 0;
	char buffer[BUFFER_SIZE + 1];

	/* 檢查FIFO_NAME檔案是否存在,如果不存在就建立它 */
	if (access(FIFO_NAME, F_OK) == -1) {
		/* mkfifo建立命名管道(即特殊型別的檔案FIFO) */
		res = mkfifo(FIFO_NAME, 0777);
		if (res != 0) {
			fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
			exit(EXIT_FAILURE);
		}
	}
	printf("Process %d opening FIFO_OWRONLY\n", getpid());

	/* open函式以O_WRONLY方式開啟FIFO檔案,如果成功pipe_fd指向開啟的檔案 */
	pipe_fd = open(FIFO_NAME, open_mode);
	printf("Process %d result %d\n", getpid(), pipe_fd);

	if (pipe_fd != -1) {
		/* */
		while(bytes_sent < TEN_MEG) {
			/* write函式從buffer指向的記憶體中寫入BUFFER_SIZE個位元組到pipe_fd檔案中
			 * 如果成功則返回實際寫入的位元組數 */
			res = write(pipe_fd, buffer, BUFFER_SIZE);
			if (res == -1) {
				fprintf(stderr, "Write error on pipe\n");
				exit(EXIT_FAILURE);
			}
			bytes_sent += res;
		}
		(void)close(pipe_fd);
	} else {
		exit(EXIT_FAILURE);
	}
	
	printf("Process %d finished\n", getpid());
	exit(EXIT_SUCCESS);
}
第一個程式是生產者程式,它在需要時建立管道,然後儘可能地向管道中寫入資料
/*************************************************************************
 > File Name:    fifo4.c
 > Description:  fifo4.c程式是消費者程式,它從FIFO讀取資料並丟棄它們
 > Author:       Liubingbing
 > Created Time: 2015年07月14日 星期二 22時07分34秒
 > Other:        fifo4.c
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <limits.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;
	int open_mode = O_RDONLY;
	char buffer[BUFFER_SIZE + 1];
	int bytes_read = 0;

	memset(buffer, '\0', sizeof(buffer));
	
	printf("Process %d opening FIFO O_RDONLY\n", getpid());
	/* open函式開啟FIFO_NAME檔案,以open_mode的方式(即O_RDONLY)
	 * 如果成功,則返回檔案描述符 */
	pipe_fd = open(FIFO_NAME, open_mode);
	printf("Process %d result %d\n", getpid(), pipe_fd);

	if (pipe_fd != -1) {
		do {
			/* read函式從pipe_fd指向的檔案中讀入BUFFER_SIZE個位元組的資料到buffer指向的記憶體 
			 * 如果成功,返回實際讀入資料的位元組數 */
			res = read(pipe_fd, buffer, BUFFER_SIZE);
			bytes_read += res;
		} while (res > 0);
		(void)close(pipe_fd);
	} else {
		exit(EXIT_FAILURE);
	}

	printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
	exit(EXIT_SUCCESS);
}
第二個程式是消費者程式,它的程式碼要簡單的多,它從FIFO讀取資料並丟棄它們.
在執行這兩個程式的同時,用time命令對讀程序進行計時,輸出結果如下所示:
$ ./fifo3 &
$ time ./fifo4

兩個程式使用的都是阻塞模式的FIFO,首先啟動fifo3(寫程序/生產者),它將阻塞以等待讀程序開啟這個FIFO.fifo4(消費者)啟動以後,寫程序解除阻塞並開始向管道寫資料.同時,讀程序也開始從管道中讀取資料.
linux會安排好這兩個程序之間的排程,使它們在可以執行的時候執行,在不能執行的時候阻塞.因此,寫程序將在管道滿時阻塞,讀程序將在管道空時阻塞.
time命令的輸出顯示,讀程序只運行了不到0.1秒的時間,卻讀取了10MB的資料,這說明管道(至少在現在Linux系統中的實現)在程式之間傳遞資料是很有效率的.