IPC學習筆記(2) 管道和FIFO

來源:互聯網
上載者:User
1、概述

管道是最初的UNIX IPC形式,由於管道沒有名字,所以只能在用於有親緣關係的進程(所謂的親緣關係是指進程間有共同的祖先)。FIFO則被稱為具名管道。

先說明fork,exec,_exit函數對管道及fifo函數的影響:
fork:子進程取得父進程的管道以及fifo描述字的拷貝。
exec:所有代開的描述字依舊開啟,除非已經設定描述字的FD_CLOEXEC位。
_exit:關閉所有開啟的描述字,最後一個描述字關閉時刪除管道與FIFO中的資料。

管道和FIFO涉及的函數有:pipe, mkfifo, fcntl, open, read, write, close, unlink.

2、管道函數及說明:int pipe(int fd[2]);

(1)、這個函數提供一個單向管道。fd是輸出參數,fd[0]用來讀出,fd[1]用來寫入(這裡的讀出和寫入是的賓語是管道)。函數成功返回0,錯誤返回-1.

注意:a、linux的system call(相當於系統API)函數的傳回值是成功返回0,返回其他的就表示出錯,而windows的api函數則剛好相反,錯誤返回0,成功返回其他。b、這裡不寫函數的標頭檔,要知道它用到那些標頭檔,在類unix下用man來查看即可,如:man 2 pipe,2表示pipe是一個系統調用,如果是c函數庫裡面的函數則按3。

(2)、管道的典型用途如下(用來作為父子進程訊息的共用):
a、建立管道1(int fd1[2]) 和管道2(int fd2[2]);
b、fork建立子進程
c、父進程關閉管道1的讀出端和2的寫入端(fd1[0],fd2[1])
d、子進程關閉管道2的讀出端和1的寫入端(fd1[1],fd2[0])
代碼如下:

  1. int pipe1[2];
  2. int pipe2[2];

  1. pid_t childPid;
  2. pipe(pipe1);
  3. pipe(pipe2);

  4. if( ( childPid = fork()) == 0 ) /* child process */
  5. {
  6.     close(pipe1[1]);
  7.     close(pipe2[0]);
  8.     ...   /* your code */
  9. }
  10. /* parent process */
  11. close(pipe1[0]);
  12. close(pipe2[1]);
  13. ...
  14. waitpid(childPid, NULL, 0);
  15. ...

子進程在這裡用來專門做一件事情,像伺服器。而父進程就相當於用戶端。如是,子進程在完成任務之後就exit(0),那麼但子進程終止時候變成殭屍進程(zombie),核心給父進程發送一個訊號SIGCHILD,但是父進程沒有撲抓,預設行為是忽略;父進程後面調用到waitpid的時候就可以等待childPid進程的終止,取得終止狀態。如果沒有waitpid而直接返回,則父進程返回後那個殭屍子進程將成為託孤給init進程的孤兒進程。核心將為init進程發送SIGCHILD訊號。

這樣子,就變成: 客戶-->管道fd1-->伺服器;伺服器-->fd2-->客戶

3、fifo 有名管道(fifo的意思是:先進先出)
也是一個半雙工的單向資料流,但是有一個路徑名與之相連。函數如下:
int mkfifo(const char *pathname, mode_t mode);

說明:
a、mkfifo函數已經隱含制訂了O_CREAT | O_EXCL也就是說如果pathname那個管道已經存在,則返回一個錯誤值EEXIST,如果想開啟一個已經存在的管道,用open()即可。
b、由於fifo是先進先出的,所以write函數總是往管道末尾寫入資料,而read函數總是從管道的開頭返回資料,如果用lseek定位檔案指標,會返回ESPIPE錯誤。

應用:

  1. /* server.c */
  2. ...
  3. if( ( mkfifo(FIFO1, FILE_MODE ) < 0 ) && (errno != EEXIST ) )
  4.     /* error handle and exit(1) */
  5. if( ( mkfifo(FIFO2, FILE_MODE ) < 0 ) && (errno != EEXIST ) )
  6. {
  7.     unlink(FIFO1);
  8.     /* error handle and exit(1) */
  9. }
  10. readfd = open(FIFO1, O_RDONLY, 0);
  11. writefd = open(FIFO2, O_WRONLY, 0);
  12. /* your code */
  13. exit(0);

這是伺服器處理序的FIFO,其中FIFO1,FIFO2可以定義一個宏來指定,比如,#define FIFO1 "/temp/fifo.1"(路徑名隨意,不過由於是臨時的檔案,一般放在/temp目錄下,許可權比較低嘛)

開啟檔案之後就可以用read,write函數往管道讀寫資訊了,FIFO1,FIFO2就是像檔案描述符。

再看client的代碼:

  1. /* client.h */
  2. writefd = open(FIFO1, O_WRONLY, 0);
  3. readfd = open(FIFO2, O_RDONLY, 0);
  4. /* your code */
  5. close(writefd);
  6. close(readfd);
  7. unlink(FIFO1);
  8. unlink(FIFO2);
  9. exit(0);

說明:a、通常管道是由伺服器建立,由用戶端銷毀(unlink函數),close只是關閉管道而已。b、注意clinet.h上的那兩個open和server.h上的那兩個open的順序,否則會引起死結(見後面)

4、阻塞態下的規則(阻塞態是預設的狀態):

(1)、管道和FIFOopen函數的返回結果:
writefd = open(FIFO1, O_WRONLY, 0); 這個是用唯寫方式開啟管道,如果FIFO1此時已經有別的進程以唯讀方式開啟(就是說在這條代碼運行之前,已經有代碼open(FIFO1, O_RDONLY, 0)運行),則此函數返回成功,否則,將會阻塞到有別的進程以唯讀方式開啟FIFO1為止。反過來也一樣。

(2)、read函數作用於管道和FIFO 的返回結果:
如果FIFO1為空白(就是說裡面沒有資料):該管道以唯讀方式開啟,則返回0;FIFO1以唯寫方式開啟,則阻塞到FIFO1有資料或者是FIFO1不再以寫方式開啟為止。

(3)、write函數作用於管道和FIFO的返回結果:
如果FIFO1沒有以唯讀方式開啟,則給進程產生SIGPIPE訊號(預設行為是終止該進程);如果已經以唯讀方式開啟,則見下。

(4)、write操作的原子性:
如果寫入資料的位元組數小於等於PIPE_BUF,則該函數保證其原子性;否則不能保證。

現在來看一下上面那個程式,伺服器代碼先運行,因此:當起運行到readfd = open(FIFO1, O_RDONLY, 0);的時候,還沒有任何進程以O_WRONLY方式開啟FIFO1,進程阻塞在這裡;

然後用戶端代碼開始運行,但運行到這個地方的時候:writefd = open(FIFO1, O_WRONLY, 0);伺服器阻塞的地方開始釋放,而在用戶端,因為FIFO1在伺服器已經是以O_RDONLY開啟了,所以繼續運行。

如果用戶端的這兩個open交換一個順序,那麼readfd = open(FIFO2, O_RDONLY, 0);先運行,則由於FIFO2還沒有以O_WRONLY方式開啟,所以用戶端也阻塞,客戶和伺服器都阻塞,大家都在等對方的資源,這種情況我們稱之為死結(deadlock)

5、非阻塞態下的規則:

(1)、非阻塞態的設定。
a、調用open時可以指定 readfd = open(FIFO1, O_RDONLY | O_NONBLOCK );
b、如果readfd已經開啟,則可以用fcntl來設定O_NONBLOCK標誌。

(2)、對open操作的影響:
如果當前操作是wrfd = open(FIFO1, O_WRONLY | O_NONBLOCK, 0); 那麼如果是FIFO1在此以前沒有用O_RDONLY方式開啟過,返回ENOXIO錯誤,否則都成功返回。

(3)、對空管道或空FIFO read操作的影響:read(readfd, ...); 如果該readfd對應的FIFO用O_WRONLY開啟返回0,否則返回EAGAIN。

(4)、對write操作的影響同阻塞態下。

6、技巧:單個伺服器多個客戶時候,伺服器中有連續的兩行代碼:
readfifo = open(SERV_FIFO, O_RDONLY, 0);
dummy = open(SERV_FIFO, O_WRONLY, 0);
作用:a、伺服器運行到readfifo,阻塞,直到有客戶用O_WRONLY開啟SERV_FIFO為止。然後因為SERV_FIFO已經以O_RDONLY開啟,因此這個dummy成功返回。
b、到客戶完成任務,關閉SERV_FIFO時,SERV_FIFO變成空,因此,伺服器在運行到read語句的時候,阻塞知道下一個客戶以O_WRONLY方式開啟SERV_FIFO為止(也就是直到有下一個使用者請求為止)

7、Dos攻擊(拒絕服務型攻擊)
有見上面的阻塞,有個攻擊方法就是說,發送一條請求,但是從來不開啟自己的FiFO,讓伺服器死等。伺服器一直阻塞,沒辦法用了。

當然,現在的伺服器都是並發性伺服器,最多隻能阻塞他的一個子進程;但是即使在這種情況下一樣可以進行Dos攻擊,方法是發送大量的請求,以至於伺服器開闢的進程達到極限,而且每個子進程都用上面的辦法來阻塞。(此時不但伺服器處理序被阻塞了,整個伺服器系統都被阻塞了)。

8、位元組流
管道和FIFO是以位元組流的方式來傳遞資訊的,類似於TCP。那麼怎樣區分資訊的界限呢?有下面三種常用的技巧:

a、帶內特殊終止符:分隔標誌(類似與SLIP的標記)
b、顯示長度在資訊頭:像TCP協議棧實現。
c、每次串連一個記錄:像HTTP1.0協議實現。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.