QT基于TCP實(shí)現(xiàn)網(wǎng)絡(luò)聊天室程序
本文實(shí)例為大家分享了QT學(xué)習(xí):基于TCP的網(wǎng)絡(luò)聊天室程序,供大家參考,具體內(nèi)容如下
TCP與UDP的差別如圖:
一、TCP工作原理
如下圖所示,TCP能夠?yàn)閼?yīng)用程序提供可靠的通信連接,使一臺(tái)計(jì)算機(jī)發(fā)出的字節(jié)流無(wú)差錯(cuò) 地送達(dá)網(wǎng)絡(luò)上的其他計(jì)算機(jī)。因此,對(duì)可靠性要求高的數(shù)據(jù)通信系統(tǒng)往往使用TCP傳輸數(shù)據(jù),但在正式收發(fā)數(shù)據(jù)前,通信雙方必須首先建立連接。
二、TCP編程模型
下面介紹基于TCP的經(jīng)典編程模型,TCP客戶端與服務(wù)器間的交互時(shí)序如下圖所示:
三、TCP服務(wù)器端編程實(shí)例
TCP服務(wù)器端的具體實(shí)現(xiàn)如下:
建立工程TcpServer.pro,文件代碼如下。
(1)頭文件“tcpserver.h”中聲明了需要的各種控件,TcpServer繼承自QDialog,實(shí)現(xiàn)了服務(wù)器端的對(duì)話框顯示與控制。其具體代碼如下:
#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類的構(gòu)造函數(shù)主要實(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)服務(wù)器端界面如下圖所示:
以上完成了服務(wù)器端界面的設(shè)計(jì),下面將詳細(xì)完成聊天室的服務(wù)器端功能。
(1)在工程文件“TcpServer.pro”中添加如下語(yǔ)句:
QT += network
(2)在工程“TcpServer.pro”中添加C++類文件“tcpclientsocket.h”及“tcpclientsocket.cpp”,TcpClientSocket 繼承自QTcpSocket,創(chuàng)建一個(gè)TCP套接字,以便在服務(wù)器端實(shí)現(xiàn)與客戶端程序的通信。
頭文件“tcpclientsocket.h”的具體代碼如下:
#include <QTcpSocket>? #include <QObject>? class TcpClientSocket : public QTcpSocket? {? Q_OBJECT //添加宏(Q_OBJECT)是為了實(shí)現(xiàn)信號(hào)與槽的通信? public:? TcpClientSocket(QObject *parent=0);? signals:? void updateClients(QString,int);? void disconnected(int);? protected slots:? void dataReceived();? void slotDisconnected();? };?
(3)在源文件“tcpclientsocket.cpp”中,構(gòu)造函數(shù)(TcpClientSocket)的內(nèi)容(指定了信號(hào)與槽的
連接關(guān)系)如下:
#include "tcpclientsocket.h"? TcpClientSocket::TcpClientSocket(QObject *parent)? {? connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived())); // readyRead()是QIODevice的signal,由 QTcpSocket繼承而來(lái)。QIODevice是所有輸入/輸出設(shè)備的一個(gè)抽象類,其中定義了基本的接口,在Qt中, QTcpSocket也被看成一個(gè)QIODevice,readyRead()信號(hào)在有數(shù)據(jù)到來(lái)時(shí)發(fā)出。 ? connect(this,SIGNAL(disconnected()),this,SLOT(slotDisconnected())); // disconnected()信號(hào)在斷開(kāi)連接時(shí)發(fā)出。? }?
在源文件“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,實(shí)現(xiàn)一 個(gè)TCP協(xié)議的服務(wù)器。利用QTcpServer,開(kāi)發(fā)者可以監(jiān)聽(tīng)到指定端口的TCP連接。其具體代碼如下:
#include <QTcpServer>? #include <QObject>? #include "tcpclientsocket.h" //包含TCP的套接字? class Server : public QTcpServer? {? Q_OBJECT? //添加宏(Q_OBJECT)是為了實(shí)現(xiàn)信號(hào)與槽的通信? 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)在指定的端口對(duì)任意地址進(jìn)行監(jiān)聽(tīng)。
QHostAddress定義了幾種特殊的IP地址,如QHostAddress::Null表示一個(gè)空地址;
QHostAddress::LocalHost表示IPv4的本機(jī)地址127.0.0.1;
QHostAddress::LocalHostIPv6表示IPv6的本機(jī)地址;
QHostAddress::Broadcast表示廣播地址255.255.255.255;
QHostAddress::Any表示IPv4的任意地址0.0.0.0;
QHostAddress::AnyIPv6表示IPv6的任意地址。
在源文件“server.cpp”中,當(dāng)出現(xiàn)一個(gè)新的連接時(shí),QTcpSever觸發(fā)incomingConnection()函數(shù),參數(shù)
socketDescriptor指定了連接的Socket描述符,其具體代碼如下:
void Server::incomingConnection(int socketDescriptor)? {? TcpClientSocket *tcpClientSocket=new TcpClientSocket(this); //創(chuàng)建一個(gè)新的TcpClientSocket與客戶端通信。 ? connect(tcpClientSocket,SIGNAL(updateClients(QString,int), this,SLOT(updateClients(QString,int))); //連接TcpClientSocket的updateClients信號(hào)。 ? connect(tcpClientSocket,SIGNAL(disconnected(int)),this, SLOT(slotDisconnected(int))); //連接 TcpClientSocket的disconnected信號(hào)。? tcpClientSocket->setSocketDescriptor(socketDescriptor); //將新創(chuàng)建的TcpClient Socket的套接字描述符指定為參數(shù)socketDescriptor。? tcpClientSocketList.append(tcpClientSocket); //將tcpClientSocket加入客戶端套接字列表以便管理。? }
在源文件“server.cpp”中,updateClients()函數(shù)將任意客戶端發(fā)來(lái)的信息進(jìn)行廣播,保證聊天室的所有成員均能看到其他人的發(fā)言。其具體代碼如下:
void Server::updateClients(QString msg,int length)? {? emit updateServer(msg,length); //發(fā)出updateServer信號(hào),用來(lái)通知服務(wù)器對(duì)話框更新相應(yīng)的顯示狀態(tài)。 for(int i=0;i<tcpClientSocketList.count();i++) //實(shí)現(xiàn)信息的廣播,tcpClientSocketList中保存了所有與服務(wù)器相連的TcpClientSocket對(duì)象。? {? QTcpSocket *item = tcpClientSocketList.at(i);? if(item->write(msg.toLatin1(),length)!=length)? {? continue;? }? }? }?
在源文件“server.cpp”中,slotDisconnected()函數(shù)實(shí)現(xiàn)從tcpClientSocketList列表中將斷開(kāi)連接的
TcpClientSocket對(duì)象刪除的功能。其具體代碼如下:
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”中添加如下內(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)建一個(gè)TCP服務(wù)器,具體內(nèi)容如下:
void TcpServer::slotCreateServer()? {? server = new Server(this,port); //創(chuàng)建一個(gè)Server對(duì)象? connect(server,SIGNAL(updateServer(QString,int)),this,? SLOT(updateServer(QString,int)));? CreateBtn->setEnabled(false);? }?
槽函數(shù)updateServer()用于更新服務(wù)器上的信息顯示,具體內(nèi)容如下:
void TcpServer::updateServer(QString msg,int length)? {? ContentListWidget->addItem(msg.left(length));? }
(8)此時(shí),工程中添加了很多文件,工程文件中的內(nèi)容已經(jīng)被改變,需要重新在工程文件
“TcpServer.pro”中添加:
QT += network
此時(shí),運(yùn)行服務(wù)器端工程“TcpServer.pro”編譯通過(guò)。單擊“創(chuàng)建聊天室”按鈕,便開(kāi)通了一個(gè)TCP聊天室的服務(wù)器,如下圖所示:
四、TCP客戶端編程實(shí)例
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("發(fā)送"));? userNameLabel = new QLabel(tr("用戶名:"));? userNameLineEdit = new QLineEdit;? serverIPLabel = new QLabel(tr("服務(wù)器地址:"));? serverIPLineEdit = new QLineEdit;? portLabel = new QLabel(tr("端口:"));? portLineEdit = new QLineEdit;? enterBtn= new QPushButton(tr("進(jìn)入聊天室"));? 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)客戶端界面如下圖所示:
以上完成了客戶端界面的設(shè)計(jì),下面將完成客戶端的真正聊天功能。
(1)在客戶端工程文件“TcpClient.pro”中添加如下語(yǔ)句:
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>?
在其構(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()實(shí)現(xiàn)了進(jìn)入和離開(kāi)聊天室的功能。具體代碼如下:
void TcpClient::slotEnter() { ? ? if(!status)?? ?//status表示當(dāng)前的狀態(tài),true表示已經(jīng)進(jìn)入聊天室,false表示已經(jīng)離開(kāi)聊天室。 這里根據(jù)status的狀態(tài)決定是執(zhí)行“進(jìn)入”還是“離開(kāi)”的操作。 ? ? { ?? ??? ?/* 完成輸入合法性檢驗(yàn) */ ? ? ? ? QString ip = serverIPLineEdit->text(); ? ? ? ? if(!serverIP->setAddress(ip))//用來(lái)判斷給定的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)建了一個(gè)QTcpSocket類對(duì)象,并將信號(hào)/槽連接起來(lái) */ ? ? ? ? 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服務(wù)器端連接,連接成功后發(fā)出connected() 信號(hào) ? ? ? ? status=true; ? ? } ? ? else ? ? { ? ? ? ? int length=0; ? ? ? ? QString msg=userName+tr(":Leave Chat Room");//構(gòu)造一條離開(kāi)聊天室的消息。 ? ? ? ? if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg.length())?? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ?//通知服務(wù)器端以上 構(gòu)造的消息 ? ? ? ? { ? ? ? ? ? ? return; ? ? ? ? } ? ? ? ? tcpSocket->disconnectFromHost();?? ??? ??? ??? ??? ?//與服務(wù)器斷開(kāi)連接,斷開(kāi)連接后發(fā)出disconnected()信號(hào)。 ? ? ? ? status=false; //將status狀態(tài)復(fù)位 ? ? } }
在源文件“tcpclient.cpp”中,槽函數(shù)slotConnected()為connected()信號(hào)的響應(yīng)槽,當(dāng)與服務(wù)器連接成功后,客戶端構(gòu)造一條進(jìn)入聊天室的消息,并通知服務(wù)器。其具體代碼如下:
void TcpClient::slotConnected()? {? sendBtn->setEnabled(true);? enterBtn->setText(tr("離開(kāi)"));? 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("進(jìn)入聊天室"));? }?
當(dāng)有數(shù)據(jù)到來(lái)時(shí),觸發(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)此時(shí)運(yùn)行客戶端“TcpClient.pro”工程,結(jié)果如下圖所示:
最后,同時(shí)運(yùn)行服務(wù)器和客戶端程序,運(yùn)行結(jié)果如下圖所示,這里演示的是系統(tǒng)中登錄了兩 個(gè)用戶的狀態(tài)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C++ LeetCode1805字符串不同整數(shù)數(shù)目
這篇文章主要為大家介紹了C++ LeetCode1805字符串不同整數(shù)數(shù)目,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12基于opencv實(shí)現(xiàn)車(chē)道線檢測(cè)
這篇文章主要為大家詳細(xì)介紹了基于opencv實(shí)現(xiàn)車(chē)道線檢測(cè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-02-02VS?Code+msys2配置Windows系統(tǒng)下C/C++開(kāi)發(fā)環(huán)境
我們?cè)趙indows10中使用VS Code做C++程序開(kāi)發(fā)過(guò)程中,需要安裝MSYS2和MinGW,下面這篇文章主要給大家介紹了關(guān)于VS?Code+msys2配置Windows系統(tǒng)下C/C++開(kāi)發(fā)環(huán)境的相關(guān)資料,需要的朋友可以參考下2022-12-12C++實(shí)現(xiàn)賓館房間管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)賓館房間管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05