QT 之 TCP/IP 服务器和客户端(一)
TCP協(xié)議的程序使用的是客戶端/服務(wù)器模式,在Qt中提供了QTcpSocket類來編寫客戶端程序,使用QTcpServer類編寫服務(wù)器端程序。我們在服務(wù)器端進(jìn)行端口的監(jiān)聽,一旦發(fā)現(xiàn)客戶端的連接請求,就會發(fā)出newConnection()信號,我們可以關(guān)聯(lián)這個(gè)信號到我們自己的槽函數(shù),進(jìn)行數(shù)據(jù)的發(fā)送。而在客戶端,一旦有數(shù)據(jù)到來就會發(fā)出readyRead()信號,我們可以關(guān)聯(lián)此信號,進(jìn)行數(shù)據(jù)的接收。
一、服務(wù)器端。
在服務(wù)器端的程序中,我們監(jiān)聽本地主機(jī)的一個(gè)端口,這里使用6666,然后我們關(guān)聯(lián)newConnection()信號與自己寫的sendMessage()槽函數(shù)。就是說一旦有客戶端的連接請求,就會執(zhí)行sendMessage()函數(shù),在這個(gè)函數(shù)里我們發(fā)送一個(gè)簡單的字符串。
1.我們新建Qt4 Gui Application,工程名為“tcpServer”,選中QtNetwork模塊,Base class選擇QWidget。(說明:如果一些Qt Creator版本沒有添加模塊一項(xiàng),我們就需要在工程文件tcpServer.pro中添加一行代碼:QT += network)
2.我們在widget.ui的設(shè)計(jì)區(qū)添加一個(gè)Label,更改其objectName為statusLabel,用于顯示一些狀態(tài)信息。
3.在widget.h文件中做以下更改。
添加頭文件:#include <QtNetWork>
添加private對象:QTcpServer *tcpServer;
添加私有槽函數(shù):
private slots:
void sendMessage();
4.在widget.cpp文件中進(jìn)行更改。
在其構(gòu)造函數(shù)中添加代碼:
tcpServer = new QTcpServer(this);
??? if(!tcpServer->listen(QHostAddress::LocalHost,6666)) ? //監(jiān)聽其他IP地址則用QHostAddress::Any
??? {? //監(jiān)聽本地主機(jī)的6666端口,如果出錯(cuò)就輸出錯(cuò)誤信息,并關(guān)閉
??????? qDebug() << tcpServer->errorString();
??????? close();
??? }
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage()));
//連接信號和相應(yīng)槽函數(shù)
我們在構(gòu)造函數(shù)中使用tcpServer的listen()函數(shù)進(jìn)行監(jiān)聽,然后關(guān)聯(lián)了newConnection()和我們自己的sendMessage()函數(shù)。
下面我們實(shí)現(xiàn)sendMessage()函數(shù)。
void Widget::sendMessage()
{
??? QByteArray block; //用于暫存我們要發(fā)送的數(shù)據(jù)
??? QDataStream out(&block,QIODevice::WriteOnly);
??? //使用數(shù)據(jù)流寫入數(shù)據(jù)
??? out.setVersion(QDataStream::Qt_4_6);
??? //設(shè)置數(shù)據(jù)流的版本,客戶端和服務(wù)器端使用的版本要相同
??? out<<(quint16) 0;
??? out<<tr(“hello Tcp!!!”);
??? out.device()->seek(0);
??? out<<(quint16) (block.size() – sizeof(quint16));
??? QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
??? //我們獲取已經(jīng)建立的連接的子套接字
??? connect(clientConnection,SIGNAL(disconnected()),clientConnection,
??????????? SLOT(deleteLater()));
??? clientConnection->write(block);
??? clientConnection->disconnectFromHost();
??? ui->statusLabel->setText(“send message successful!!!”);
??? //發(fā)送數(shù)據(jù)成功后,顯示提示
}
這個(gè)是數(shù)據(jù)發(fā)送函數(shù),我們主要介紹兩點(diǎn):
(1)為了保證在客戶端能接收到完整的文件,我們都在數(shù)據(jù)流的最開始寫入完整文件的大小信息,這樣客戶端就可以根據(jù)大小信息來判斷是否接受到了完整的文件。而在服務(wù)器端,我們在發(fā)送數(shù)據(jù)時(shí)就要首先發(fā)送實(shí)際文件的大小信息,但是,文件的大小一開始是無法預(yù)知的,所以我們先使用了out<<(quint16) 0;在block的開始添加了一個(gè)quint16大小的空間,也就是兩字節(jié)的空間,它用于后面放置文件的大小信息。然后out<<tr(“hello Tcp!!!”);輸入實(shí)際的文件,這里是字符串。當(dāng)文件輸入完成后我們在使用out.device()->seek(0);返回到block的開始,加入實(shí)際的文件大小信息,也就是后面的代碼,它是實(shí)際文件的大小:out<<(quint16) (block.size() – sizeof(quint16));
(2)在服務(wù)器端我們可以使用tcpServer的nextPendingConnection()函數(shù)來獲取已經(jīng)建立的連接的Tcp套接字,使用它來完成數(shù)據(jù)的發(fā)送和其它操作。比如這里,我們關(guān)聯(lián)了disconnected()信號和deleteLater()槽函數(shù),然后我們發(fā)送數(shù)據(jù)
clientConnection->write(block);
然后是clientConnection->disconnectFromHost();它表示當(dāng)發(fā)送完成時(shí)就會斷開連接,這時(shí)就會發(fā)出disconnected()信號,而最后調(diào)用deleteLater()函數(shù)保證在關(guān)閉連接后刪除該套接字clientConnection。
5.這樣服務(wù)器的程序就完成了,我們先運(yùn)行一下程序。
二、客戶端。
我們在客戶端程序中向服務(wù)器發(fā)送連接請求,當(dāng)連接成功時(shí)接收服務(wù)器發(fā)送的數(shù)據(jù)。
1. .我們新建Qt4 Gui Application,工程名為“tcpClient”,選中QtNetwork模塊,Base class選擇QWidget。
2,我們在widget.ui中添加幾個(gè)標(biāo)簽Label和兩個(gè)Line Edit以及一個(gè)按鈕Push Button。
其中“主機(jī)”后的Line Edit的objectName為hostLineEdit,“端口號”后的為portLineEdit。
“收到的信息”標(biāo)簽的objectName為messageLabel 。
3.在widget.h文件中做更改。
添加頭文件:#include <QtNetwork>
添加private變量:
QTcpSocket *tcpSocket;
QString message;? //存放從服務(wù)器接收到的字符串
quint16 blockSize;? //存放文件的大小信息
添加私有槽函數(shù):
private slots:
??? void newConnect(); //連接服務(wù)器
??? void readMessage();? //接收數(shù)據(jù)
void displayError(QAbstractSocket::SocketError);? //顯示錯(cuò)誤
4.在widget.cpp文件中做更改。
(1)在構(gòu)造函數(shù)中添加代碼:
tcpSocket = new QTcpSocket(this);
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage()));
connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),
???????? this,SLOT(displayError(QAbstractSocket::SocketError)));
這里關(guān)聯(lián)了tcpSocket的兩個(gè)信號,當(dāng)有數(shù)據(jù)到來時(shí)發(fā)出readyRead()信號,我們執(zhí)行讀取數(shù)據(jù)的readMessage()函數(shù)。當(dāng)出現(xiàn)錯(cuò)誤時(shí)發(fā)出error()信號,我們執(zhí)行displayError()槽函數(shù)。
(2)實(shí)現(xiàn)newConnect()函數(shù)。
void Widget::newConnect()
{
??? blockSize = 0; //初始化其為0
??? tcpSocket->abort(); //取消已有的連接
??? tcpSocket->connectToHost(ui->hostLineEdit->text(),
???????????????????????????? ui->portLineEdit->text().toInt());
??? //連接到主機(jī),這里從界面獲取主機(jī)地址和端口號
}
這個(gè)函數(shù)實(shí)現(xiàn)了連接到服務(wù)器,下面會在“連接”按鈕的單擊事件槽函數(shù)中調(diào)用這個(gè)函數(shù)。
(3)實(shí)現(xiàn)readMessage()函數(shù)。
void Widget::readMessage()
{
??? QDataStream in(tcpSocket);
??? in.setVersion(QDataStream::Qt_4_6);
??? //設(shè)置數(shù)據(jù)流版本,這里要和服務(wù)器端相同
??? if(blockSize==0) //如果是剛開始接收數(shù)據(jù)
??? {
??????? //判斷接收的數(shù)據(jù)是否有兩字節(jié),也就是文件的大小信息
??????? //如果有則保存到blockSize變量中,沒有則返回,繼續(xù)接收數(shù)據(jù)
??????? if(tcpSocket->bytesAvailable() < (int)sizeof(quint16)) return;
??????? in >> blockSize;
??? }
??? if(tcpSocket->bytesAvailable() < blockSize) return;
??? //如果沒有得到全部的數(shù)據(jù),則返回,繼續(xù)接收數(shù)據(jù)
??? in >> message;
??? //將接收到的數(shù)據(jù)存放到變量中
??? ui->messageLabel->setText(message);
??? //顯示接收到的數(shù)據(jù)
}
這個(gè)函數(shù)實(shí)現(xiàn)了數(shù)據(jù)的接收,它與服務(wù)器端的發(fā)送函數(shù)相對應(yīng)。首先我們要獲取文件的大小信息,然后根據(jù)文件的大小來判斷是否接收到了完整的文件。
(4)實(shí)現(xiàn)displayError()函數(shù)。
void Widget::displayError(QAbstractSocket::SocketError)
{
??? qDebug() << tcpSocket->errorString(); //輸出錯(cuò)誤信息
}
這里簡單的實(shí)現(xiàn)了錯(cuò)誤信息的輸出。
(5)我們在widget.ui中進(jìn)入“連接”按鈕的單擊事件槽函數(shù),然后更改如下。
void Widget::on_pushButton_clicked() //連接按鈕
{
??? newConnect(); //請求連接
}
這里直接調(diào)用了newConnect()函數(shù)。
5.我們運(yùn)行程序,同時(shí)運(yùn)行服務(wù)器程序,然后在“主機(jī)”后填入“l(fā)ocalhost”,在“端口號”后填入“6666”,點(diǎn)擊“連接”按鈕。
?
可以看到我們正確地接收到了數(shù)據(jù)。因?yàn)榉?wù)器端和客戶端是在同一臺機(jī)子上運(yùn)行的,所以我這里填寫了“主機(jī)”為“l(fā)ocalhost”,如果你在不同的機(jī)子上運(yùn)行,需要在“主機(jī)”后填寫其正確的IP地址。
總結(jié)
以上是生活随笔為你收集整理的QT 之 TCP/IP 服务器和客户端(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用GCC生成无格式二进制文件(plai
- 下一篇: OpenGL开发库的详细介绍