Qt進程間的通信

文章來源:http://mobile.51cto.com/symbian-270726.htm

 

在QT中,信號和槽的機制取代了這種繁雜的、易崩潰的對象通信機制。信號是當對象狀態改變時所發出的。槽是用來接收發射的信號並響應相應事件的類的成員函數。信號和槽的連接是通過connect()函數來實現的。

 

1、QT通信機制

爲了更好的實現QT的信息交互,在QT系統中創建了較爲完善的通信機制。QT通信可分爲QT內部通信和外部通信兩大類。對於這兩類通信機制及應用場合做如以下分析:

(1)QT內部對象間通信

在圖形用戶界面編程中,經常需要將一個窗口部件的變化通知給窗口的其它部件使其產生相應的變化。對於這種內部對象間的通信QT主要採用了信號的機制。這種機制是QT區別於其他GUI工具的核心機制。在大部分的GUI工具中,通常爲可能觸發的每種行爲通過定義回調函數來實現。這種回調函數是一個指向函數的指針,在進行函數回調執行時不能保證所傳遞的函數參數類型的正確性,因此容易造成進程的崩潰。

QT中,信號的機制取代了這種繁雜的、易崩潰的對象通信機制。信號是當對象狀態改變時所發出的。是用來接收發射的信號並響應相應事件的類的成員函數。信號和槽的連接是通過connect()函數來實現的。例如,實現單擊按鈕終止應用程序運行的代碼connect(button , SIGNAL(clicked()) , qApp , SLOT(quit()) );實現過程就是一個button被單擊後會激發clicked信號,通過connect()函數的連接qApp會接收到此信號並執行槽函數quit()。在此過程中,信號的發出並不關心什麼樣的對象來接收此信號,也不關心是否有對象來接收此信號,只要對象狀態發生改變此信號就會發出。此時槽也並不知曉有什麼的信號與自己相聯繫和是否有信號與自己聯繫,這樣信號和槽就真正的實現了程序代碼的封裝,提高了代碼的可重用性。同時,信號和槽的連接還實現了類型的安全性,如果類型不匹配,它會以警告的方式報告類型錯誤,而不會使系統產生崩潰。

(2)QT與外部設備間通信

QT與外部通信主要是將外部發來的消息以事件的方式進行接收處理。外部設備將主要通過socket與QT應用程序進行連接。在此,以輸入設備與QT應用程序的通信爲例說明QT與外部通信的原理。

在QT的應用程序開始運行時,主程序將通過函數調用來創建並啓動qwsServer服務器,然後通過socket建立該服務器與輸入硬件設備的連接。服務器啓動後將會打開鼠標與鍵盤設備,然後將打開的設備文件描述符fd連接到socket上。等到QT應用程序進入主事件循環時,事件處理程序將通過Linux系統的select函數來檢測文件描述符fd的狀態變化情況以實現對socket的監聽。如果文件描述符fd狀態改變,說明設備有數據輸入。此時,事件處理程序將會發出信號使設備輸入的數據能及時得到QT應用程序的響應。數據進入服務器內部就會以事件的形式將數據放入事件隊列裏,等待QT客戶應用程序接收處理。處理結束後再將事件放入請求隊列裏,通過服務器將事件發送到相應硬件上,完成外部輸入設備與QT應用程序的整個通信過程。

2、 QProcess機制分析

QProcess類通常是被用來啓動外部程序,並與它們進行通信的。QProcess是把外部進程看成是一個有序的I/O設備,因此可通過write()函數實現對進程標準輸入的寫操作,通過read(),readLine()和getChar()函數實現對標準輸出的讀操作。

(1) QProcess通信機制

QT可以通過QProcess類實現前端程序對外部應用程序的調用。這個過程的實現首先是將前端運行的程序看成是QT的主進程,然後再通過創建主進程的子進程來調用外部的應用程序。這樣QProcess的通信機制就抽象爲父子進程之間的通信機制。QProcess在實現父子進程間的通信過程中是運用Linux系統的無名管道來實現的,因此爲了能更加清楚的說明QProcess的通信機制,在此首先介紹關於無名管道實現父子進程間的通信機制。

無名管道是一種只能夠在同族父子之間通信,並且在通信過程中,只能從固定的一端寫,從另一端讀的單向的通信方式。該無名管道是通過調用pipe()函數而創建的。創建代碼如下:

#include <unistd.h>   
int pipe(int fd[2]) ;  
返回:若成功則爲0,若出錯則爲-1 


創建後經參數fd返回兩個文件描述符:fd[0]爲讀而打開,fd[1]爲寫而打開。經過fork()函數創建其子進程後,子進程將擁有與父進程相同的兩個文件描述符。如果想要實現父進程向子進程的通信則關閉父進程的讀端fd[0],同時關閉子進程的寫端fd[1]。這樣就建立了從父進程到子進程的通信連接。

由於無名管道的單向通信性,所以如果要應用無名管道實現父子進程之間的雙向通信則至少需要應用雙管道進行通信。QProcess類的通信原理就是利用多管道實現了父子進程之間的通信。然而對於外部運行的應用程序大都是通過標準輸入而讀得信息,通過標準輸出而發送出信息,因此只通過建立管道並不能完成內外進程?之間的通信。要解決此問題,就如該模塊開始時所說,QProcess是把外部進程看成是一個I/O設備,然後通過對I/O設備的讀寫來完成內外進程的通信。

在QProcess中父子進程之間是通過管道連接的,要實現子進程能從標準輸入中讀得父進程對管道的寫操作,同時父進程能從管道中讀得子進程對標準輸出或標準容錯的寫操作,就要在子進程中將管道的讀端描述符複製給標準輸入端,將另外管道的寫端描述符複製給標準輸出端和標準容錯端,即實現管道端口地址的重定向。這樣子進程對標準輸入、標準輸出及標準容錯的操作就反應到了管道中。

QProcess在正常渠道模式下具體實現共用了五個無名管道進行通信。五個管道的描述符分別用childpipe[2],stdinChannelpipe[2],stdoutChannelpipe[2],stderrChannelpipe[2]和deathpipe[2]五個數組來保存。deathpipe指代的管道會用在消亡的子進程與父進程之間。當子進程準備撤銷時會發送一個表示該子進程消亡的字符給父進程來等待父進程進行處理。stdinChannelpipe,stdoutChannelpipe和stderrChannelpipe所指代的管道分別與標準輸入,標準輸出和標準容錯進行綁定,實現了與外部程序的通信。childpipe指代的管道主要是爲父子進程之間的通信而建立的。

如果在管道中有新數據寫入,就會通知相應進程去讀。另外圖2是QProcess在正常渠道模式下的通信原理圖,如果是在融合渠道模式下,將沒有容錯管道,此時原理圖中將沒有第一個管道,也就不會有管道描述符。同時,標準容錯端和標準輸出端將共同掛接到子進程的stdoutChannelpipe的寫端,來實現內外進程的通信。

(2) QProcess應用方式

由於QProcess類實現了對底層通信方式較爲完善的封裝,因此利用QProcess類將更爲方便的實現對外部應用程序的調用。在此,通過在QT界面中調用外部mplayer的例子來簡單說明QProcess的應用方式。

const QString mplayerPath("/mnt/yaffs/mplayer");   
const QString musicFile("/mnt/yaffs/music/sound.mp3");  
QProcess* mplayerProcess=new QProcess();  
QStringList args;  
args<<"-slave";  
args<<"-quiet";  
args << "-wid";  
args<<musicFile;  
mplayerProcess->setProcessChannelMode(QProcess::MergedChannels);  
yProcess->start(mplayerPath,args); 


第一行指明瞭所要調用的外部應用程序mplayer的位置。第二行指明瞭所要播放的歌曲文件及地址。第五行設置mplayer爲後臺模式。在此模式下,mplayer將從標準輸入中讀得信息,並通過標準輸出向主進程發送信息。六七行爲mplayer運行的參數。第九行爲設置進程渠道的模式爲融合模式,即將標準輸出和標準容錯綁定到同一個管道的寫端。第十行爲啓動外部應用程序mplayer。內核中管道及通信環境的建立都是在此步中完成的。

mplayer在slave模式下運行會自動從標準輸入中讀取信息並執行。由QProcess的通信原理可知,管道的讀端描述符stdinChannelpipe[0]複製給了標準輸入,即標準輸入的描述符也爲stdinChannelpipe[0],因此按照標準輸入的描述符去讀信息就是到stdinChannelpipe所對應的管道中讀取信息。所以如果想在QT的主進程中發送命令使mplayer退出,只需在主程序中向stdinChannelpipe[1]端寫入命令quit就可以,執行語句爲myProcess->write(”quit\n”);(此處的write()函數爲QProcess類的成員函數,具體實現就是向stdinChannelpipe[1]端寫入信息)

(3)QProcess的發展及分析

QProcess類伴隨着QT/Embedded的發展逐漸趨於完善。在QTE2及其更前版本中還沒有QProcess類,如果想實現與外部應用程序的通信,必須要自己實現對管道或socket的建立與重定向。到了QTE3版本,就實現了對QProcess類的封裝。在QTE3的版本中,QProcess類的實現是通過應用socket來建立主進程與外部應用程序之間通信的。通信原理與圖3所示基本相同,只是將圖中的管道描述符改爲是socket的描述符即可。QT主程序在建立成對socket描述符時需要調用Linux系統函數socketpair()。在生成的成對socket描述符之間可以實現父子進程之間的雙向通信,即無論是socket的0套接口還是1套接口都可進行讀寫。

但爲了避免出現通信過程中父子進程對同一個socket的爭奪,例如,在子進程還未將父進程發送的信息全部讀出時,子進程又要求將自己產生的數據返回給父進程。如果父子進程雙向通信只用一個socket來完成,就會出現父子進程發送的信息混亂情況。因此,對於QProcess的實現仍然必須通過多個socket來共同完成。

由上面的描述可知,儘管socket有雙向通信功能,但在實現QProcess過程中只是利用socket實現了單向通信功能。因此既浪費了對資源的利用又增加了系統的開銷。爲了解決此問題,QTE4版本將QProcess的通信連接方式由socket改爲了只能實現單向通信的無名管道來實現。通信原理就是以上3.1 QProcess通信機制中所描述的。

3、其它通信方式

除了上面介紹的無名管道和socket通信方式外,一般操作系統中常用的進程通信機制也都可以用於QT系統內部不同進程之間的通信,如消息隊列、共享內存、信號量、有名管道等機制。其中信號量機制在QT中已經重新進行了封裝;有些機制則可以直接通過操作系統的系統調用來實現。另外,如果我們只是想通過管道或socket來實現較簡單的外部通信,也可以重新創建管道或socket來實現自己要求的功能。例如,還是在QT主程序中調用外部mplayer。如果我們只是想在QT主程序中控制mplayer,而不要求得到mplayer輸出的信息。則可以按照以下方式來實現:

const char* mplayerPath = "/mnt/yaffs/mplayer";    
const char* musicFile = "/mnt/yaffs/music/sound.mp3";   
const char* arg[5];   
arg[0] = mplayerPath;   
arg[1] = "-slave";   
arg[2] = "-quiet";  
arg[3] = musicFile;  
arg[4] = NULL;  

int fd[2],pid;  
 if(pipe(fd)<0)   
      printf("creating pipe is error\n");   
else 
     while((pid=fork())<0);  

if(pid==0)   
{   
      ::close(fd[1]);   
      ::dup2(fd[0],STDIN_FILENO);  
      execvp(arg[0],(const* char*)arg);   
}   
else
{  
      ::close(fd[0]);
} 


第1到8行與前面QProcess類實現調用mplayer一樣,是用來指明mplayer運行時參數的。第10行是創建一個管道。第12行是創建一個子進程。15,20行是關閉父子進程中沒用的管道描述符。此時可結合圖2.1和圖2.2來理解從父進程到子進程通信環境的建立。第16行是把子進程的讀端與標準輸入綁定,以便mplayer能夠接收到父進程發出的命令。17行就是從子進程中調用外部mplayer的實現。此時,程序執行後,mplayer就可以運行起來。如果想在QT主程序中通過發送命令使mplayer退出,就在管道的寫端寫入命令"quit"就可以。實現語句爲write(fd[1], "quit",strlen("quit"));

該例子說明了QT通信方式運用的靈活性,可以根據實際情況進行應用。同時該例子的實現方式正是利用了QProcess類實現的機制,因此可以結合這個例子更加深刻的理解QProcess類的實現機制。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章