首頁 > 軟體

QT基於TCP實現網路聊天室程式

2022-08-22 14:00:56

本文範例為大家分享了QT學習:基於TCP的網路聊天室程式,供大家參考,具體內容如下

TCP與UDP的差別如圖:

一、TCP工作原理

如下圖所示,TCP能夠為應用程式提供可靠的通訊連線,使一臺計算機發出的位元組流無差錯 地送達網路上的其他計算機。因此,對可靠性要求高的資料通訊系統往往使用TCP傳輸資料,但在正式收發資料前,通訊雙方必須首先建立連線。

二、TCP程式設計模型

下面介紹基於TCP的經典程式設計模型,TCP使用者端與伺服器間的互動時序如下圖所示:

三、TCP伺服器端程式設計範例

TCP伺服器端的具體實現如下:

建立工程TcpServer.pro,檔案程式碼如下。

(1)標頭檔案“tcpserver.h”中宣告了需要的各種控制元件,TcpServer繼承自QDialog,實現了伺服器端的對話方塊顯示與控制。其具體程式碼如下:

#include <QDialog> 
#include <QListWidget> 
#include <QLabel> 
#include <QLineEdit> 
#include <QPushButton> 
#include <QGridLayout> 
class TcpServer : public QDialog 
{ 
Q_OBJECT 
public: 
TcpServer(QWidget *parent = 0,Qt::WindowFlags f=0); 
~TcpServer(); 
private: 
QListWidget *ContentListWidget; 
QLabel *PortLabel; 
QLineEdit *PortLineEdit; 
QPushButton *CreateBtn; 
QGridLayout *mainLayout; 
}; 

(2)在原始檔“tcpserver.cpp”中,TcpServer類別建構函式主要實現表單各控制元件的建立、佈局等,其具體程式碼如下:

#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("建立聊天室")); 
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”中新增如下語句:

QT += network 

(2)在工程“TcpServer.pro”中新增C++類別檔案“tcpclientsocket.h”及“tcpclientsocket.cpp”,TcpClientSocket 繼承自QTcpSocket,建立一個TCP通訊端,以便在伺服器端實現與使用者端程式的通訊。
標頭檔案“tcpclientsocket.h”的具體程式碼如下:

#include <QTcpSocket> 
#include <QObject> 
class TcpClientSocket : public QTcpSocket 
{ 
Q_OBJECT //新增宏(Q_OBJECT)是為了實現訊號與槽的通訊 
public: 
TcpClientSocket(QObject *parent=0); 
signals: 
void updateClients(QString,int); 
void disconnected(int); 
protected slots: 
void dataReceived(); 
void slotDisconnected(); 
}; 

(3)在原始檔“tcpclientsocket.cpp”中,建構函式(TcpClientSocket)的內容(指定了訊號與槽的
連線關係)如下:

#include "tcpclientsocket.h" 
TcpClientSocket::TcpClientSocket(QObject *parent) 
{ 
connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived())); // readyRead()是QIODevice的signal,由 QTcpSocket繼承而來。QIODevice是所有輸入/輸出裝置的一個抽象類,其中定義了基本的介面,在Qt中, QTcpSocket也被看成一個QIODevice,readyRead()訊號在有資料到來時發出。  
connect(this,SIGNAL(disconnected()),this,SLOT(slotDisconnected())); // disconnected()訊號在斷開連線時發出。 
} 

在原始檔“tcpclientsocket.cpp”中,dataReceived()函數的具體程式碼如下:

void TcpClientSocket::dataReceived() 
{ 
while(bytesAvailable()>0) 
{ 
int length = bytesAvailable(); 
char buf[1024]; 
read(buf,length); 
QString msg=buf; 
emit updateClients(msg,length); 
} 
}

在原始檔“tcpclientsocket.cpp”中,槽函數slotDisconnected()的具體程式碼如下:

void TcpClientSocket::slotDisconnected() 
{ 
emit disconnected(this->socketDescriptor()); 
} 

(4)在工程“TcpServer.pro”中新增C++類別檔案“server.h”及“server.cpp”,Server繼承自QTcpServer,實現一 個TCP協定的伺服器。利用QTcpServer,開發者可以監聽到指定埠的TCP連線。其具體程式碼如下:

#include <QTcpServer> 
#include <QObject> 
#include "tcpclientsocket.h" //包含TCP的通訊端 
class Server : public QTcpServer 
{ 
Q_OBJECT 
//新增宏(Q_OBJECT)是為了實現訊號與槽的通訊 
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”中,建構函式(Server)的具體內容如下:

#include "server.h" 
Server::Server(QObject *parent,int port):QTcpServer(parent) 
{ 
listen(QHostAddress::Any,port); 
}

其中,listen(QHostAddress::Any,port)在指定的埠對任意地址進行監聽。
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”中,當出現一個新的連線時,QTcpSever觸發incomingConnection()函數,引數

socketDescriptor指定了連線的Socket描述符,其具體程式碼如下:

void Server::incomingConnection(int socketDescriptor) 
{ 
TcpClientSocket *tcpClientSocket=new TcpClientSocket(this); //建立一個新的TcpClientSocket與使用者端通訊。  
connect(tcpClientSocket,SIGNAL(updateClients(QString,int), this,SLOT(updateClients(QString,int))); //連線TcpClientSocket的updateClients訊號。  
connect(tcpClientSocket,SIGNAL(disconnected(int)),this, SLOT(slotDisconnected(int))); //連線 TcpClientSocket的disconnected訊號。 
tcpClientSocket->setSocketDescriptor(socketDescriptor); //將新建立的TcpClient Socket的通訊端描述符指定為引數socketDescriptor。 
tcpClientSocketList.append(tcpClientSocket); //將tcpClientSocket加入使用者端通訊端列表以便管理。 
}

在原始檔“server.cpp”中,updateClients()函數將任意使用者端發來的資訊進行廣播,保證聊天室的所有成員均能看到其他人的發言。其具體程式碼如下:

void Server::updateClients(QString msg,int length) 
{ 
emit updateServer(msg,length); //發出updateServer訊號,用來通知伺服器對話方塊更新相應的顯示狀態。
for(int i=0;i<tcpClientSocketList.count();i++) //實現資訊的廣播,tcpClientSocketList中儲存了所有與伺服器相連的TcpClientSocket物件。 
{ 
QTcpSocket *item = tcpClientSocketList.at(i); 
if(item->write(msg.toLatin1(),length)!=length) 
{ 
continue; 
} 
} 
} 

在原始檔“server.cpp”中,slotDisconnected()函數實現從tcpClientSocketList列表中將斷開連線的
TcpClientSocket物件刪除的功能。其具體程式碼如下:

void Server::slotDisconnected(int descriptor) 
{ 
for(int i=0;i<tcpClientSocketList.count();i++) 
{ 
QTcpSocket *item = tcpClientSocketList.at(i); 
if(item->socketDescriptor()==descriptor) 
{ 
tcpClientSocketList.removeAt(i); 
return; 
} 
}
return; 
}

(6)在標頭檔案“tcpserver.h”中新增如下內容:

#include "server.h" 
private: 
int port; 
Server *server; 
public slots: 
void slotCreateServer(); 
void updateServer(QString,int);

(7)在原始檔“tcpserver.cpp”中,在建構函式中新增如下程式碼:

port=8010; 
PortLineEdit->setText(QString::number(port)); 
connect(CreateBtn,SIGNAL(clicked()),this,SLOT(slotCreateServer())); 

其中,槽函數slotCreateServer()用於建立一個TCP伺服器,具體內容如下:

void TcpServer::slotCreateServer() 
{ 
server = new Server(this,port); //建立一個Server物件 
connect(server,SIGNAL(updateServer(QString,int)),this, 
SLOT(updateServer(QString,int))); 
CreateBtn->setEnabled(false); 
} 

槽函數updateServer()用於更新伺服器上的資訊顯示,具體內容如下:

void TcpServer::updateServer(QString msg,int length) 
{ 
ContentListWidget->addItem(msg.left(length)); 
}

(8)此時,工程中新增了很多檔案,工程檔案中的內容已經被改變,需要重新在工程檔案
“TcpServer.pro”中新增:

QT += network 

此時,執行伺服器端工程“TcpServer.pro”編譯通過。單擊“建立聊天室”按鈕,便開通了一個TCP聊天室的伺服器,如下圖所示:

四、TCP使用者端程式設計範例

TCP使用者端程式設計具體步驟如下:

建立工程“TcpClient.pro”,檔案程式碼如下。

(1)在標頭檔案“tcpclient.h”中,TcpClient類繼承自QDialog類,宣告了需要的各種控制元件,其具體程式碼如下:

#include <QDialog> 
#include <QListWidget> 
#include <QLineEdit> 
#include <QPushButton> 
#include <QLabel> 
#include <QGridLayout> 
class TcpClient : public QDialog 
{ 
Q_OBJECT 
public: 
TcpClient(QWidget *parent = 0,Qt::WindowFlags f=0); 
~TcpClient(); 
private: 
QListWidget *contentListWidget; 
QLineEdit *sendLineEdit; 
QPushButton *sendBtn; 
QLabel *userNameLabel; 
QLineEdit *userNameLineEdit; 
QLabel *serverIPLabel; 
QLineEdit *serverIPLineEdit; 
QLabel *portLabel; 
QLineEdit *portLineEdit; 
QPushButton *enterBtn; 
QGridLayout *mainLayout; 
};

(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("傳送")); 
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”中新增如下語句:

QT += network 

(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> 

在其建構函式中新增如下程式碼:

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);

在以上程式碼中,槽函數slotEnter()實現了進入和離開聊天室的功能。具體程式碼如下:

void TcpClient::slotEnter()
{
    if(!status)    //status表示當前的狀態,true表示已經進入聊天室,false表示已經離開聊天室。 這裡根據status的狀態決定是執行「進入」還是「離開」的操作。
    {
        /* 完成輸入合法性檢驗 */
        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();
        /* 建立了一個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伺服器端連線,連線成功後發出connected() 訊號
        status=true;
    }
    else
    {
        int length=0;
        QString msg=userName+tr(":Leave Chat Room");//構造一條離開聊天室的訊息。
        if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg.length())                                                //通知伺服器端以上 構造的訊息
        {
            return;
        }
        tcpSocket->disconnectFromHost();                    //與伺服器斷開連線,斷開連線後發出disconnected()訊號。
        status=false; //將status狀態復位
    }
}

在原始檔“tcpclient.cpp”中,槽函數slotConnected()為connected()訊號的響應槽,當與伺服器連線成功後,使用者端構造一條進入聊天室的訊息,並通知伺服器。其具體程式碼如下:

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”中,槽函數slotSend()的具體程式碼如下:

void TcpClient::slotSend() 
{ 
if(sendLineEdit->text()=="") 
{ 
return; 
}
QString msg=userName+":"+sendLineEdit->text(); 
tcpSocket->write(msg.toLatin1(),msg.length()); 
sendLineEdit->clear(); 
}

在原始檔“tcpclient.cpp”中,槽函數slotDisconnected()的具體內容如下:

void TcpClient::slotDisconnected() 
{ 
sendBtn->setEnabled(false); 
enterBtn->setText(tr("進入聊天室")); 
} 

當有資料到來時,觸發原始檔“tcpclient.cpp”的dataReceived()函數,從通訊端中將有效資料取出並顯示,其程式碼如下:

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”工程,結果如下圖所示:

最後,同時執行伺服器和使用者端程式,執行結果如下圖所示,這裡演示的是系統中登入了兩 個使用者的狀態。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com