




版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 商業(yè)合作備忘錄與協(xié)議條款
- 綜合設(shè)計能力對紡織品設(shè)計師的重要性試題及答案
- 貴州國企招聘2025貴州省水利水電工程咨詢有限責(zé)任公司招聘12人筆試參考題庫附帶答案詳解
- 紡織品檢測員的知識架構(gòu)與技能提升試題及答案
- 2025遼寧沈陽地鐵集團有限公司所屬公司招聘11人筆試參考題庫附帶答案詳解
- 2025河南三門峽盧氏縣國有資本投資運營有限公司招聘6人筆試參考題庫附帶答案詳解
- 2025四川南充臨江東方建設(shè)集團有限公司招聘11人筆試參考題庫附帶答案詳解
- 2025中國華冶科工集團有限公司校園招聘280人筆試參考題庫附帶答案詳解
- 超級宇宙考試題及答案
- 駱駝祥子面試題及答案
- 護工培訓(xùn)課件課件
- 工業(yè)氣體企業(yè)公司組織架構(gòu)圖職能部門及工作職責(zé)
- 20XX上海嘉定高中高三英語一模試卷
- xxx猩紅熱ppt課件
- T梁臺座計算書
- 建筑施工企業(yè)售后服務(wù)保障方案
- 01-《數(shù)值分析》實驗指導(dǎo)書
- 第四章 潛孔鉆機
- 佳能700D單反相機拍攝技巧[技巧]
- 農(nóng)產(chǎn)品批發(fā)市場管理技術(shù)規(guī)范編制說明
- 重慶市婚姻介紹合同協(xié)議書范本模板
評論
0/150
提交評論