QT学习:基于TCP的网络聊天室程序
TCP與UDP的差別如圖:
一、TCP工作原理
如下圖所示,TCP能夠為應用程序提供可靠的通信連接,使一臺計算機發(fā)出的字節(jié)流無差錯 地送達網(wǎng)絡上的其他計算機。因此,對可靠性要求高的數(shù)據(jù)通信系統(tǒng)往往使用TCP傳輸數(shù)據(jù),但在正式收發(fā)數(shù)據(jù)前,通信雙方必須首先建立連接。
二、TCP編程模型
下面介紹基于TCP的經(jīng)典編程模型,TCP客戶端與服務器間的交互時序如下圖所示:
三、TCP服務器端編程實例
TCP服務器端的具體實現(xiàn)如下:
建立工程TcpServer.pro,文件代碼如下。
(1)頭文件“tcpserver.h”中聲明了需要的各種控件,TcpServer繼承自QDialog,實現(xiàn)了服務器端的對話框顯示與控制。其具體代碼如下:
(2)在源文件“tcpserver.cpp”中,TcpServer類的構(gòu)造函數(shù)主要實現(xiàn)窗體各控件的創(chuàng)建、布局等,其具體代碼如下:
#include "tcpserver.h" TcpServer::TcpServer(QWidget *parent,Qt::WindowFlags f) : QDialog(parent,f) { setWindowTitle(tr("TCP Server")); ContentListWidget = new QListWidget; PortLabel = new QLabel(tr("端口:")); PortLineEdit = new QLineEdit; CreateBtn = new QPushButton(tr("創(chuàng)建聊天室")); mainLayout = new QGridLayout(this); mainLayout->addWidget(ContentListWidget,0,0,1,2); mainLayout->addWidget(PortLabel,1,0); mainLayout->addWidget(PortLineEdit,1,1); mainLayout->addWidget(CreateBtn,2,0,1,2); }(3)服務器端界面如下圖所示:
以上完成了服務器端界面的設計,下面將詳細完成聊天室的服務器端功能。
(1)在工程文件“TcpServer.pro”中添加如下語句:
(2)在工程“TcpServer.pro”中添加C++類文件“tcpclientsocket.h”及“tcpclientsocket.cpp”,TcpClientSocket 繼承自QTcpSocket,創(chuàng)建一個TCP套接字,以便在服務器端實現(xiàn)與客戶端程序的通信。
頭文件“tcpclientsocket.h”的具體代碼如下:
(3)在源文件“tcpclientsocket.cpp”中,構(gòu)造函數(shù)(TcpClientSocket)的內(nèi)容(指定了信號與槽的
連接關(guān)系)如下:
在源文件“tcpclientsocket.cpp”中,dataReceived()函數(shù)的具體代碼如下:
void TcpClientSocket::dataReceived() { while(bytesAvailable()>0) { int length = bytesAvailable(); char buf[1024]; read(buf,length); QString msg=buf; emit updateClients(msg,length); } }在源文件“tcpclientsocket.cpp”中,槽函數(shù)slotDisconnected()的具體代碼如下:
void TcpClientSocket::slotDisconnected() { emit disconnected(this->socketDescriptor()); }(4)在工程“TcpServer.pro”中添加C++類文件“server.h”及“server.cpp”,Server繼承自QTcpServer,實現(xiàn)一 個TCP協(xié)議的服務器。利用QTcpServer,開發(fā)者可以監(jiān)聽到指定端口的TCP連接。其具體代碼如下:
#include <QTcpServer> #include <QObject> #include "tcpclientsocket.h" //包含TCP的套接字 class Server : public QTcpServer { Q_OBJECT //添加宏(Q_OBJECT)是為了實現(xiàn)信號與槽的通信 public: Server(QObject *parent=0,int port=0); QList<TcpClientSocket*> tcpClientSocketList; signals: void updateServer(QString,int); public slots: void updateClients(QString,int); void slotDisconnected(int); protected: void incomingConnection(int socketDescriptor); };(5)在源文件“server.cpp”中,構(gòu)造函數(shù)(Server)的具體內(nèi)容如下:
#include "server.h" Server::Server(QObject *parent,int port):QTcpServer(parent) { listen(QHostAddress::Any,port); }其中,listen(QHostAddress::Any,port)在指定的端口對任意地址進行監(jiān)聽。
QHostAddress定義了幾種特殊的IP地址,如QHostAddress::Null表示一個空地址;
QHostAddress::LocalHost表示IPv4的本機地址127.0.0.1;
QHostAddress::LocalHostIPv6表示IPv6的本機地址;
QHostAddress::Broadcast表示廣播地址255.255.255.255;
QHostAddress::Any表示IPv4的任意地址0.0.0.0;
QHostAddress::AnyIPv6表示IPv6的任意地址。
在源文件“server.cpp”中,當出現(xiàn)一個新的連接時,QTcpSever觸發(fā)incomingConnection()函數(shù),參數(shù)
socketDescriptor指定了連接的Socket描述符,其具體代碼如下:
在源文件“server.cpp”中,updateClients()函數(shù)將任意客戶端發(fā)來的信息進行廣播,保證聊天室的所有成員均能看到其他人的發(fā)言。其具體代碼如下:
void Server::updateClients(QString msg,int length) { emit updateServer(msg,length); //發(fā)出updateServer信號,用來通知服務器對話框更新相應的顯示狀態(tài)。 for(int i=0;i<tcpClientSocketList.count();i++) //實現(xiàn)信息的廣播,tcpClientSocketList中保存了所有與服務器相連的TcpClientSocket對象。 { QTcpSocket *item = tcpClientSocketList.at(i); if(item->write(msg.toLatin1(),length)!=length) { continue; } } }在源文件“server.cpp”中,slotDisconnected()函數(shù)實現(xiàn)從tcpClientSocketList列表中將斷開連接的
TcpClientSocket對象刪除的功能。其具體代碼如下:
(6)在頭文件“tcpserver.h”中添加如下內(nèi)容:
#include "server.h" private: int port; Server *server; public slots: void slotCreateServer(); void updateServer(QString,int);(7)在源文件“tcpserver.cpp”中,在構(gòu)造函數(shù)中添加如下代碼:
port=8010; PortLineEdit->setText(QString::number(port)); connect(CreateBtn,SIGNAL(clicked()),this,SLOT(slotCreateServer()));其中,槽函數(shù)slotCreateServer()用于創(chuàng)建一個TCP服務器,具體內(nèi)容如下:
void TcpServer::slotCreateServer() { server = new Server(this,port); //創(chuàng)建一個Server對象 connect(server,SIGNAL(updateServer(QString,int)),this, SLOT(updateServer(QString,int))); CreateBtn->setEnabled(false); }槽函數(shù)updateServer()用于更新服務器上的信息顯示,具體內(nèi)容如下:
void TcpServer::updateServer(QString msg,int length) { ContentListWidget->addItem(msg.left(length)); }(8)此時,工程中添加了很多文件,工程文件中的內(nèi)容已經(jīng)被改變,需要重新在工程文件
“TcpServer.pro”中添加:
此時,運行服務器端工程“TcpServer.pro”編譯通過。單擊“創(chuàng)建聊天室”按鈕,便開通了一個TCP聊天室的服務器,如下圖所示:
四、TCP客戶端編程實例
TCP客戶端編程具體步驟如下:
建立工程“TcpClient.pro”,文件代碼如下。
(1)在頭文件“tcpclient.h”中,TcpClient類繼承自QDialog類,聲明了需要的各種控件,其具體代碼如下:
(2)源文件“tcpclient.cpp”的具體代碼如下:
#include "tcpclient.h" TcpClient::TcpClient(QWidget *parent,Qt::WindowFlags f) : QDialog(parent,f) { setWindowTitle(tr("TCP Client")); contentListWidget = new QListWidget; sendLineEdit = new QLineEdit; sendBtn = new QPushButton(tr("發(fā)送")); userNameLabel = new QLabel(tr("用戶名:")); userNameLineEdit = new QLineEdit; serverIPLabel = new QLabel(tr("服務器地址:")); serverIPLineEdit = new QLineEdit; portLabel = new QLabel(tr("端口:")); portLineEdit = new QLineEdit; enterBtn= new QPushButton(tr("進入聊天室")); mainLayout = new QGridLayout(this); mainLayout->addWidget(contentListWidget,0,0,1,2); mainLayout->addWidget(sendLineEdit,1,0); mainLayout->addWidget(sendBtn,1,1); mainLayout->addWidget(userNameLabel,2,0); mainLayout->addWidget(userNameLineEdit,2,1); mainLayout->addWidget(serverIPLabel,3,0); mainLayout->addWidget(serverIPLineEdit,3,1); mainLayout->addWidget(portLabel,4,0); mainLayout->addWidget(portLineEdit,4,1); mainLayout->addWidget(enterBtn,5,0,1,2); }(3)客戶端界面如下圖所示:
以上完成了客戶端界面的設計,下面將完成客戶端的真正聊天功能。
(1)在客戶端工程文件“TcpClient.pro”中添加如下語句:
(2)在頭文件“tcpclient.h”中添加如下代碼:
#include <QHostAddress> #include <QTcpSocket> private: bool status; int port; QHostAddress *serverIP; QString userName; QTcpSocket *tcpSocket; public slots: void slotEnter(); void slotConnected(); void slotDisconnected(); void dataReceived(); void slotSend();(3)在源文件“tcpclient.cpp”中添加頭文件:
#include <QMessageBox> #include <QHostInfo>在其構(gòu)造函數(shù)中添加如下代碼:
status = false; port = 8010; portLineEdit->setText(QString::number(port)); serverIP =new QHostAddress(); connect(enterBtn,SIGNAL(clicked()),this,SLOT(slotEnter())); connect(sendBtn,SIGNAL(clicked()),this,SLOT(slotSend())); sendBtn->setEnabled(false);在以上代碼中,槽函數(shù)slotEnter()實現(xiàn)了進入和離開聊天室的功能。具體代碼如下:
void TcpClient::slotEnter() {if(!status) //status表示當前的狀態(tài),true表示已經(jīng)進入聊天室,false表示已經(jīng)離開聊天室。 這里根據(jù)status的狀態(tài)決定是執(zhí)行“進入”還是“離開”的操作。{/* 完成輸入合法性檢驗 */QString ip = serverIPLineEdit->text();if(!serverIP->setAddress(ip))//用來判斷給定的IP地址能否被正確解析。{QMessageBox::information(this,tr("error"),tr("server ip address error!"));return;}if(userNameLineEdit->text()==""){QMessageBox::information(this,tr("error"),tr("User name error!"));return;}userName=userNameLineEdit->text();/* 創(chuàng)建了一個QTcpSocket類對象,并將信號/槽連接起來 */tcpSocket = new QTcpSocket(this);connect(tcpSocket,SIGNAL(connected()),this,SLOT (slotConnected()));connect(tcpSocket,SIGNAL(disconnected()),this,SLOT (slotDisconnected()));connect(tcpSocket,SIGNAL(readyRead()),this,SLOT (dataReceived()));tcpSocket->connectToHost(*serverIP,port); //與TCP服務器端連接,連接成功后發(fā)出connected() 信號status=true;}else{int length=0;QString msg=userName+tr(":Leave Chat Room");//構(gòu)造一條離開聊天室的消息。if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg.length()) //通知服務器端以上 構(gòu)造的消息{return;}tcpSocket->disconnectFromHost(); //與服務器斷開連接,斷開連接后發(fā)出disconnected()信號。status=false; //將status狀態(tài)復位} }在源文件“tcpclient.cpp”中,槽函數(shù)slotConnected()為connected()信號的響應槽,當與服務器連接成功后,客戶端構(gòu)造一條進入聊天室的消息,并通知服務器。其具體代碼如下:
void TcpClient::slotConnected() { sendBtn->setEnabled(true); enterBtn->setText(tr("離開")); int length=0; QString msg=userName+tr(":Enter Chat Room"); if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg.length()) { return; } }在源文件“tcpclient.cpp”中,槽函數(shù)slotSend()的具體代碼如下:
void TcpClient::slotSend() { if(sendLineEdit->text()=="") { return; } QString msg=userName+":"+sendLineEdit->text(); tcpSocket->write(msg.toLatin1(),msg.length()); sendLineEdit->clear(); }在源文件“tcpclient.cpp”中,槽函數(shù)slotDisconnected()的具體內(nèi)容如下:
void TcpClient::slotDisconnected() { sendBtn->setEnabled(false); enterBtn->setText(tr("進入聊天室")); }當有數(shù)據(jù)到來時,觸發(fā)源文件“tcpclient.cpp”的dataReceived()函數(shù),從套接字中將有效數(shù)據(jù)取出并顯示,其代碼如下:
void TcpClient::dataReceived() { while(tcpSocket->bytesAvailable()>0) { QByteArray datagram; datagram.resize(tcpSocket->bytesAvailable()); tcpSocket->read(datagram.data(),datagram.size()); QString msg=datagram.data(); contentListWidget->addItem(msg.left(datagram.size())); } }(4)此時運行客戶端“TcpClient.pro”工程,結(jié)果如下圖所示:
最后,同時運行服務器和客戶端程序,運行結(jié)果如下圖所示,這里演示的是系統(tǒng)中登錄了兩 個用戶的狀態(tài)。
總結(jié)
以上是生活随笔為你收集整理的QT学习:基于TCP的网络聊天室程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QT学习:基于UDP的网络广播程序
- 下一篇: QT学习:网络应用开发练习(文件下载)