黃 翩,張 瓊,祝 婷
(西安電子科技大學(xué)電子信息攻防對(duì)抗與仿真重點(diǎn)實(shí)驗(yàn)室,陜西西安 710071)
目前大多數(shù)智能設(shè)備和儀表均采用RS232/485的通信方式,不具備遠(yuǎn)程控制和數(shù)據(jù)傳輸能力,而如何利用以太網(wǎng)實(shí)現(xiàn)遠(yuǎn)程通信則是問(wèn)題的關(guān)鍵。近年來(lái),以TCP/IP為基礎(chǔ)的面向數(shù)據(jù)流和連接的可靠傳輸協(xié)議得到迅猛發(fā)展,但傳統(tǒng)的軟件開(kāi)發(fā)方法是利用Socket套接字來(lái)編寫(xiě)程序,因?yàn)槠涮捉幼直容^復(fù)雜,包含了許多的數(shù)據(jù)結(jié)構(gòu)和函數(shù),難以掌握和使用。而Qt把網(wǎng)絡(luò)編程有關(guān)的數(shù)據(jù)結(jié)構(gòu)和函數(shù)封裝成類(lèi),在較大程度上模塊化,于是軟件開(kāi)發(fā)的程序變得簡(jiǎn)潔、高效,可重用性較好,用戶(hù)開(kāi)發(fā)非常方便。
傳輸控制協(xié)議(Transmission Control Protocol,TCP)是一個(gè)用于數(shù)據(jù)傳輸?shù)牡蛯泳W(wǎng)絡(luò)協(xié)議,多個(gè)互聯(lián)網(wǎng)協(xié)議都是基于TCP協(xié)議的。TCP是一個(gè)面向數(shù)據(jù)流和連接的可靠傳輸協(xié)議[1],對(duì)于應(yīng)用程序來(lái)說(shuō),數(shù)據(jù)是一個(gè)較長(zhǎng)的流,因此在TCP上建立的高層協(xié)議通常是面向塊(Block-oriented)或面向行的(Line-oriented)。面向塊的協(xié)議把數(shù)據(jù)作為二進(jìn)制塊傳輸,每個(gè)數(shù)據(jù)塊由一個(gè)大小字段和包含的數(shù)據(jù)組成。面向行的協(xié)議把數(shù)據(jù)作為一個(gè)文本文件的行來(lái)傳輸,每一個(gè)數(shù)據(jù)行都以一個(gè)換行符結(jié)束。
在TCP/IP協(xié)議中,TCP協(xié)議提供可靠的連接服務(wù),采用3次握手建立一個(gè)連接[2]:
第一次握手:建立連接時(shí),客戶(hù)端發(fā)送 SYN包(SYN=j)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn);
第二次握手:服務(wù)器收到SYN包,必須確認(rèn)客戶(hù)的SYN(ACK=j+1),同時(shí)自己也發(fā)送一個(gè)SYN包(SYN=k),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);
第三次握手:客戶(hù)端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ACK=k+1),此包發(fā)送完畢,客戶(hù)端和服務(wù)器進(jìn)入ESTABLISHED狀態(tài),完成3次握手。
完成3次握手,客戶(hù)端和服務(wù)器開(kāi)始傳送數(shù)據(jù)。
TCP Socket的通信工作原理[6]如圖1所示。
圖1 TCPSocket通信原理圖
客戶(hù)端首先通過(guò)調(diào)用Socket()函數(shù)創(chuàng)建TCP套接口,指定服務(wù)器IP地址和端口,然后調(diào)用connect()函數(shù)與服務(wù)器取得連接,接著進(jìn)行數(shù)據(jù)處理,讀入并輸出客戶(hù)端的應(yīng)答,最后關(guān)閉程序。
服務(wù)器端也是通過(guò)調(diào)用Socket()函數(shù)來(lái)創(chuàng)建TCP套接口,用bind函數(shù)將Socket與主機(jī)信息進(jìn)行綁定,然后通過(guò)listen()函數(shù)監(jiān)聽(tīng)客戶(hù)端的連接,accept()接受客戶(hù)端的連接后進(jìn)行數(shù)據(jù)處理,讀入并輸出服務(wù)器的應(yīng)答,最后終止程序。
Qt中與 TCP有關(guān)的類(lèi)主要有 QTCPServer類(lèi)、QTCPSocket類(lèi)和QHostAddress類(lèi),由于QTCPSocket類(lèi)繼承自 QIODevice。所以,其可從 QDataStream或QTextStream中讀取或?qū)懭霐?shù)據(jù),而且從文件讀數(shù)據(jù)和從網(wǎng)絡(luò)上讀數(shù)據(jù)有一個(gè)明顯的不同:必須保證用操作符“?”讀取數(shù)據(jù)時(shí),已從另一方接收了足夠的數(shù)據(jù)。如果沒(méi)有做到這點(diǎn),那么則會(huì)有一個(gè)失敗的結(jié)果:行為未定義。此外,QTCPServer類(lèi)和QTCPSocket類(lèi)需要事件機(jī)制支持,故要在程序中開(kāi)啟事件循環(huán)或調(diào)用相應(yīng)的事件響應(yīng)函數(shù)。
QTCPSocket類(lèi)為T(mén)CP提供了一個(gè)接口,繼承自QAbstractSocket。可以使用QTCPSocket來(lái)實(shí)現(xiàn)POP3、SMTP和NNTP等標(biāo)準(zhǔn)網(wǎng)絡(luò)協(xié)議,也可實(shí)現(xiàn)自定義的網(wǎng)絡(luò)協(xié)議。QTCPSocket傳輸?shù)氖沁B續(xù)的數(shù)據(jù)流,尤其適合連續(xù)的數(shù)據(jù)傳輸[1]。
在數(shù)據(jù)傳輸前,必須建立一個(gè)TCP連接到遠(yuǎn)程的主機(jī)和端口,可以使用connectToHost()函數(shù)與服務(wù)器進(jìn)行連接,該函數(shù)有兩種重載形式,一種使用字符串來(lái)表示主機(jī)地址的參數(shù)類(lèi)型,一種是用QHostAddress類(lèi)。一旦建立連接,將返回一個(gè)connected()信號(hào),如果出現(xiàn)錯(cuò)誤,將返回 error(QAbstractSocket::SocketError socketError)信號(hào),socketError參數(shù)描述錯(cuò)誤的類(lèi)型。
連接成功后便可根據(jù)需要發(fā)送或接收數(shù)據(jù)??墒褂脀rite()寫(xiě)入數(shù)據(jù),使用read()讀取數(shù)據(jù)。當(dāng)QTCPSocket收到新的數(shù)據(jù)時(shí),readyRead()信號(hào)就會(huì)被發(fā)送。將該信號(hào)與處理該信號(hào)的槽函數(shù)相連,實(shí)現(xiàn)相應(yīng)的功能。但是該信號(hào)被發(fā)送時(shí),并不代表數(shù)據(jù)已經(jīng)被完整接收,接收到的可能是一包不完整的數(shù)據(jù)、一包完整的數(shù)據(jù)或若干包完整的數(shù)據(jù)和一包不完整的數(shù)據(jù)、若干包完整的數(shù)據(jù)等多種情況,所以在處理數(shù)據(jù)時(shí)要考慮所有的情況,以免錯(cuò)誤地去解析數(shù)據(jù)[1]。從一個(gè)QTCPSocket中讀取數(shù)據(jù)前,必須先調(diào)用bytesAvailable()函數(shù)來(lái)確保已有足夠數(shù)據(jù)可用。
最后可使用disconnectFromHost()函數(shù)來(lái)斷開(kāi)與服務(wù)器的連接。使用QTCPSocket類(lèi)實(shí)現(xiàn)客戶(hù)端的流程如圖2所示。
圖2 QTCPSocket類(lèi)實(shí)現(xiàn)客戶(hù)端流程
服務(wù)器端也需要 QTCPSocket才能與客戶(hù)端的QTCPSocket建立一個(gè)TCP連接。不同之處在于服務(wù)器端QTCPSocket的創(chuàng)建是在服務(wù)器監(jiān)聽(tīng)到連接請(qǐng)求時(shí)進(jìn)行的。
QTCPServer類(lèi)可用來(lái)處理到來(lái)的TCP連接,值得注意的是,該類(lèi)直接繼承于 QObject基類(lèi),而不是QAbstractSocket抽象套接字類(lèi)。其調(diào)用listen()設(shè)置服務(wù)器,監(jiān)聽(tīng)某一地址和端口,然后關(guān)聯(lián)newConnection()信號(hào),當(dāng)收到客戶(hù)端連接請(qǐng)求時(shí)就發(fā)射該信號(hào)。在槽中,調(diào)用nextPendingConnection()來(lái)接受這個(gè)連接,然后創(chuàng)建一個(gè)QTCPSocket對(duì)象與客戶(hù)端進(jìn)行通信。
以雷達(dá)信號(hào)環(huán)境模擬器的上位機(jī)與下位機(jī)的TCP通信為例,上位機(jī)相當(dāng)于服務(wù)器,下位機(jī)則是客戶(hù)端。雷達(dá)信號(hào)環(huán)境模擬器有5個(gè)不同頻段的雷達(dá),相當(dāng)于5個(gè)不同的客戶(hù)端,服務(wù)器端監(jiān)聽(tīng)客戶(hù)端的接連,當(dāng)客戶(hù)端連接上服務(wù)器時(shí),服務(wù)器首先給客戶(hù)端發(fā)送一個(gè)詢(xún)問(wèn)幀,客戶(hù)端回答應(yīng)答幀;接著服務(wù)器發(fā)送自檢命令,客戶(hù)端響應(yīng)自檢命令,回送自檢結(jié)果;最后服務(wù)器發(fā)送雷達(dá)參數(shù)幀。
(1)開(kāi)啟服務(wù)器,支持多客戶(hù)端接入,能夠?qū)崟r(shí)顯示每個(gè)客戶(hù)端接入狀態(tài)。
(2)等待所有客戶(hù)端都處于連接狀態(tài)時(shí),向各個(gè)客戶(hù)端發(fā)送數(shù)據(jù)。
(3)發(fā)送完成后等待客戶(hù)端回復(fù),顯示回復(fù)結(jié)果。
(4)通信完成,關(guān)閉服務(wù)器。
(1)新建服務(wù)器類(lèi)TCPServer和TCPSocket類(lèi),分別繼承自QTCPServer和QTCPSocket。新建服務(wù)器類(lèi)TCPServer用于接收客戶(hù)端的TCP請(qǐng)求,存儲(chǔ)所有客戶(hù)端信息,并向主窗口發(fā)送信息。在這個(gè)類(lèi)中最關(guān)鍵的是實(shí)例化QTCPServer中的虛函數(shù):void incomingConnection(int socketDescriptor),重寫(xiě)這個(gè)虛函數(shù),有TCP請(qǐng)求時(shí)會(huì)觸發(fā),參數(shù)為描述socket ID的int變量,此函數(shù)在QTCPServer檢測(cè)到外來(lái) TCP請(qǐng)求時(shí),會(huì)自動(dòng)調(diào)用[3-4]。
新建TCPSocket類(lèi),控制發(fā)送/接收數(shù)據(jù),向TCPS-erver發(fā)送相關(guān)信號(hào)。在TCPServer類(lèi)的incomingConnection(int socketDescriptor)函數(shù)中,通過(guò)設(shè)置Socket-Descriptor,初始化一個(gè) TCPSocket實(shí)例[4],具體實(shí)現(xiàn)如圖3所示。
圖3 incomingConnection()函數(shù)的實(shí)現(xiàn)
上述newConnection()信號(hào)是接收到局域網(wǎng)內(nèi)的TCP請(qǐng)求后,連接此信號(hào)到自定義的槽函數(shù)connect(TCPServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));但這只針對(duì)一個(gè)客戶(hù)端的情況,無(wú)法處理多客戶(hù)端。
TCPSocket類(lèi),連接有 readyRead()信號(hào),ready-Read()信號(hào)為新連接中有可讀數(shù)據(jù)時(shí)發(fā)出:connect(this,SIGNAL(readyRead()),this,SLOT(emitready-Read()));
在emitreadyRead()的槽函數(shù)中發(fā)出一個(gè)讀信號(hào)給TCPServer:
void TCPSocket::emitreadyRead()
{
emit readyReadSocket((QTCPSocket*)this);
}
(2)接收/發(fā)送數(shù)據(jù)的算法實(shí)現(xiàn)。連接建立好后便可進(jìn)行數(shù)據(jù)的接收與發(fā)送。在TCPServer中將TCPSocket發(fā)送的readyReadSocket((QTCPSocket*)this)信號(hào)連接給槽函數(shù)readMessage(QTCPSocket*)進(jìn)行數(shù)據(jù)讀取,算法實(shí)現(xiàn)如圖4所示。
圖4 readMessage()的實(shí)現(xiàn)算法
通過(guò)點(diǎn)擊界面上的發(fā)送數(shù)據(jù)按鈕觸發(fā)槽函數(shù)sendMessage(char*)發(fā)送數(shù)據(jù),算法實(shí)現(xiàn)如圖 5所示。
圖5 sendMessage()的實(shí)現(xiàn)算法
綜上所述,完成此服務(wù)器的搭建,建立了Server類(lèi)、TCPServer類(lèi)和 TCPSocket類(lèi)。Server類(lèi)繼承自QWidget,主要完成界面上的功能,實(shí)現(xiàn)人機(jī)交互;TCPServer類(lèi)主要完成接收客戶(hù)端的連接請(qǐng)求,進(jìn)行數(shù)據(jù)的接收和發(fā)送;TCPSocket類(lèi)主要完成套接字的創(chuàng)建。這3類(lèi)之間有些信號(hào)是相互關(guān)聯(lián)的,所以這3類(lèi)層層相扣,缺一不可。完整工程如圖6所示。
圖6 建立完整的工程
Qt對(duì)Socket的封裝,方便了開(kāi)發(fā)人員,且Qt還提供了一種signals/slots的信號(hào)槽機(jī)制,用這種安全類(lèi)型來(lái)替代callback(回調(diào)),這使各元件之間的協(xié)同工作變得簡(jiǎn)單。同時(shí)Qt是源代碼級(jí)的跨平臺(tái),一次編寫(xiě),隨處編譯,一次開(kāi)發(fā)的Qt應(yīng)用程序可以移植到不同平臺(tái)上,只需重新編譯即可運(yùn)行[5]。這樣也減少了開(kāi)發(fā)者在開(kāi)發(fā)不同平臺(tái)下應(yīng)用程序的工作量?;赒t的這些優(yōu)點(diǎn),可預(yù)見(jiàn)Qt將被更多開(kāi)發(fā)人員推崇,其應(yīng)用將不斷擴(kuò)展。
[1]霍亞飛.QtCreator快速入門(mén)[M].北京:北京航空航天大學(xué)出版社,2012.
[2]LAURA A.Chappell,Ed Tittel.TCP/IP 協(xié)議原理與應(yīng)用[M].北京:清華大學(xué)出版社,2009.
[3]Nokia公司.Qt參考文檔[EB/OL].(2011-11-13)[2014 -04 -11]http://doc.trolltech.com/5.0/index.html
[4]Jasmin Blanchette,Mark Summerfield.C++GUI Qt4 編程[M].2 版.北京:電子工業(yè)出版社,2008.
[5]蔡志明,盧傳富,李立夏.精通Qt4編程[M].北京:電子工業(yè)出版社,2008.
[6]趙偉.基于Socket的消息隊(duì)列中間件的研究與實(shí)現(xiàn)[D].呼和浩特:內(nèi)蒙古大學(xué),2007.
[7]郭春柱.嵌入式系統(tǒng)設(shè)計(jì)師案例導(dǎo)學(xué)[M].西安:西安電子科技大學(xué)出版社,2007.
[8]么麗穎.基于Linux的服務(wù)器集群系統(tǒng)的研究與設(shè)計(jì)[J].電子科技,2012,25(6):4 -5,26.