php超詳細(xì)講解命名管道
進(jìn)程間為什么要通信
進(jìn)程間通信的目的:
- 數(shù)據(jù)傳輸:一個 進(jìn)程需要將它的數(shù)據(jù) 發(fā)送給另一個進(jìn)程。
- 通知事件:一個進(jìn)程需要向另一個或一組進(jìn)程 發(fā)送消息,通知它(它們)發(fā)生了 某種事件(如進(jìn)程終止時要通知父進(jìn)程)。
- 資源共享:多個進(jìn)程之間 共享同樣的資源 。為了做到這一點,需要內(nèi)核提供互斥和同步機制。
- 進(jìn)程控制:有些進(jìn)程 希望完全控制另一個進(jìn)程的執(zhí)行 (如 Debug 進(jìn)程),此時控制進(jìn)程希望能夠攔截另一個進(jìn)程的所有狀態(tài)信息
進(jìn)程不是孤立的,一個足夠大的項目絕對不是單一的進(jìn)程可以支撐的起的。所以我們需要進(jìn)程間通信,來滿足不同進(jìn)程間信息交互與傳遞的需求。
進(jìn)程間通信本質(zhì)上是進(jìn)程與進(jìn)程之間交換數(shù)據(jù)的手段。
進(jìn)程如何實現(xiàn)通信
每個進(jìn)程的用戶地址空間都是獨立的(進(jìn)程通過虛擬內(nèi)存地址達(dá)到進(jìn)程相互隔離),一般而言是不能互相訪問的,進(jìn)程之間要通信必須通過內(nèi)核,也就是說操作內(nèi)核提供一個緩沖區(qū),用戶進(jìn)程操作這個緩沖區(qū)【讀寫數(shù)據(jù)】來實現(xiàn)通信。
常見進(jìn)程通信方式
今天我們要學(xué)習(xí)的是管道中的命名管道
管道概念
其實管道一共有兩種,一種是匿名管道,它的特點是只能用于具有共同祖先的進(jìn)程(具有親緣關(guān)系的進(jìn)程)之間進(jìn)行通信;比如父子進(jìn)程,通常,一個匿名管道由一個進(jìn)程創(chuàng)建,然后該進(jìn)程調(diào)用fork,此后父、子進(jìn)程之間就可應(yīng)用該管道。
對于匿名管道,它的通信范圍是存在父子關(guān)系的進(jìn)程。因為管道沒有實體,也就是沒有管道文件,只能通過 fork 來復(fù)制父進(jìn)程 fd 文件描述符,來達(dá)到通信的目的。
第二種是命名管道可以實現(xiàn)兩個不相關(guān)進(jìn)程之間進(jìn)行數(shù)據(jù)交互,也能實現(xiàn)父子進(jìn)程之間數(shù)據(jù)交互。命名管道是一種特殊類型的文件,有實體
對于命名管道,它可以在不相關(guān)的進(jìn)程間也能相互通信。因為命名管道,提前創(chuàng)建了一個類型為管道的設(shè)備文件,在進(jìn)程里只要使用這個設(shè)備文件,就可以相互通信。
由于php擴展并沒有提供匿名管道的封裝,只提供了命名管道的,所有我們先不講匿名管道,有興趣的自行了解
命名管道實現(xiàn)
不管是匿名管道還是命名管道,進(jìn)程寫入的數(shù)據(jù)都是緩存在內(nèi)核中,另一個進(jìn)程讀取數(shù)據(jù)時候自然也是從內(nèi)核中獲取,同時通信數(shù)據(jù)都遵循先進(jìn)先出原則,類似于隊列
posix_mkfifo函數(shù)
php 通過 posix_mkfifo 函數(shù)創(chuàng)建命名管道文件
<?php //定義管道文件 $file = 'fifo_dalei'; //posix_access() 函數(shù)檢測管道文件是否存在,如果不存在使用posix_mkfifo() 函數(shù)創(chuàng)建一個命名管道文件 if(!posix_access($file, POSIX_F_OK)){ if(posix_mkfifo($file,0666)){ echo "命名管道創(chuàng)建成功\n"; } }
執(zhí)行代碼我們就得到了創(chuàng)建出來的管道文件 fifo_dalei
,我們查看文件權(quán)限位以 p
開頭就表示這個文件是一個管道文件,當(dāng)然我們也可以使用 file
命令查看fifo_dalei
文件類型是管道類型
我們可以直接在終端操作讀寫這個命名管道文件
比如:我們打開兩個終端,A終端與B終端,A終端負(fù)責(zé)讀命名管道中的數(shù)據(jù),B終端負(fù)責(zé)往命名管道寫入數(shù)據(jù)
我們通過 cat
命令 讀取命名管道,如果命名管道沒有數(shù)據(jù) cat fifo_dalei
命令會阻塞住,直到有數(shù)據(jù)寫入命名管道,cat fifo_dalei
才會輸出數(shù)據(jù)內(nèi)容并結(jié)束,反過來如果先將數(shù)據(jù)寫入命名管道,卻沒有另一個進(jìn)程讀取命名管道的內(nèi)容,寫入命令依然會阻塞。
由此可以發(fā)現(xiàn),管道這種通信方式效率低,不適合進(jìn)程間頻繁地交換數(shù)據(jù)。當(dāng)然,它的好處,自然就是簡單,同時也我們很容易得知管道里的數(shù)據(jù)已經(jīng)被另一個進(jìn)程讀取了。
那么在php中我們?nèi)绾问褂妹艿牢募?,下面我們以父子進(jìn)程通信為例
<?php // 定義命名管道文件 $file = 'fifo_dalei'; // posix_access 函數(shù)檢測當(dāng)前文件是否存在 if(!posix_access($file, POSIX_F_OK)){ //posix_mkfifo 函數(shù)創(chuàng)建命名管道 if(posix_mkfifo($file,0666)){ echo "命名管道創(chuàng)建成功\n"; } } // fork 創(chuàng)建子進(jìn)程 $pid = pcntl_fork(); if($pid == 0){//子進(jìn)程執(zhí)行邏輯 // 用讀方式打開命名管道,需要注意的是如果命名管道內(nèi)沒有數(shù)據(jù),fopen 函數(shù)會阻塞 $fd = fopen($file, 'r'); // fread 函數(shù) 讀取命名管道,10個字節(jié)長度數(shù)據(jù) $data = fread($fd,10); if($data){ fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data); } exit(0); } // 用寫方式打開命名管道 $fd = fopen($file, 'w'); // 往命名管道寫入數(shù)據(jù) 'dalei' 寫入的長度為5 $len = fwrite($fd,'dalei',5); fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len); // 關(guān)閉管道文件 fclose($fd); //回收執(zhí)行完畢退出子進(jìn)程,防止僵尸進(jìn)程 $pid = pcntl_wait($status); if($pid > 0){ fprintf(STDOUT,"exit process pid = %d\n",$pid); }
分析上面代碼,首先父進(jìn)程32023 寫入數(shù)據(jù)dalei
到命名管道,子進(jìn)程 32024 讀取命名管道中父進(jìn)程寫入的數(shù)據(jù),然后 32024 子進(jìn)程退出。
需要注意的是 fopen()
函數(shù)打開命名管道必須讀端寫端都打開,不然 fopen
() 函數(shù)會阻塞
上面我們通過命令操作管道發(fā)現(xiàn),管道內(nèi)必須有內(nèi)容,才能讀取管道,不然會阻塞,php操作管道也同樣有這個問題,看下面代碼,我們通過 while
不停循環(huán)讀取管道內(nèi)容,但是寫數(shù)據(jù)端寫入一次會發(fā)生什么事情呢?
<?php // 定義命名管道文件 $file = 'fifo_dalei'; // posix_access 函數(shù)檢測當(dāng)前文件是否存在 if(!posix_access($file, POSIX_F_OK)){ //posix_mkfifo 函數(shù)創(chuàng)建命名管道 if(posix_mkfifo($file,0666)){ echo "命名管道創(chuàng)建成功\n"; } } // fork 創(chuàng)建子進(jìn)程 $pid = pcntl_fork(); if($pid == 0){//子進(jìn)程執(zhí)行邏輯 // 用讀方式打開命名管道,需要注意的是如果命名管道內(nèi)沒有數(shù)據(jù),fopen 函數(shù)會阻塞 $fd = fopen($file, 'r'); while(1){ // fread 函數(shù) 讀取命名管道,10個字節(jié)長度數(shù)據(jù) $data = fread($fd,10); if($data){ fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data); } echo "hello\n"; sleep(2); } exit(0); } // 用寫方式打開命名管道 $fd = fopen($file, 'w'); // 往命名管道寫入數(shù)據(jù) 'dalei' 寫入的長度為5 $len = fwrite($fd,'dalei',5); fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len); // 關(guān)閉管道文件 #fclose($fd); //回收執(zhí)行完畢退出子進(jìn)程,防止僵尸進(jìn)程 $pid = pcntl_wait($status); if($pid > 0){ fprintf(STDOUT,"exit process pid = %d\n",$pid); }
通過運行上面代碼,當(dāng)父進(jìn)程只寫入一次,子進(jìn)程循環(huán)讀,由于命名管道內(nèi)無數(shù)據(jù)會造成 fread()
函數(shù)阻塞,無法往下執(zhí)行也就無法打印出 hello
,如何以非阻塞的方式讀取數(shù)據(jù)呢?
下面我們就來介紹 stream_set_blocking()
函數(shù)實現(xiàn)非阻塞讀命名管道,即命名管道無數(shù)據(jù)立即返回,并不會阻塞在fread()
函數(shù)
<?php // 定義命名管道文件 $file = 'fifo_dalei'; // posix_access 函數(shù)檢測當(dāng)前文件是否存在 if(!posix_access($file, POSIX_F_OK)){ //posix_mkfifo 函數(shù)創(chuàng)建命名管道 if(posix_mkfifo($file,0666)){ echo "命名管道創(chuàng)建成果\n"; } } // fork 創(chuàng)建子進(jìn)程 $pid = pcntl_fork(); if($pid == 0){//子進(jìn)程執(zhí)行邏輯 // 用讀方式打開命名管道,需要注意的是如果命名管道內(nèi)沒有數(shù)據(jù),fopen 函數(shù)會阻塞 $fd = fopen($file, 'r'); //設(shè)置非阻塞讀取命名管道 stream_set_blocking($fd,0); while(1){ // fread 函數(shù) 讀取命名管道,10個字節(jié)長度數(shù)據(jù) $data = fread($fd,10); if($data){ fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data); } echo "hello\n"; sleep(2); } exit(0); } // 用寫方式打開命名管道 $fd = fopen($file, 'w'); // 往命名管道寫入數(shù)據(jù) 'dalei' 寫入的長度為5 $len = fwrite($fd,'dalei',5); fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len); // 關(guān)閉管道文件 #fclose($fd); //回收執(zhí)行完畢退出子進(jìn)程,防止僵尸進(jìn)程 $pid = pcntl_wait($status); if($pid > 0){ fprintf(STDOUT,"exit process pid = %d\n",$pid); }
還有個需要注意的知識點,當(dāng)寫端寫入數(shù)據(jù)的過程中,如果讀端退出,寫入數(shù)據(jù)將失敗,并且產(chǎn)生中斷信號 SIGPIPE
, 下面是實驗代碼
<?php // 定義命名管道文件 $file = 'fifo_dalei'; // posix_access 函數(shù)檢測當(dāng)前文件是否存在 if(!posix_access($file, POSIX_F_OK)){ //posix_mkfifo 函數(shù)創(chuàng)建命名管道 if(posix_mkfifo($file,0666)){ echo "命名管道創(chuàng)建成果\n"; } } // 安裝信號處理器,處理捕獲信號 pcntl_signal(SIGPIPE,function($signo){ fprintf(STDOUT,"signo=%d\n",$signo); }); // fork 創(chuàng)建子進(jìn)程 $pid = pcntl_fork(); if($pid == 0){//子進(jìn)程執(zhí)行邏輯 // 用讀方式打開命名管道,需要注意的是如果命名管道內(nèi)沒有數(shù)據(jù),fopen 函數(shù)會阻塞 $fd = fopen($file, 'r'); //設(shè)置非阻塞讀取命名管道 stream_set_blocking($fd,0); $i = 0; while(1){ // fread 函數(shù) 讀取命名管道,10個字節(jié)長度數(shù)據(jù) $data = fread($fd,10); if($data){ $i++; if($i > 2){ fclose($fd); break; } fprintf(STDOUT,"read process pid = %d, recv:%s\n",getmypid(),$data); } sleep(2); } exit(0); } // 用寫方式打開命名管道 $fd = fopen($file, 'w'); stream_set_blocking($fd,0); while(1){ // 信號分發(fā)(沒有這個函數(shù),信號無法被捕獲) pcntl_signal_dispatch(); // 往命名管道寫入數(shù)據(jù) 'dalei' 寫入的長度為5 $len = fwrite($fd,'dalei',5); fprintf(STDOUT,"write process pid = %d, write len=%d\n",posix_getpid(),$len); sleep(2); } // 關(guān)閉管道文件 fclose($fd); //回收執(zhí)行完畢退出子進(jìn)程,防止僵尸進(jìn)程 $pid = pcntl_wait($status); if($pid > 0){ fprintf(STDOUT,"exit process pid = %d\n",$pid); }
通過分析代碼我們得知,我們定義了一個 $i 變量
用于累加,當(dāng)累加數(shù)大于2,也就是read
執(zhí)行超過兩次后讀進(jìn)程退出,寫進(jìn)程write
將無法再向命名管道寫入數(shù)據(jù),并且產(chǎn)生信號數(shù)為13的中斷信號
使用 kill -l
查看linux 中所有信號
無血緣進(jìn)程間通信
寫端
<?php $file = 'fifo_dalei'; if(!posix_access($file,POSIX_F_OK)){ if(!posix_mkfifo($file,0666)){ } } $fd = fopen($file,'w'); while(1){ //獲取終端輸入數(shù)據(jù),大小限制1280字節(jié) $data = fgets(STDIN,1280); // 寫入數(shù)據(jù)到命名管道,數(shù)據(jù)長度限制為10個字節(jié) $len = fwrite($fd,$data,10); fprintf(STDOUT,"pid=%d, write len = %d\n",posix_getpid(),$len); } fclose($fd);
讀端 (一定要設(shè)置非阻塞讀,不然寫端,寫入數(shù)據(jù)讀端無法讀取,只有寫端進(jìn)程退出才,讀端才能全部讀取出來)
<?php $file = 'fifo_dalei'; if(!posix_access($file,POSIX_F_OK)){ if(posix_mkfifo($file,0666)){ } } $fd = fopen($file,'r'); //設(shè)置非阻塞讀 stream_set_blocking($fd,0); while(1){ //讀取命名管道內(nèi)容,讀取長度限制為128字節(jié) $data = fread($fd,128); if($data){ fprintf(STDOUT,"pid=%d,data=%s\n",posix_getpid(),$data); } } fclose($fd);
通過上面的學(xué)習(xí),我們已經(jīng)了解了命名管道的用法與實現(xiàn),當(dāng)然大家還可以自己動手嘗試編寫命名管道的多種組合方式。比如說,多個讀端,一個寫端,讀端是同時能讀取到寫入內(nèi)容還是,一個能獲取,一個獲取不到,又或者多個寫端一個讀端會發(fā)生什么情況,這些大家都可以自己實現(xiàn)看看,畢竟實踐出真知,不動手只聽別人說可不行哦!
到此這篇關(guān)于php超詳細(xì)講解命名管道的文章就介紹到這了,更多相關(guān)php命名管道內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PHP中使用json數(shù)據(jù)格式定義字面量對象的方法
這篇文章主要介紹了PHP中使用json數(shù)據(jù)格式定義字面量對象的方法,這是一種變通方法,使用json還可以在類中生成數(shù)組哦,需要的朋友可以參考下2014-08-08簡單介紹win7下搭建apache+php+mysql開發(fā)環(huán)境
這里給大家介紹的是Win7下搭建“PHP+Apache+MySql”網(wǎng)站運行環(huán)境詳細(xì)方法步驟,十分的細(xì)致全面,有需要的小伙伴可以參考下。2015-08-08thinkphp實現(xiàn)把數(shù)據(jù)庫中的列的值存到下拉框中的方法
本文主要介紹了thinkphp把數(shù)據(jù)庫中的列的值存到下拉框中的方法。具有一定的參考價值,下面跟著小編一起來看下吧2017-01-01