QT進程間通信 詳細介紹
1、QT通信機制
為了更好的實現(xiàn)QT的信息交互,在QT系統(tǒng)中創(chuàng)建了較為完善的通信機制。QT的通信可分為QT內(nèi)部通信和外部通信兩大類。對于這兩類通信機制及應用場合做如以下分析:
(1)QT內(nèi)部對象間通信
在圖形用戶界面編程中,經(jīng)常需要將一個窗口部件的變化通知給窗口的其它部件使其產(chǎn)生相應的變化。對于這種內(nèi)部對象間的通信,QT主要采用了信號和槽的機制。這種機制是QT區(qū)別于其他GUI工具的核心機制。在大部分的GUI工具中,通常為可能觸發(fā)的每種行為通過定義回調(diào)函數(shù)來實現(xiàn)。這種回調(diào)函數(shù)是一個指向函數(shù)的指針,在進行函數(shù)回調(diào)執(zhí)行時不能保證所傳遞的函數(shù)參數(shù)類型的正確性,因此容易造成進程的崩潰。
在QT中,信號和槽的機制取代了這種繁雜的、易崩潰的對象通信機制。信號是當對象狀態(tài)改變時所發(fā)出的。槽是用來接收發(fā)射的信號并響應相應事件的類的成員函數(shù)。信號和槽的連接是通過connect()函數(shù)來實現(xiàn)的。例如,實現(xiàn)單擊按鈕終止應用程序運行的代碼connect(button , SIGNAL(clicked()) , qApp , SLOT(quit()) );實現(xiàn)過程就是一個button被單擊后會激發(fā)clicked信號,通過connect()函數(shù)的連接qApp會接收到此信號并執(zhí)行槽函數(shù)quit()。在此過程中,信號的發(fā)出并不關心什么樣的對象來接收此信號,也不關心是否有對象來接收此信號,只要對象狀態(tài)發(fā)生改變此信號就會發(fā)出。此時槽也并不知曉有什么的信號與自己相聯(lián)系和是否有信號與自己聯(lián)系,這樣信號和槽就真正的實現(xiàn)了程序代碼的封裝,提高了代碼的可重用性。同時,信號和槽的連接還實現(xiàn)了類型的安全性,如果類型不匹配,它會以警告的方式報告類型錯誤,而不會使系統(tǒng)產(chǎn)生崩潰。
(2)QT與外部設備間通信
QT與外部通信主要是將外部發(fā)來的消息以事件的方式進行接收處理。外部設備將主要通過socket與QT應用程序進行連接。在此,以輸入設備與QT應用程序的通信為例說明QT與外部通信的原理。
在QT的應用程序開始運行時,主程序?qū)⑼ㄟ^函數(shù)調(diào)用來創(chuàng)建并啟動qwsServer服務器,然后通過socket建立該服務器與輸入硬件設備的連接。服務器啟動后將會打開鼠標與鍵盤設備,然后將打開的設備文件描述符fd連接到socket上。等到QT應用程序進入主事件循環(huán)時,事件處理程序?qū)⑼ㄟ^Linux系統(tǒng)的select函數(shù)來檢測文件描述符fd的狀態(tài)變化情況以實現(xiàn)對socket的監(jiān)聽。如果文件描述符fd狀態(tài)改變,說明設備有數(shù)據(jù)輸入。此時,事件處理程序?qū)l(fā)出信號使設備輸入的數(shù)據(jù)能及時得到QT應用程序的響應。數(shù)據(jù)進入服務器內(nèi)部就會以事件的形式將數(shù)據(jù)放入事件隊列里,等待QT客戶應用程序接收處理。處理結束后再將事件放入請求隊列里,通過服務器將事件發(fā)送到相應硬件上,完成外部輸入設備與QT應用程序的整個通信過程。
2、 QProcess機制分析
QProcess類通常是被用來啟動外部程序,并與它們進行通信的。QProcess是把外部進程看成是一個有序的I/O設備,因此可通過write()函數(shù)實現(xiàn)對進程標準輸入的寫操作,通過read(),readLine()和getChar()函數(shù)實現(xiàn)對標準輸出的讀操作。
(1) QProcess通信機制
QT可以通過QProcess類實現(xiàn)前端程序?qū)ν獠繎贸绦虻恼{(diào)用。這個過程的實現(xiàn)首先是將前端運行的程序看成是QT的主進程,然后再通過創(chuàng)建主進程的子進程來調(diào)用外部的應用程序。這樣QProcess的通信機制就抽象為父子進程之間的通信機制。QProcess在實現(xiàn)父子進程間的通信過程中是運用Linux系統(tǒng)的無名管道來實現(xiàn)的,因此為了能更加清楚的說明QProcess的通信機制,在此首先介紹關于無名管道實現(xiàn)父子進程間的通信機制。
無名管道是一種只能夠在同族父子之間通信,并且在通信過程中,只能從固定的一端寫,從另一端讀的單向的通信方式。該無名管道是通過調(diào)用pipe()函數(shù)而創(chuàng)建的。創(chuàng)建代碼如下:
- #include <unistd.h>
- int pipe(int fd[2]) ;
- 返回:若成功則為0,若出錯則為-1
創(chuàng)建后經(jīng)參數(shù)fd返回兩個文件描述符:fd[0]為讀而打開,fd[1]為寫而打開。經(jīng)過fork()函數(shù)創(chuàng)建其子進程后,子進程將擁有與父進程相同的兩個文件描述符。如果想要實現(xiàn)父進程向子進程的通信則關閉父進程的讀端fd[0],同時關閉子進程的寫端fd[1]。這樣就建立了從父進程到子進程的通信連接。
由于無名管道的單向通信性,所以如果要應用無名管道實現(xiàn)父子進程之間的雙向通信則至少需要應用雙管道進行通信。QProcess類的通信原理就是利用多管道實現(xiàn)了父子進程之間的通信。然而對于外部運行的應用程序大都是通過標準輸入而讀得信息,通過標準輸出而發(fā)送出信息,因此只通過建立管道并不能完成內(nèi)外進程?之間的通信。要解決此問題,就如該模塊開始時所說,QProcess是把外部進程看成是一個I/O設備,然后通過對I/O設備的讀寫來完成內(nèi)外進程的通信。
在QProcess中父子進程之間是通過管道連接的,要實現(xiàn)子進程能從標準輸入中讀得父進程對管道的寫操作,同時父進程能從管道中讀得子進程對標準輸出或標準容錯的寫操作,就要在子進程中將管道的讀端描述符復制給標準輸入端,將另外管道的寫端描述符復制給標準輸出端和標準容錯端,即實現(xiàn)管道端口地址的重定向。這樣子進程對標準輸入、標準輸出及標準容錯的操作就反應到了管道中。
QProcess在正常渠道模式下具體實現(xiàn)共用了五個無名管道進行通信。五個管道的描述符分別用childpipe[2],stdinChannelpipe[2],stdoutChannelpipe[2],stderrChannelpipe[2]和deathpipe[2]五個數(shù)組來保存。deathpipe指代的管道會用在消亡的子進程與父進程之間。當子進程準備撤銷時會發(fā)送一個表示該子進程消亡的字符給父進程來等待父進程進行處理。stdinChannelpipe,stdoutChannelpipe和stderrChannelpipe所指代的管道分別與標準輸入,標準輸出和標準容錯進行綁定,實現(xiàn)了與外部程序的通信。childpipe指代的管道主要是為父子進程之間的通信而建立的。
如果在管道中有新數(shù)據(jù)寫入,就會通知相應進程去讀。另外圖2是QProcess在正常渠道模式下的通信原理圖,如果是在融合渠道模式下,將沒有容錯管道,此時原理圖中將沒有***個管道,也就不會有管道描述符。同時,標準容錯端和標準輸出端將共同掛接到子進程的stdoutChannelpipe的寫端,來實現(xiàn)內(nèi)外進程的通信。
(2) QProcess應用方式
由于QProcess類實現(xiàn)了對底層通信方式較為完善的封裝,因此利用QProcess類將更為方便的實現(xiàn)對外部應用程序的調(diào)用。在此,通過在QT界面中調(diào)用外部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);
***行指明了所要調(diào)用的外部應用程序mplayer的位置。第二行指明了所要播放的歌曲文件及地址。第五行設置mplayer為后臺模式。在此模式下,mplayer將從標準輸入中讀得信息,并通過標準輸出向主進程發(fā)送信息。六七行為mplayer運行的參數(shù)。第九行為設置進程渠道的模式為融合模式,即將標準輸出和標準容錯綁定到同一個管道的寫端。第十行為啟動外部應用程序mplayer。內(nèi)核中管道及通信環(huán)境的建立都是在此步中完成的。
mplayer在slave模式下運行會自動從標準輸入中讀取信息并執(zhí)行。由QProcess的通信原理可知,管道的讀端描述符stdinChannelpipe[0]復制給了標準輸入,即標準輸入的描述符也為stdinChannelpipe[0],因此按照標準輸入的描述符去讀信息就是到stdinChannelpipe所對應的管道中讀取信息。所以如果想在QT的主進程中發(fā)送命令使mplayer退出,只需在主程序中向stdinChannelpipe[1]端寫入命令quit就可以,執(zhí)行語句為myProcess->write(”quit\n”);(此處的write()函數(shù)為QProcess類的成員函數(shù),具體實現(xiàn)就是向stdinChannelpipe[1]端寫入信息)
(3)QProcess的發(fā)展及分析
QProcess類伴隨著QT/Embedded的發(fā)展逐漸趨于完善。在QTE2及其更前版本中還沒有QProcess類,如果想實現(xiàn)與外部應用程序的通信,必須要自己實現(xiàn)對管道或socket的建立與重定向。到了QTE3版本,就實現(xiàn)了對QProcess類的封裝。在QTE3的版本中,QProcess類的實現(xiàn)是通過應用socket來建立主進程與外部應用程序之間通信的。通信原理與圖3所示基本相同,只是將圖中的管道描述符改為是socket的描述符即可。QT主程序在建立成對socket描述符時需要調(diào)用Linux系統(tǒng)函數(shù)socketpair()。在生成的成對socket描述符之間可以實現(xiàn)父子進程之間的雙向通信,即無論是socket的0套接口還是1套接口都可進行讀寫。
但為了避免出現(xiàn)通信過程中父子進程對同一個socket的爭奪,例如,在子進程還未將父進程發(fā)送的信息全部讀出時,子進程又要求將自己產(chǎn)生的數(shù)據(jù)返回給父進程。如果父子進程雙向通信只用一個socket來完成,就會出現(xiàn)父子進程發(fā)送的信息混亂情況。因此,對于QProcess的實現(xiàn)仍然必須通過多個socket來共同完成。
由上面的描述可知,盡管socket有雙向通信功能,但在實現(xiàn)QProcess過程中只是利用socket實現(xiàn)了單向通信功能。因此既浪費了對資源的利用又增加了系統(tǒng)的開銷。為了解決此問題,QTE4版本將QProcess的通信連接方式由socket改為了只能實現(xiàn)單向通信的無名管道來實現(xiàn)。通信原理就是以上3.1 QProcess通信機制中所描述的。
3、其它通信方式
除了上面介紹的無名管道和socket通信方式外,一般操作系統(tǒng)中常用的進程間通信機制也都可以用于QT系統(tǒng)內(nèi)部不同進程之間的通信,如消息隊列、共享內(nèi)存、信號量、有名管道等機制。其中信號量機制在QT中已經(jīng)重新進行了封裝;有些機制則可以直接通過操作系統(tǒng)的系統(tǒng)調(diào)用來實現(xiàn)。另外,如果我們只是想通過管道或socket來實現(xiàn)較簡單的外部通信,也可以重新創(chuàng)建管道或socket來實現(xiàn)自己要求的功能。例如,還是在QT主程序中調(diào)用外部mplayer。如果我們只是想在QT主程序中控制mplayer,而不要求得到mplayer輸出的信息。則可以按照以下方式來實現(xiàn):
- 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類實現(xiàn)調(diào)用mplayer一樣,是用來指明mplayer運行時參數(shù)的。第10行是創(chuàng)建一個管道。第12行是創(chuàng)建一個子進程。15,20行是關閉父子進程中沒用的管道描述符。此時可結合圖2.1和圖2.2來理解從父進程到子進程通信環(huán)境的建立。第16行是把子進程的讀端與標準輸入綁定,以便mplayer能夠接收到父進程發(fā)出的命令。17行就是從子進程中調(diào)用外部mplayer的實現(xiàn)。此時,程序執(zhí)行后,mplayer就可以運行起來。如果想在QT主程序中通過發(fā)送命令使mplayer退出,就在管道的寫端寫入命令"quit"就可以。實現(xiàn)語句為write(fd[1], "quit",strlen("quit"));
該例子說明了QT通信方式運用的靈活性,可以根據(jù)實際情況進行應用。同時該例子的實現(xiàn)方式正是利用了QProcess類實現(xiàn)的機制,因此可以結合這個例子更加深刻的理解QProcess類的實現(xiàn)機制。
小結:QT進程間通信 的內(nèi)容介紹完了,希望本文對你也剖幫助,其實里面有很多內(nèi)容是我們在學習過程蘇接觸到的!