詳見 https://blog.csdn.net/fengel_cs/article/details/46894605
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 << "-af volume=10"
-
- args<<musicFile;
- mplayerProcess->setProcessChannelMode(QProcess::MergedChannels);
-
- mplayerProcess ->start(mplayerPath,args);
- 第一行指明了所要調用的外部應用程序mplayer的位置。
- 第二行指明了所要播放的聲音文件及目錄路徑。
- 第三行創建一個指向類 QProcess的指針。
- 第四到第九行指定mplayer參數,具體參數可以查看 maplayer參數介紹 。
- -slave參數表示打開slave模式. 這用來將MPlayer作為其它程序的后端. MPlayer將從他的標准輸入讀取簡單命令行, 而不再截獲鍵盤事件. SLAVE模式協議部分將解釋其語法。
- -quiet顯示較少的輸出和狀態信息 。
-
- -wid可以為mplayer指定輸出窗口。
-
第十一行為啟動外部應用程序mplayer。內核中管道及通信環境的建立都是在此步中完成的。
mplayer在slave模式下運行會自動從標准輸入中讀取信息並執行。由QProcess的通信原理可知,管道的讀端描述符 stdinChannelpipe[0]復制給了標准輸入,即標准輸入的描述符也為stdinChannelpipe[0],因此按照標准輸入的描述符去 讀信息就是到stdinChannelpipe所對應的管道中讀取信息。所以如果想在QT的主進程中發送命令使mplayer退出,只需在主程序中向 stdinChannelpipe[1]端寫入命令quit就可以,執行語句為myProcess->write(”quit ”);(此處的 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 ");
- 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類的實現機制。
- -quiet顯示較少的輸出和狀態信息 。
- -slave參數表示打開slave模式. 這用來將MPlayer作為其它程序的后端. MPlayer將從他的標准輸入讀取簡單命令行, 而不再截獲鍵盤事件. SLAVE模式協議部分將解釋其語法。
- 第四到第九行指定mplayer參數,具體參數可以查看 maplayer參數介紹 。
- 第三行創建一個指向類 QProcess的指針。
- 第二行指明了所要播放的聲音文件及目錄路徑。
- 第一行指明了所要調用的外部應用程序mplayer的位置。
- mplayerProcess ->start(mplayerPath,args);