FIFO簡介
FIFO就是Unix的一種復合POSIX標准的進程間通信機制。他又稱為命名管道,跟管道的不同點是,每個FIFO都有一個路徑名與之關聯。
FIFO雖然有路徑名,但是他這中文件是在內核態(管道也是在內核態),跟文件系統沒有關系。
單個服務器進程,多個客戶端進程與服務器進通信。客戶端進程想服務器進程發送請求(客戶端進程通過write寫FIFO),服務端處理(通過read讀客戶進程的請求)之后返回相應內容(通過write寫入專用FIFO)。
單個服務器進程多個客戶端進程通信框架
客戶進程和服務器進程都知道專用FIFO的路徑名,關鍵是怎樣創建客戶和服務器進程專用的FIFO。因為,一個系統的進程id是確定的並且互斥,所以我們可以通過進程id去尋找FIFO,客戶進程和服務進程之間的FIFO用進程id來創建,在客戶和服務器進程之間協商一個格式(比如“fifo.pid”格式)。
多個客戶進程向服務器進程請求服務
本示例代碼,實現的是將客戶進程請求打開一個文件,具體就是客戶進程傳文件名,服務器進程將文件內容返回。
客戶端代碼:

1 /* 2 * client.c 3 * 4 * Created on: Nov 2, 2016 5 * Author: sujunjun 6 */ 7 8 9 #include<stdio.h> 10 #include<unistd.h> 11 #include<sys/stat.h> 12 #include<limits.h> 13 #include<fcntl.h> 14 #include<stdlib.h> 15 #include<errno.h> 16 #include<string.h> 17 18 #define FIFO_SERVER "fifo.server" 19 #define SIZE PIPE_BUF 20 #define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH) 21 22 int main(){ 23 24 pid_t pid=getpid(); 25 char buf[SIZE]; 26 char client_fifo_name[SIZE];//from server 27 snprintf(client_fifo_name,sizeof(client_fifo_name),"fifo.%ld",(long)pid); 28 if(mkfifo(client_fifo_name,FILE_MODE)<0 && errno!=EEXIST){ 29 printf("mk error \n"); 30 exit(1); 31 } 32 snprintf(buf,sizeof(buf),"%ld ",(long)pid); 33 int len=strlen(buf); 34 char *pathname=buf+len; 35 fgets(pathname,SIZE-len,stdin); 36 len =strlen(buf);/f include \n 37 38 int writefd=open(FIFO_SERVER,O_WRONLY); 39 write(writefd,buf,len-1);//len-1==not include \n 40 41 int readfd=open(client_fifo_name,O_RDONLY); 42 int r; 43 while((r=read(readfd,buf,SIZE))>0){ 44 write(STDOUT_FILENO,buf,r); 45 } 46 close(readfd); 47 unlink(client_fifo_name);//delete this temp file 48 return 0; 49 }
服務器代碼:

1 /* 2 * server.c 3 * 4 * Created on: Nov 2, 2016 5 */ 6 7 #include<stdio.h> 8 #include<unistd.h> 9 #include<sys/stat.h> 10 #include<errno.h> 11 #include<stdlib.h> 12 #include<fcntl.h> 13 #include<limits.h> 14 #include<string.h> 15 16 #define FIFO_SERVER "fifo.server"//all known 17 18 #define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH) 19 20 #define SIZE PIPE_BUF 21 int main(){ 22 if(mkfifo(FIFO_SERVER,FILE_MODE)<0 && errno!=EEXIST){ 23 printf("mk error \n"); 24 exit(1); 25 } 26 27 int readfd=open(FIFO_SERVER,O_RDONLY); 28 int writefd=open(FIFO_SERVER,O_WRONLY); 29 30 if(readfd==-1||writefd==-1){ 31 printf("open error \n"); 32 exit(1); 33 } 34 35 int r;char buf[SIZE];char *pathname;pid_t pid;char client_fifo_name[SIZE]; 36 while((r=read(readfd,buf,SIZE))>0){ 37 buf[r]='\0'; 38 if((pathname=strchr(buf,' '))==NULL){ 39 printf("not include pid\n"); 40 continue; 41 } 42 *pathname++='\0';//*pid_s=0 pid_s++ 43 pid=atol(buf); 44 snprintf(client_fifo_name,sizeof(client_fifo_name),"fifo.%ld",(long)pid); 45 if((writefd=open(client_fifo_name,O_WRONLY))<0){ 46 printf("error\n"); 47 continue; 48 } 49 int fd; 50 if((fd=open(pathname,O_RDONLY))<0){ 51 printf("open this file error\n"); 52 write(writefd,"open this file error\n",sizeof("open this file error\n")); 53 close(writefd); 54 }else { 55 while((r=read(fd,buf,SIZE))>0){ 56 write(writefd,buf,r); 57 } 58 close(fd); 59 } 60 close(writefd); 61 } 62 return 0; 63 }
運行示例:
編譯兩個文件
gcc client.c -o client
gcc server.c -o server
./server
./client
./client
./client
多開幾個客戶進程,請求打開不同的文件,就會看到顯示的文件內容。
總結
本例子設計的是一個迭代服務器,如果要設計一個並發服務器,還需要一些多線程編程的知識。
還是要對FIFO一些特性要熟悉,比如open一個FIFO的時候,阻塞和非阻塞open之間的區別。還有FIFO的一次請求字節數必須小於等於PIPE_BUF(在limits.h中),這樣能保證一次讀請求是原子操作。如果不是原子的,那么兩次請求的字節內容將無法區分。因為服務器進程是一直在循環讀請求字節(服務器進程里有一個大循環,read用readline代替就更好理解了)。
snprintf()函數還是比較方便,java中提供的一些字符串與數字之間轉換的便利操作,其實在C語言中也有,只不過被我們忽視了。snprintf函數就為我們提供了一個很好的數字轉換為字符串的函數。例如:snprintf(buf,buf的長度,"process id is %ld",(long)getpid());最后buf中存放的是"process id is 1234"(進程具體ID).
另外,迭代服務器存在一些弊端,就是在處理服務型攻擊的時候比較麻煩,這個不如並發服務器好處理。
服務型攻擊,就拿咱們的這個代碼來說,如果咱們的客戶進程只是請求服務,卻從不打開自己進程與服務器專用FIFO來讀服務回傳的內容,這個時候,服務器可能就處於停頓狀態。這就叫服務型攻擊(DoS型攻擊)。