Qt實現(xiàn)簡單TCP服務(wù)器_第1頁
Qt實現(xiàn)簡單TCP服務(wù)器_第2頁
Qt實現(xiàn)簡單TCP服務(wù)器_第3頁
Qt實現(xiàn)簡單TCP服務(wù)器_第4頁
Qt實現(xiàn)簡單TCP服務(wù)器_第5頁
已閱讀5頁,還剩13頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

第Qt實現(xiàn)簡單TCP服務(wù)器本文實例為大家分享了Qt學(xué)習(xí)記錄之簡單的TCP服務(wù)器,供大家參考,具體內(nèi)容如下

簡單的多連接TCP服務(wù)器?

本節(jié)我們使用Qt來編寫一個簡單的多連接TCP服務(wù)器程序,涉及到的功能有監(jiān)聽本地IP、打印上線客戶端的IP端口號,接收客戶端發(fā)來的文字信息并打印其IP端口號、單獨或全部地向客戶端發(fā)送文字信息、顯示下線客戶端的IP端口號,并具有踢人的功能。

?該程序使用正點原子的網(wǎng)絡(luò)助手來驗證功能。Qt基于5.9.9版本。

1、創(chuàng)建工程以及配置工作

創(chuàng)建工程的過程就不再介紹了,這里我選擇的是QWidget,因為比較簡單。

然后我們在.pro文件中添加網(wǎng)絡(luò)的模塊,否則待會添加頭文件時會提示沒有這個頭文件。

其他東西不需要看,只要在這后面加上network即可。

再然后,在widget.h頭文件中包含兩個頭文件:

#includeQTcpServer

#includeQTcpSocket

使用Qt搭建TCP服務(wù)器需要兩個套接字:

一個是QTcpServer套接字,這是是用來監(jiān)聽本地的某個IP及端口的。監(jiān)聽成功后,其他的TCP客戶端就可以連接這個服務(wù)器了(當(dāng)然前提是這個服務(wù)器監(jiān)聽的IP是公網(wǎng)IP,或者客戶端與服務(wù)器在同一局域網(wǎng)下)一個是QTcpSocket套接字,這個是服務(wù)器與客戶端通信用的套接字。每當(dāng)有一臺客戶端連接上了這臺服務(wù)器,都會產(chǎn)生這樣的一個套接字。客戶端可以通過某個套接字來查看某個客戶端的IP地址與端口號等信息,也可以拿著這個套接字單獨地與這一個客戶端收發(fā)數(shù)據(jù)。理論上來講,一個TCP服務(wù)器是可以被無限個客戶端連上的。

這里也插一句,如果使用Qt搭建一個TCP客戶端,只需要QTcpSocket套接字。

2、ui界面的設(shè)計

1、comboBox。下拉列表框,用來選擇監(jiān)聽的IP地址,因為能夠被監(jiān)聽的地址肯定是本機擁有的地址,可以是有線、無線網(wǎng)卡的IP地址(局域網(wǎng)),也可以是寬帶分配到的IP地址(廣域網(wǎng))。下一節(jié)我們再來學(xué)習(xí)如何查看本機支持的IP。

2、portEdit。旁邊的單行文本框是用來輸入要監(jiān)聽的端口號。有時候IP的某個端口號已經(jīng)被某個程序占用,此時再去監(jiān)聽就會監(jiān)聽失敗。

3、openbtn。點擊開啟按鈕,監(jiān)聽選定的IP地址及端口號。監(jiān)聽失敗會在QtCreator的控制臺打印失敗的信息。監(jiān)聽成功,同樣也會打印信息,并且IP下拉框、端口號文本框還有本按鈕控件都會變成不可選中狀態(tài)。

4、comboBox_2。選擇某個已連接的客戶端(如果有的話),或者選擇全部。這個可以查看連接上服務(wù)器的客戶端IP及端口號,單獨或全部地向客戶端發(fā)送信息,或者強制踢下線。

5、kickbtn。點擊按鈕,強制使選中的客戶端下線。

6、closebtn。關(guān)閉服務(wù)器。停止監(jiān)聽之前選中的IP及端口號,并斷開全部的TCP連接。

7、recvEdit。接收文本框。當(dāng)客戶端發(fā)來信息,會打印在上面。

8、clearbtn。清除接收文本框內(nèi)的全部內(nèi)容。

9、pushButton。在控制臺上打印全部連接的客戶端IP及端口號。

10、sendEdit。發(fā)送文本框。

11、sendbtn。點擊發(fā)送按鈕,會向某個、全部客戶端發(fā)送文本框內(nèi)的信息

大體的設(shè)計就是這樣,目前只滿足了基本的功能,后續(xù)可以增添更多的功能,并對界面進行美化

3、本地IP的獲取

從這一節(jié)開始,我們將正式進行代碼的編寫

首先,打開命令行,輸入ipconfig

這四個IP地址就我我電腦上可以監(jiān)聽的IP地址,我的程序就是以此來搭建TCP服務(wù)器。其他的IP地址,目前是監(jiān)聽不了的。

Qt獲取本機IP

首先在widge.cpp文件中包含一個頭文件

#includeQtNetwork

然后在Widget類的構(gòu)造函數(shù)中添加如下代碼:

/*讀取本機網(wǎng)卡信息...*/

QStringlocalHostName=QHostInfo::localHostName();

QHostInfoinfo=QHostInfo::fromName(localHostName);

/*將本機所有的IPV4地址添加到comboBox下.*/

foreach(QHostAddressipAddress,info.addresses())

{

if(ipAtocol()==QAbstractSocket::IPv4Protocol)

{

qDebug()ipAddress.toString();

ui-comboBox-addItem(ipAddress.toString());

}

}

這里解釋幾點:

調(diào)用info.addresses()成員函數(shù),返回的是一個QList容器,里面包含著本機全部的IP地址所有的IP地址都在一個容器中,我們需要遍歷這個容器,將里面的IP地址一一取出,這里用到了foreach去遍歷。和for循環(huán)類似,首先定義一個用于接收的對象:ipAddress,然后會一一讀取容器中的內(nèi)容,賦值給這個對象。如果暫時弄不明白也沒有關(guān)系,看多了自然就懂了?;蛘邔W(xué)過python,這句語句和foripAddrinipAddrList:一樣。if(ipAtocol()==QAbstractSocket::IPv4Protocol)這句用來判斷遍歷到的IP是不是IPV4的地址。因為目前IPV6還沒有完全普及,而且不如IPV4易讀。下拉列表控件使用addItem()成員函數(shù)添加元素。

點擊運行,控制臺就會打印信息,并且下拉列表也會顯示信息:

4、開啟服務(wù)器

首先要有一個QTcpServer套接字對象,可以直接在Widget類中聲明這么一個對象;也可以在類中聲明一個對象指針,然后在構(gòu)造函數(shù)中new一個對象,讓這個指針指向它。這里我選擇第二種方法。

/*在widget.h中類定義里添加*/

QTcpServer*server;

/*在widget.cpp中構(gòu)造函數(shù)中添加*/

server=newQTcpServer(this);

然后為開啟按鈕添加槽函數(shù),這里我圖方便直接右擊控件并點擊轉(zhuǎn)到槽。

voidWidget::on_openbtn_clicked()

/*監(jiān)聽本地IP加端口號.*/

if(server-listen(QHostAddress(ui-comboBox-currentText()),ui-portEdit-text().toInt())==false)

{

/*監(jiān)聽失敗,打印信息.*/

qDebug()"listenfalse";

}

else

{

/*監(jiān)聽成功,則將一些控件鎖死.*/

ui-openbtn-setDisabled(true);

ui-portEdit-setDisabled(true);

ui-comboBox-setDisabled(true);

qDebug()"監(jiān)聽成功";

/*激活關(guān)閉按鈕.*/

ui-closebtn-setEnabled(true);

}

}

解釋:

對于下拉列表控件(comboBox),使用currentText()成員函數(shù)獲取選中元素的信息,返回QString型。

對于單行文本控件(portEdit),使用text()成員函數(shù)返回文本,并用toInt()轉(zhuǎn)化為int型

對于TCP服務(wù)器套接字(server),使用listen(constQHostAddressaddress=QHostAddress::Any,quint16port=0)成員函數(shù)綁定IP及端口。

對于大部分控件,使用setDisabled(true)成員函數(shù)可使控件變?yōu)椴豢牲c擊狀態(tài)

最后,我們就可以開啟服務(wù)器了。如果開啟失敗,往往是因為選擇的端口號被占用,這里推薦使用8081這個端口號(8080經(jīng)常會被占用)。開啟成功后,我們就可以使用正點原子的網(wǎng)絡(luò)助手去嘗試連接它。

可以看到,右邊的網(wǎng)絡(luò)助手已經(jīng)處于連接成功的狀態(tài),因此可以證明TCP服務(wù)器已經(jīng)被成功開啟。其他的東西暫時不用看,后面會一一實現(xiàn)。

5、服務(wù)器等待客戶端連接

在構(gòu)造函數(shù)中添加如下代碼:

connect(server,QTcpServer::newConnection,this,[=](){

/*獲取新連接客戶端的socket*/

QTcpSocket*socket=server-nextPendingConnection();

/*將這個socket添加到List容器中...*/

sockList.append(socket);

/*獲取客戶端的IP地址和端口號信息,并轉(zhuǎn)換為字符串.*/

QStringinfo=socket-peerAddress().toString()\

+':'+QString::number(socket-peerPort());

/*將信息打印到文本框.*/

ui-recvEdit-append("已連接:"+info);

/*將客戶端的信息添加到comboBox_2下.*/

ui-comboBox_2-addItem(info);

/*將新連接的socket對象的可以讀取信號連接到接收槽函數(shù).*/

connect(socket,QTcpSocket::readyRead,this,Widget::on_recv);

/*將新連接的socket對象的斷開連接信號連接到斷開槽函數(shù).*/

connect(socket,QTcpSocket::disconnected,this,Widget::on_disconnect);

});

解釋:

當(dāng)有客戶端連接到服務(wù)器,服務(wù)器套接字會觸發(fā)一個newConnection信號。服務(wù)器套接字通過nextPendingConnection()成員函數(shù)獲取到用于和客戶端通信的socket(QTcpSocket)。QTcpSocket套接字通過peerAddress()和peerPort()成員函數(shù),得到該客戶端的IP及端口號。toString()是為了把IP信息轉(zhuǎn)化為字符串,可以試試打印轉(zhuǎn)化前的樣子。QString::number(longn,intbase=10)用于將數(shù)字轉(zhuǎn)化為字符串。接收文本框(recvEdit)使用append()成員函數(shù)打印文字。最后的兩個信號和槽的連接暫時不用看這里我還使用了一個QList容器來保存已連接客戶端的socket

/*在widget.h中類定義里添加*/

QListQTcpSocket*sockList;

最后,我們就可以檢驗一下多連接時的狀態(tài)了

6、實現(xiàn)接收數(shù)據(jù)

如果用于通信的socket(QTcpSocket)接收到數(shù)據(jù),就會觸發(fā)QTcpSocket::readyRead信號,我這里寫了一個槽函數(shù),專門用來接收數(shù)據(jù):

voidWidget::on_recv()

/*找到觸發(fā)信號的那個socket對象.*/

QTcpSocket*sock=qobject_castQTcpSocket*(sender());

/*讀取信息并轉(zhuǎn)化為字符串.*/

QStringinfo="來自"+sock-peerAddress().toString()\

+':'+QString::number(sock-peerPort());

/*將客戶端的信息打印到文本框.*/

ui-recvEdit-append(info);

/*將接收到的數(shù)據(jù)也打印到文本框.*/

ui-recvEdit-append(sock-readAll());

}

解釋:

第一句是用來找到觸發(fā)信號的那個對象(QTcpSocket)。因為這個程序允許多個客戶端連接,每個客戶端在連接后,server都會獲取到與之相對應(yīng)的QTcpSocket套接字對象,意思就是說套接字是與客戶端一一對應(yīng)的。因此找到觸發(fā)信號的QTcpSocket對象,就等于找到了發(fā)送消息的那個客戶端。同樣的,將客戶端的信息打包成字符串,并且顯示到接收文本框QTcpSocket對象,調(diào)用readAll()成員函數(shù),來獲取接收到的信息。

現(xiàn)在也可以解釋上一節(jié)中的connect(socket,QTcpSocket::readyRead,this,Widget::on_recv);了??赡苡腥藭苫螅@里的socket是局部變量,調(diào)用完會被釋放掉,那么這個連接是不是傳了個野指針進去?其實并不是的,socket是個指針,指向server-nextPendingConnection()返回的QTcpSocket對象,真正的對象應(yīng)該是存在內(nèi)部堆內(nèi)存中的,這里只是用了一個局部指針變量去接收它。而connect函數(shù),傳進去的是地址,地址自然就是內(nèi)部QTcpSocket對象的地址了。

最后,演示一下:

注意:Qt使用utf-8編碼格式,而原子的軟件默認(rèn)是GBk格式,所以發(fā)送中文之前,先把原子的軟件全部改成utf-8格式。

實現(xiàn)清除數(shù)據(jù)

這個非常簡單,就沒有必要多說了:

voidWidget::on_clearbtn_clicked()

/*點擊清除按鈕則清除接收文本框.*/

ui-recvEdit-clear();

}

7、實現(xiàn)客戶端的選擇

對客戶端的選擇無非就兩種情況:一種情況是選擇全部連接上的客戶端,一種是選擇單獨的某個客戶端。

我為Widget類添加了一個屬性來解決這兩類問題:

QTcpSocket*currSock;

如果第二個下拉列表框選擇了All,那么這個指針就會指向NULL。如果選擇的是某個特定的IP及端口,那么這個指針就會指向?qū)?yīng)的那個QTcpSocket對象了。

那么對象從哪里找?從前幾節(jié)說過的容器中去找。這個容器用于保存當(dāng)前連接上的客戶端的socket。每當(dāng)有客戶端連上,這個容器就會將它的socket保存進來。每當(dāng)有客戶端下線,這個容器同樣會把下線客戶端的socket刪掉。

QListQTcpSocket*sockList;

QList表示這是一個QList類型的容器,還有其他類型的容器尖括號內(nèi)的東西,表示這個一個裝QTcpSocket對象指針的容器。因為這是一個模板類,還可以是int型的容器,取決于自己的定義。最后的sockList則代表這個容器的名字

下拉列表框的信號和槽

這里我同樣是以右擊控件再點轉(zhuǎn)到槽的方式來自動生成槽函數(shù):

這里的信號不要選錯了,當(dāng)這個控件選擇的選項發(fā)送變化時,會觸發(fā)這個信號。下面是槽函數(shù):

voidWidget::on_comboBox_2_currentIndexChanged(constQStringarg1)

/*如果當(dāng)前選擇的是All,當(dāng)前sock指針就指向空.*/

if(arg1=="All")

{

currSock=NULL;

return;

}

/*不然就讀取選中的信息,將其拆分為IP地址和端口號.*/

QStringListinfo=arg1.split(':');

QStringip=info[0];

intport=info[1].toInt();

/*遍歷容器,找到對應(yīng)的那個socket.*/

foreach(QTcpSocket*sock,sockList)

{

if(sock-peerAddress().toString()==ipsock-peerPort()==port)

{

/*當(dāng)前sock指針指向找到的那個socket.*/

currSock=sock;

break;

}

}

}

解釋:

這個槽函數(shù)是帶有參數(shù)的,參數(shù)就是選中選項的字符串。后面的代碼通過冒號用來拆分字符串。比如某個選項上面的內(nèi)容是:8080,調(diào)用這些代碼后,就會被拆分成IP地址和端口號。最后遍歷容器內(nèi)的所有socket,找到IP與端口與選項中一樣的那個socket。

這個功能暫時不太好演示,大家可以自己弄個按鈕控件,點擊按鈕則打印currSock指向socket的信息,來檢驗一下。

8、實現(xiàn)發(fā)送功能

到了這一步,程序就開始越寫越簡單了

為發(fā)送按鈕添加一個槽函數(shù):

voidWidget::on_sendbtn_clicked()

/*如果是為開啟服務(wù)器、未連接或者選擇All的時候,currSock才會指向空

前兩種情況容器是空的,所以也不會出錯。如果是選中All時候,

且多個客戶端連接,則會遍歷容器中所有的socket,并且一一發(fā)送出去.*/

if(currSock==NULL)

{

foreach(QTcpSocket*sock,sockList)

{

sock-write(ui-sendEdit-toPlainText().toUtf8());

}

}

else

{

/*如果選擇的是一個特定的客戶端,則只向它發(fā)送.*/

currSock-write(ui-sendEdit-toPlainText().toUtf8());

}

}

解釋:

當(dāng)currSock指針指向空的時候,可能有以下幾種狀態(tài):

程序剛剛初始化完成,此時還沒有監(jiān)聽

程序已經(jīng)開始監(jiān)聽,且有一個或多個客戶端上線

程序開始監(jiān)聽,還沒有客戶端連接,或者之前連接的客戶端全部下線

對于1、3兩種情況,sockList容器中是空的,因此即使遍歷也遍歷不到任何東西,自然也就不會給不存在的socket發(fā)送信息。對于第二種情況,也就不用多解釋了。當(dāng)然,這樣的程序可能是存在問題的??梢远嘧鲆恍┡袛?,并且彈出警告提示框,提醒用戶做了錯誤的操作。

最后,演示一下。服務(wù)器先給1號客戶端發(fā)送你好1,接著分別給2、3號客戶端發(fā)送你好2、3,最后給所有的客戶端發(fā)送你好123:

9、實現(xiàn)客戶端下線

之前我們在構(gòu)造函數(shù)中添加了這一句代碼:

connect(socket,QTcpSocket::disconnected,this,Widget::on_disconnect);

把套接字的斷開連接信號連接到了斷開連接槽函數(shù),槽函數(shù)內(nèi)容如下:

voidWidget::on_disconnect()

/*找到觸發(fā)信號的那個socket對象.*/

QTcpSocket*sock=qobject_castQTcpSocket*(sender());

/*將斷開連接的那個socket從List容器中移除.*/

sockList.removeOne(sock);

/*將客戶端信息轉(zhuǎn)化為字符串.*/

QStringinfo=sock-peerAddress().toString()\

+':'+QString::number(sock-peerPort());

/*將斷開連接的消息打印到文本框.*/

ui-recvEdit-append(info+"已斷開");

/*根據(jù)字符串找到comboBox_2中的對應(yīng)元素的索引號*/

intindex=ui-comboBox_2-findText(info);

/*刪除那個元素.*/

ui-comboBox_2-removeItem(index);

/*將新連接的socket對象的可以讀取信號與接收槽函數(shù)斷開.*/

disconnect(sock,QTcpSocket::readyRead,this,Widget::on_recv);

/*將新連接的socket對象的斷開連接信號與斷開槽函數(shù)斷開.*/

disconnect(sock,QTcpSocket::disconnected,this,Widget::on_disconnect);

}

解釋:

同樣先找到觸發(fā)信號的那一個套接字。使用removeOne成員函數(shù)將它從容器中刪除將對應(yīng)客戶端的信息打包成字符串,并且將離線的消息打印到接收文本框?qū)⑾吕斜砜蛑械膶?yīng)項也給刪除斷開信號和槽的連接

最后,演示一下客戶端主動下線會有什么現(xiàn)象:

10、實現(xiàn)踢人功能

剛才是客戶端自己下線,現(xiàn)在是服務(wù)器主動踢人下線。但無論是哪一種,在斷開連接的時候都會觸發(fā)QTcpSocket::disconnected信號

為踢人按鈕添加槽函數(shù):

voidWidget::on_kickbtn_clicked()

/*將全部的客戶端踢下線.*/

if(currSock==NULL)

{

foreach(QTcpSocket*sock,sockList)

{

sock-close();

}

}

else

{

/*將選中的那一個客戶端踢下線.*/

currSock-close();

}

}

到了這里,我感覺已經(jīng)沒有什么好說的了,只需知道調(diào)用close()函數(shù)可以斷開連接。

這里也不做演示了。

11、實現(xiàn)關(guān)閉服務(wù)器

關(guān)閉服務(wù)器主要需要做兩件事:

1、停止監(jiān)聽IP及端口。

2、關(guān)閉所有與客戶端之間的連接。因為停止監(jiān)聽是不夠的,之前的連接還是存在的,甚至還能繼續(xù)收發(fā)數(shù)據(jù)。

voidWidget::on_closebtn_clicked()

currSock=NULL;

/*關(guān)閉監(jiān)聽.*/

server-close();

/*將一些控件恢復(fù).*/

ui-closebtn-setDisabled(true);

ui-openbtn-set

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論