詳解 QT 4中構(gòu)建多線程服務(wù)器
首先說一下對多線程這個(gè)名詞的理解過程。以前聽說過很多次多線程這個(gè)詞,而且往往與服務(wù)器聯(lián)系起來,因此一直把多線程誤解為服務(wù)器特有的功能;直到這次課程設(shè)計(jì),仔細(xì)學(xué)習(xí)了一下多線程的機(jī)制,才知道真正的意思。簡單的來說,就是同時(shí)有多個(gè)線程一起運(yùn)行,而不同的線程可以執(zhí)行不同的操作。舉個(gè)例子,一個(gè)圖像處理工具,可以用鼠標(biāo)一邊移動圖像,一邊用快捷鍵縮放圖像,此時(shí),移動圖像、縮放圖像就是不同的線程來處理的,如果不支持多線程而是單線程的,那么只能挨個(gè)操作了。
而對于服務(wù)器來說,多線程的這個(gè)特性太有用了,因?yàn)?strong>多線程使得服務(wù)器可能同時(shí)響應(yīng)多個(gè)客戶端的請求,所以現(xiàn)在服務(wù)器大多采用多線程,所以才會造成我開始的誤解。
不管是多線程,還是服務(wù)器,QT中已經(jīng)封裝好了特定的類,所以使用起來也很方便。下面建立一個(gè)支持多線程、TCP的服務(wù)器。
首先建立一個(gè)服務(wù)器。新建一個(gè)類(Server)繼承QT中的QTcpServer類即可。服務(wù)器的職責(zé)是監(jiān)聽端口。當(dāng)監(jiān)聽到有客戶端試圖與服務(wù)器建立連接的時(shí)候,分配socket與客戶端連接,再進(jìn)行數(shù)據(jù)通信。QTcpServer的listen()方法執(zhí)行監(jiān)聽過程,可以指定監(jiān)聽的地址和端口。若給定了QHostAddress類型的監(jiān)聽地址,則監(jiān)聽該地址,否則,監(jiān)聽所有地址;若給定了quint16類型的監(jiān)聽端口,則監(jiān)聽該端口,否則,隨機(jī)選定一個(gè)監(jiān)聽端口。
- view plaincopy to clipboardprint?
- Server * server = new Server;
- if(!server->listen(host,port)){
- ...//error
- }
Server * server = new Server;if(!server->listen(host,port)){...//error}QTcpServer有一個(gè)虛函數(shù)incomingConnection(int socketDescriptor),服務(wù)器每當(dāng)監(jiān)聽到一個(gè)客戶端試圖建立連接的時(shí)候,會自動調(diào)用這個(gè)函數(shù),因此,處理這個(gè)請求的過程就可以在這個(gè)函數(shù)中電影,即在子類Server的定義階段,重新定義incomingConnection()這個(gè)函數(shù)。
對于一個(gè)多線程的服務(wù)器,每當(dāng)客戶端試圖連接的時(shí)候,服務(wù)器應(yīng)該啟動一個(gè)線程,負(fù)責(zé)對這個(gè)客戶端進(jìn)行服務(wù),所以,incomingConnection()這個(gè)函數(shù)所要做的就是建立一個(gè)線程,而所建立的線程的作用就是對客戶端進(jìn)行服務(wù),而這其中建立socket連接是基礎(chǔ)。服務(wù)器在監(jiān)聽到客戶端試圖建立socket連接時(shí),會為此socket分配一個(gè)唯一的標(biāo)識socketDescriptor,這個(gè)標(biāo)識將在服務(wù)器端建立socket連接時(shí)使用,所以應(yīng)提供給每一個(gè)線程。
在QT中使用多線程,建立一個(gè)類(Thread)繼承QThread類即可。QThread類也有一個(gè)虛函數(shù),這個(gè)函數(shù)是run(),線程建立并啟動(QThread::start())后,就會執(zhí)行這里面的代碼,因此,線程的邏輯過程就應(yīng)該在run()里面定義。服務(wù)器的線程要根據(jù)socketDescriptor標(biāo)識的socket建立連接,然后進(jìn)行數(shù)據(jù)通信,所以要將socketDescriptor傳入到Thread中,前面說過,線程是在incomingConnection()里面建立,用構(gòu)造函數(shù)將socketDescriptor傳入Thread類,再用socketDescriptor建立socket連接。
定義incomingConnection()
- view plaincopy to clipboardprint?
- void incomingConnection(int socketDescriptor){
- Thread * thread = new Thread(socketDescriptor);
- thread->start();
- }
- void incomingConnection(int socketDescriptor){ Thread * thread = new Thread(socketDescriptor); thread->start();}定義run()
- view plaincopy to clipboardprint?
- void run(){
- QTcpSocket * socket = new QTcpSocket(socketDescriptor);
- ...//數(shù)據(jù)通信
- }
- void run(){ QTcpSocket * socket = new QTcpSocket(socketDescriptor); ...//數(shù)據(jù)通信}自此,一個(gè)簡單的多線程服務(wù)器建立完畢。
寫的不好,望請指教。
QT4中socket通信
最近的軟件工程課程設(shè)計(jì)讓我重新開始使用QT,上次數(shù)據(jù)結(jié)構(gòu)的課程設(shè)計(jì)也是用QT,雖然是做出來了,但是現(xiàn)在想想,那個(gè)時(shí)候?qū)T的理解,或者說得更廣一點(diǎn),對OO的理解,簡直太差勁了,當(dāng)然,人的知識是進(jìn)步的,所以現(xiàn)在有這樣的感受是很正常的。雖然整體的開發(fā)工作還沒有完全結(jié)束,但是已經(jīng)有了很多心得體會,所以特來記錄分享一下。
我們的系統(tǒng)采用的是C/S結(jié)構(gòu),所以客戶端與服務(wù)器通信是最關(guān)鍵,不幸的是,雖然我們沒有用過QT的socket類,我們也沒有估計(jì)好通信的難度,等到意識到第一次使用的困難時(shí),已經(jīng)是第5天了,始終沒有進(jìn)展,我臨危受命?,F(xiàn)在是第6天,剛剛把通信模塊封裝好,算是對這兩天的突擊的一個(gè)回報(bào)。
#p#
我們遇到的問題socket已經(jīng)建立,并且發(fā)送端已經(jīng)將消息發(fā)送,但是接收端始終收不到消息。(我用的socket類型是TCP,也就是QTcpSocket類)
發(fā)送端(發(fā)送端一直不存在問題)代碼如下:
- view plaincopy to clipboardprint?
- ... // 建立連接,客戶端和服務(wù)器端有區(qū)別,在此省略<BR>
- QByteArray block;
- QDataStream out(&block,QIODevice::WriteOnly); // 寫信息至block中,用到QDataStream類<BR>
- socket.write(block);// 信息寫完畢,寫入socket,由socket發(fā)送<BR>
- socket.disconnectFormHost();
- socket.waitForDisconnected();
- ... // 建立連接,客戶端和服務(wù)器端有區(qū)別,在此省略QByteArray block;QDataStream out(&block,QIODevice::WriteOnly);
- // 寫信息至block中,用到QDataStream類socket.write(block);
- // 信息寫完畢,寫入socket,由socket發(fā)送socket.disconnectFormHost();socket.waitForDisconnected();有問題的接收端代碼如下:
- view plaincopy to clipboardprint?
- ... // 建立連接<BR>
- QDataStream in(&socket);// 接收socket中的數(shù)據(jù)流<BR>
- ... // 從數(shù)據(jù)流 in 中讀數(shù)據(jù)
- ... // 建立連接QDataStream in(&socket);// 接收socket中的數(shù)據(jù)流... // 從數(shù)據(jù)流 in 中讀數(shù)據(jù)
以上是最原始的接收和發(fā)送端工作過程,調(diào)試過程中,分別講兩端的socket的狀態(tài)打印出來,結(jié)果是發(fā)送端為A connection is established. 而接收端為The socket has started establishing a connection. 也就是說發(fā)送端正確的建立了連接,并將數(shù)據(jù)寫入,而接收端只是正在建立連接,而并沒有建立好,所以是根本不會受到數(shù)據(jù)的。所以先要確保接收端的連接建立好。waitForConnected()方法就可以解決這個(gè)問題,它將一直等待直到連接已經(jīng)建立。
改進(jìn)后的接收端代碼:
- view plaincopy to clipboardprint?
- ... // 建立連接<BR>
- socket.waitForConnected(5000) // 5000表示等待的時(shí)間,默認(rèn)參數(shù)為3000,單位是百萬分之一秒
- QDataStream in(&socket);// 接收socket中的數(shù)據(jù)流<BR>
- ... // 從數(shù)據(jù)流 in 中讀數(shù)據(jù)
- ... // 建立連接socket.waitForConnected(5000)
- // 5000表示等待的時(shí)間,默認(rèn)參數(shù)為3000,單位是百萬分之一秒QDataStream in(&socket);
- // 接收socket中的數(shù)據(jù)流...
- // 從數(shù)據(jù)流 in 中讀數(shù)據(jù)此時(shí),接收端輸出的socket狀態(tài)為A connection is established,連接成功建立。
但是還是收不到信息,參考了一下別人的程序,再比對一下參考手冊,原來QTcpSocket的爺爺類(其實(shí)是父類QAbstractSocket的父類)QIODevice有一個(gè)readyRead的信號(signal),當(dāng)信息準(zhǔn)備好并可以讀的時(shí)候,這個(gè)信號就將發(fā)出,也就是說,只有當(dāng)這個(gè)信號發(fā)出的時(shí)候,才可以讀消息。所以要把讀消息的動作read作為一個(gè)槽(slot),并將其與readyRead信號連接。
- view plaincopy to clipboardprint?
- connect(&socket,SIGNAL(readyRead()),this,SLOT(read()));
- connect(&socket,SIGNAL(readyRead()),this,SLOT(read()));
但是直接觸發(fā)socket信號,而不用圖形界面的動作來觸發(fā)一個(gè)動作并由這個(gè)動作來觸發(fā)socket信號一直也觸發(fā)不了read這個(gè)動作。但是我要封裝成一個(gè)接口類提供給上層使用,用圖形界面自然是不現(xiàn)實(shí)的,于是翻閱了手冊,發(fā)現(xiàn)了一個(gè)QAbstractSocket類的一個(gè)方法——waitForReadyRead(),這個(gè)方法將一直等待到數(shù)據(jù)可以讀時(shí)結(jié)束,此時(shí)就可以讀數(shù)據(jù)了。方法也很簡單:
- view plaincopy to clipboardprint?
- ... // 建立連接<BR>
- socket.waitForConnected(5000) // 5000表示等待的時(shí)間,默認(rèn)參數(shù)為3000,單位是百萬分之一秒<BR>
- if(!socket.waitForReadyRead(3000)){//3000為等待時(shí)間,沒有默認(rèn)的等待時(shí)間,單位是百萬分之一秒
- return ;
- }<BR>
- QDataStream in(&socket);// 接收socket中的數(shù)據(jù)流<BR>
- ... // 從數(shù)據(jù)流 in 中讀數(shù)據(jù)
- ... // 建立連接socket.waitForConnected(5000) // 5000表示等待的時(shí)間,默認(rèn)參數(shù)為3000,單位是百萬分之一秒
- if(!socket.waitForReadyRead(3000)){//3000為等待時(shí)間,沒有默認(rèn)的等待時(shí)間,單位是百萬分之一秒return ;
- }
- QDataStream in(&socket);// 接收socket中的數(shù)據(jù)流... // 從數(shù)據(jù)流 in 中讀數(shù)據(jù)這樣,數(shù)據(jù)成功讀取出來,實(shí)現(xiàn)數(shù)據(jù)的通信。
單向的數(shù)據(jù)傳輸問題解決了,然后再利用單向的數(shù)據(jù)通信組裝成雙向的數(shù)據(jù)通信,這過程中也會遇到不少問題,將在另一篇日志介紹。
原文地址:http://blog.csdn.net/dongfangyu/archive/2010/10/03/5919789.aspx
小結(jié):關(guān)于詳解 QT 4中構(gòu)建多線程服務(wù)器的內(nèi)容介紹完了,希望本文對你有所幫助!更多內(nèi)容請參考編輯推薦。