第1章 管道和FIFO
1.1 pipe
int pipe(int fd[2]);
創建一個單向、半雙工管道,其中fd[0]用於讀,fd[1]用於寫。
1.2 fork
管道很少在單個進程內使用,一般用在兩個有親緣關係的進程間,父進程在pipe後fork,然後和子進程或子進程的後裔利用這個管道通信。
pipe(fd);
if ((pid = fork()) == 0)
{
close(fd[0]);
/*用fd[1]向父進程傳遞數據*/
}
close(fd[1]);
/*用fd[0]接收子進程傳遞的數據*/
1.3 全雙工管道
可移植的創建全雙工管道的方法是創建兩個管道,分別負責一個方向的通信。
Linux中可以用socketpair創建一對Unix域套接字來模擬全雙工管道的功能。
1.4 popen和pclose
FILE *popen(const char *cmd, const char *type);
int pclose(FILE *stream);
管道的一種常見應用模式是父進程pipe+fork後,子進程重定向標準輸入和輸出,再exec執行一個目標程序。
popen用來簡化這一系列操作,其中type用來指定讀寫。pclose會先關閉文件流,等到子進程執行結束後再返回子進程的退出狀態。
1.5 shell中創建管道
當在shell中輸入這樣的命令時:
who | sort | lp
該shell將創建三個進程和其間的兩個管道,還把每個管道的fd[0]複製到相應進程的stdin,fd[1]複製到相應進程的stdout上。
1.6 mkfifo
int mkfifo(const char *pathname, mode_t mode);
mkfifo用來創建一個FIFO文件,它的mode隱含了O_CREAT | O_EXCL。FIFO允許無親緣關係的任意進程訪問,這是和管道的最大區別。
1.7 打開FIFO
創建FIFO後,可用open或fopen打開FIFO,打開時只能指定讀或寫,不能同時指定。
默認的阻塞模式下,以讀或寫模式打開一個FIFO時,若當前沒有進程以對應的寫或讀模式打開FIFO,這個open將會阻塞,直到另一個進程以對應模式打開FIFO才返回。不正確的FIFO操作可能會導致死鎖。
非阻塞模式下,讀模式打開一個沒有進程寫打開的FIFO會成功返回,相反則會返回錯誤。
所有打開FIFO的進程都關閉FIFO後,FIFO中的剩餘數據會被丟棄。
1.8 設置屬性
open打開FIFO時可指定O_NONBLOCK標誌,如:
writefd = open(FIFO1, O_WRONLY | O_NONBLOCK, 0);
對描述符可以用fcntl啓用O_NONBLOCK標誌。管道不能用open,因此必須用這一方法:
flags = fcntl(fd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
1.9 讀寫管道和FIFO
對管道和FIFO的write總從末尾寫,read總從開頭讀,lseek會返回錯誤。
阻塞模式下,read時若管道中有數據則返回,可能返回的數據比請求的少;若管道爲空,如果有進程寫打開該管道,則阻塞,否則返回0,表示遇到了EOF。
write時寫入的數據會分成若干個最大爲PIPE_BUF字節的段,每段都是原子寫入,但多個進程的多段數據寫入不保證原子性。寫一個沒有進程讀打開的FIFO會產生SIGPIPE信號。
非阻塞read和write不會影響操作的原子性,但會在需要阻塞的時候返回錯誤,包括:
1) read空管道。
2) write的字節數不大於PIPE_BUF,但管道中剩餘空間不足時,爲了保證原子操作。
3) write的字節數大於PIPE_BUF,管道中還有空間則寫入那麼多字節,否則返回錯誤。
1.10 FIFO的單服務器多客戶
服務器需要一個公開的FIFO路徑接收客戶的請求,客戶爲了能接收服務器的迴應,應該在請求中附上客戶自己創建的FIFO路徑。
併發服務器可以創建一個子進程池或子線程池來提高響應速度。
對迭代服務器的拒絕服務器型攻擊是指故意發送一個不完整的請求,服務器就會陷入對剩下部分的等待中。對併發服務器的攻擊則是發送大量獨立請求,導致服務器fork失敗。
1.11 字節流和消息
管道和FIFO發送的是無邊界的字節流,有三種技巧用於判定消息邊界:
1) 約定的終止符。
2) 顯式指定長度。
3) 每次連接一個記錄。
1.12 管道和FIFO的限制
系統的限制:
1) OPEN_MAX 打開的最大描述符數。
2) PIPE_BUF 原子寫入的最大數據量。
FIFO是單主機上的IPC形式,不能用在NFS文件系統上。