🙌秋名山碼民的主頁
😂oi退役選手,Java、大數據、單片機、IoT均有所涉獵,熱愛技術,技術無罪
🎉歡迎關注🔎點贊👍收藏??留言📝
獲取源碼,添加WX
目錄
- 前言
- 一、主界面和聊天窗口
- 二、UDP聊天
- 三、TCP文件傳輸
- server類
- Clint類
- 最后
前言
QQ是一款優秀的聊天軟件,本文將提供主要代碼和思路來實現一個類似于QQ群聊的網絡聊天軟件,大致有以下倆個功能:
采用qt5編寫,實現基于UDP的文本聊天功能,和基于TCP的文件傳輸功能
基本聊天會話功能
通過獲取每一個用戶運行該程序的時候,發送廣播來實現,不僅用戶登錄的時候進行廣播,退出、發送信息的時候都使用UDP廣播來告知用戶,每個用戶的聊天窗口為一個端點
文件傳輸功能實現
文件的傳輸采用TCP來實現,用C/S架構
- 主界面選中要發送的文件,單擊傳輸,打開發送文件對話框
- 當用戶單擊發送的時候,程序通過UDP廣播給接收端,接收端在收到文件的UDP消息后,彈出提示框,是否接收
- 如果接收,先創建一個TCP通信客戶端,雙方進行TCP通信,如果拒絕,再通過UDP廣播告知發送端
一、主界面和聊天窗口
#ifndef DRAWER_H
#define DRAWER_H#include <QToolBox>
#include <QToolButton>
#include <QWidget>
#include "myqq.h"class Drawer : public QToolBox
{
public:Drawer();
private:QToolButton *toolBtn1;//聊天對象窗口指針QWidget *chatWidget1;private slots:// 顯示聊天對象窗口void showChatWidget1();MyQQ *myqq;};#endif // DRAWER_H
setWindowTitle(tr("My QQ v01"));setWindowIcon(QPixmap(":/images/R-C.jpg"));toolBtn1 = new QToolButton;toolBtn1->setText(tr("冰雪奇緣"));toolBtn1->setIcon(QPixmap(":/images/girl1.jpg"));toolBtn1->setAutoRaise(true); //設置toolBtn1在顯示時自動提升,使得按鈕外觀更加立體感。toolBtn1->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); //設置toolBtn1的按鈕樣式為圖標在文本旁邊的形式。// 將顯示函數與抽屜盒中相對應的用戶按鈕進行綁定//connect(toolBtn1,SIGNAL(clicked()),this,SLOT(showChatWidget1()));//connect(toolBtn1, &QToolButton::clicked, this, &QToolBox::showChatWidget1);connect(toolBtn1, &QToolButton::clicked, this, &Drawer::showChatWidget1);
二、UDP聊天
原理:如果要進行聊天,則首先要獲取所有登錄用戶的信息,這個功能是通過在每一個用戶運行該程序時發送廣播實現的,不僅用戶登錄時要進行廣播,而且在用戶退出、發送消息時都使用UDP廣播來告知所有用戶。
#ifndef SERVER_H
#define SERVER_H#include <QDialog>
#include <QFile>
#include <QTcpServer>
#include <QTime>namespace Ui {
class Server;
}class Server : public QDialog
{Q_OBJECTpublic:explicit Server(QWidget *parent = nullptr);~Server();void initSrv(); // 初始化服務器void refused(); // 關閉服務器protected:void closeEvent(QCloseEvent *);void updClntProgress(qint64 numBytes);private slots:void on_Server_accepted();void sendMsg(); //發送數據void updclntProgress(qint64 numBytes); // 更新進度條void on_sOpenBtn_clicked();void on_sSendBtn_clicked();void on_sCloseBtn_clicked();private:Ui::Server *ui;qint16 tPort;QTcpServer *tSrv;QString fileName;QString theFileName;QFile *locFile; //待發送的文件qint64 totalBytes; //總共要發送的qint64 bytesWritten; //已發送的qint64 bytesTobeWrite; //待發送的qint64 payloadSize; //被初始化為一個常量QByteArray outBlock; // 緩存一次的QTcpSocket *clntConn;QTime time;signals:void sendFileName(QString fileName);
};#endif // SERVER_H
#include "server.h"
#include "ui_server.h"#include <QFile>
#include<QTcpServer>
#include<QTcpSocket>
#include<QMessageBox>
#include <QFileDialog>
#include<QDebug>Server::Server(QWidget *parent) :QDialog(parent),ui(new Ui::Server)
{ui->setupUi(this);setFixedSize(400,207);tPort = 5555;tSrv = new QTcpServer(this);connect(tSrv,&QTcpServer::newConnection,this,&Server::sendMsg);initSrv();
}void Server::initSrv()
{payloadSize = 64*1024;totalBytes = 0;bytesWritten = 0;ui->sOpenBtn->setEnabled(true);ui->sSendBtn->setEnabled(false);tSrv->close();
}// 發送數據
void Server::sendMsg()
{ui->sSendBtn->setEnabled(false);clntConn = tSrv->nextPendingConnection();connect(clntConn,SIGNAL(bytesWritten(gint64)),this,SLOT(updCIntProgress(qint64)));ui->sStatusLabel->setText(tr("開始傳送文件 號1 !").arg(theFileName));locFile = new QFile(fileName);if(!locFile->open((QFile::ReadOnly))){QMessageBox::warning(this,tr("應用程序"), tr("無法讀取文件號1: n各2").arg(fileName).arg(locFile->errorString()));return;}totalBytes = locFile->size();QDataStream sendOut(&outBlock, QIODevice::WriteOnly);sendOut.setVersion(QDataStream::Qt_4_7);time.start();QString curFile = fileName.right(fileName.size() - fileName.lastIndexOf('/') - 1);sendOut << qint64(0) << qint64((outBlock.size() - sizeof(qint64)*2));bytesTobeWrite = totalBytes - clntConn->write(outBlock);outBlock.reserve(0);
}// 更新進度條
void Server::updClntProgress(qint64 numBytes)
{// 防止傳輸大文件產生凍結qApp->processEvents();bytesWritten += (int)numBytes;if(bytesTobeWrite > 0){outBlock = locFile->read(qMin(bytesTobeWrite,payloadSize));bytesTobeWrite -= (int)clntConn->write(outBlock);outBlock.resize(0);} else{locFile->close();}ui->progressBar->setMaximum(totalBytes);ui->progressBar->setValue(bytesWritten);float useTime = time.elapsed();double speed = bytesWritten / useTime;ui->sStatusLabel->setText(tr("已發送 %1MB (%2MB/s)\n 共%3MB 已用時:%4s\n 估計剩余時間:%5秒").arg(bytesWritten/(1024*1024)).arg(bytesWritten / (1024*1024)).arg(speed*1000 / (1024*1024),0,'f',0).arg(totalBytes / (1024 * 1024)).arg(useTime/1000,0,'f',0).arg(totalBytes/speed/1000 - useTime/1000,0,'f',0));if(bytesWritten == totalBytes){locFile->close();tSrv->close();ui->sStatusLabel->setText(tr("傳送文件 %1 成功").arg(theFileName));}}
Server::~Server()
{delete ui;
}void Server::on_sOpenBtn_clicked()
{fileName = QFileDialog::getOpenFileName(this);if(!fileName.isEmpty()){theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1);ui->sStatusLabel->setText(tr("要發送的文件為:%1").arg(theFileName));ui->sOpenBtn->setEnabled(false);ui->sSendBtn->setEnabled(true);}
}void Server::on_sSendBtn_clicked()
{if(!tSrv->listen(QHostAddress::Any,tPort)){qDebug() << tSrv ->errorString();close();return;}ui->sStatusLabel->setText("等待……");emit sendFileName(theFileName);
}void Server::on_sCloseBtn_clicked()
{if(tSrv->isListening()){tSrv->close();if(locFile->isOpen())locFile->close();clntConn->abort();}close();
}void Server::closeEvent(QCloseEvent *)
{on_sCloseBtn_clicked();
}void Server::refused()
{tSrv->close();ui->sStatusLabel->setText(tr("對方拒絕!"));
}
三、TCP文件傳輸
文件的傳輸采用TCP來實現,用C/S(客戶端/服務器)方式,創建倆個新類,client和server類
server類
#ifndef SERVER_H
#define SERVER_H#include <QDialog>
#include <QFile>
#include <QTcpServer>
#include <QTime>namespace Ui {
class Server;
}class Server : public QDialog
{Q_OBJECTpublic:explicit Server(QWidget *parent = nullptr);~Server();void initSrv(); // 初始化服務器void refused(); // 關閉服務器protected:void closeEvent(QCloseEvent *);void updClntProgress(qint64 numBytes);private slots:void on_Server_accepted();void sendMsg(); //發送數據void updclntProgress(qint64 numBytes); // 更新進度條void on_sOpenBtn_clicked();void on_sSendBtn_clicked();void on_sCloseBtn_clicked();private:Ui::Server *ui;qint16 tPort;QTcpServer *tSrv;QString fileName;QString theFileName;QFile *locFile; //待發送的文件qint64 totalBytes; //總共要發送的qint64 bytesWritten; //已發送的qint64 bytesTobeWrite; //待發送的qint64 payloadSize; //被初始化為一個常量QByteArray outBlock; // 緩存一次的QTcpSocket *clntConn;QTime time;signals:void sendFileName(QString fileName);
};#endif // SERVER_H
#include "server.h"
#include "ui_server.h"#include <QFile>
#include<QTcpServer>
#include<QTcpSocket>
#include<QMessageBox>
#include <QFileDialog>
#include<QDebug>Server::Server(QWidget *parent) :QDialog(parent),ui(new Ui::Server)
{ui->setupUi(this);setFixedSize(400,207);tPort = 5555;tSrv = new QTcpServer(this);connect(tSrv,&QTcpServer::newConnection,this,&Server::sendMsg);initSrv();
}void Server::initSrv()
{payloadSize = 64*1024;totalBytes = 0;bytesWritten = 0;ui->sOpenBtn->setEnabled(true);ui->sSendBtn->setEnabled(false);tSrv->close();
}// 發送數據
void Server::sendMsg()
{ui->sSendBtn->setEnabled(false);clntConn = tSrv->nextPendingConnection();connect(clntConn,SIGNAL(bytesWritten(gint64)),this,SLOT(updCIntProgress(qint64)));ui->sStatusLabel->setText(tr("開始傳送文件 號1 !").arg(theFileName));locFile = new QFile(fileName);if(!locFile->open((QFile::ReadOnly))){QMessageBox::warning(this,tr("應用程序"), tr("無法讀取文件號1: n各2").arg(fileName).arg(locFile->errorString()));return;}totalBytes = locFile->size();QDataStream sendOut(&outBlock, QIODevice::WriteOnly);sendOut.setVersion(QDataStream::Qt_4_7);time.start();QString curFile = fileName.right(fileName.size() - fileName.lastIndexOf('/') - 1);sendOut << qint64(0) << qint64((outBlock.size() - sizeof(qint64)*2));bytesTobeWrite = totalBytes - clntConn->write(outBlock);outBlock.reserve(0);
}// 更新進度條
void Server::updClntProgress(qint64 numBytes)
{// 防止傳輸大文件產生凍結qApp->processEvents();bytesWritten += (int)numBytes;if(bytesTobeWrite > 0){outBlock = locFile->read(qMin(bytesTobeWrite,payloadSize));bytesTobeWrite -= (int)clntConn->write(outBlock);outBlock.resize(0);} else{locFile->close();}ui->progressBar->setMaximum(totalBytes);ui->progressBar->setValue(bytesWritten);float useTime = time.elapsed();double speed = bytesWritten / useTime;ui->sStatusLabel->setText(tr("已發送 %1MB (%2MB/s)\n 共%3MB 已用時:%4s\n 估計剩余時間:%5秒").arg(bytesWritten/(1024*1024)).arg(bytesWritten / (1024*1024)).arg(speed*1000 / (1024*1024),0,'f',0).arg(totalBytes / (1024 * 1024)).arg(useTime/1000,0,'f',0).arg(totalBytes/speed/1000 - useTime/1000,0,'f',0));if(bytesWritten == totalBytes){locFile->close();tSrv->close();ui->sStatusLabel->setText(tr("傳送文件 %1 成功").arg(theFileName));}}
Server::~Server()
{delete ui;
}void Server::on_sOpenBtn_clicked()
{fileName = QFileDialog::getOpenFileName(this);if(!fileName.isEmpty()){theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1);ui->sStatusLabel->setText(tr("要發送的文件為:%1").arg(theFileName));ui->sOpenBtn->setEnabled(false);ui->sSendBtn->setEnabled(true);}
}void Server::on_sSendBtn_clicked()
{if(!tSrv->listen(QHostAddress::Any,tPort)){qDebug() << tSrv ->errorString();close();return;}ui->sStatusLabel->setText("等待……");emit sendFileName(theFileName);
}void Server::on_sCloseBtn_clicked()
{if(tSrv->isListening()){tSrv->close();if(locFile->isOpen())locFile->close();clntConn->abort();}close();
}void Server::closeEvent(QCloseEvent *)
{on_sCloseBtn_clicked();
}void Server::refused()
{tSrv->close();ui->sStatusLabel->setText(tr("對方拒絕!"));
}
Clint類
TCP客戶端類,用于接收文件。
#ifndef CLIENT_H
#define CLIENT_H#include <QDialog>
#include <QHostAddress>
#include <QFile>
#include <QTime>
#include <QTcpSocket>namespace Ui {
class client;
}class client : public QDialog
{Q_OBJECTpublic:explicit client(QWidget *parent = nullptr);~client();void setHostAddr(QHostAddress addr);void setFileName(QString name);protected:void closeEvent(QCloseEvent *);private:Ui::client *ui;QTcpSocket *tClnt;quint16 blockSize;QHostAddress hostAddr;qint16 tPort;qint64 totalBytes;qint64 bytesReceived;qint64 fileNameSize;QString fileName;QFile *locFile;QByteArray inBlock;QTime time;private slots:void newConn(); // 連接到服務器void readMsg(); // 讀取文件數據
// void displayErr(QAbstractSocket::SocketError); // 顯示錯誤信息void on_cCancleBtn_clicked();void on_cCloseBtn_clicked();
};#endif // CLIENT_H
#include "client.h"
#include "ui_client.h"
#include <QDebug>
#include <QMessageBox>
#include <QTime>client::client(QWidget *parent) :QDialog(parent),ui(new Ui::client)
{ui->setupUi(this);setFixedSize(400,190);totalBytes = 0;bytesReceived = 0;fileNameSize = 0;tClnt = new QTcpSocket(this);tPort = 5555;connect(tClnt, &QTcpSocket::readyRead, this, &client::readMsg);}// 連接服務器
void client::newConn()
{blockSize = 0;tClnt->abort();tClnt->connectToHost(hostAddr,tPort);time.start();
}// 發送文件
void client::readMsg()
{QDataStream in(tClnt);in.setVersion(QDataStream::Qt_4_7);float useTime = time.elapsed();if (bytesReceived <= sizeof(qint64)*2){if ((tClnt->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize == 0)){in>>totalBytes>>fileNameSize;bytesReceived += sizeof(qint64)*2;}if((tClnt->bytesAvailable() >= fileNameSize) && (fileNameSize != 0)){in>>fileName;bytesReceived +=fileNameSize;if(!locFile->open(QFile::WriteOnly)){QMessageBox::warning(this,tr("應用程序"),tr("無法讀取文件%1:\n%2.").arg(fileName).arg(locFile->errorString()));}return;}else{return;}}if (bytesReceived < totalBytes){bytesReceived += tClnt->bytesAvailable();inBlock = tClnt->readAll();locFile->write(inBlock);inBlock.resize(0);}ui->progressBar->setMaximum(totalBytes);ui->progressBar->setValue(bytesReceived);double speed = bytesReceived / useTime;ui->label_2->setText(tr("已接收 %1MB (%2MB/s)\n 共%3MB 已用時:%4s\n 估計剩余時間:%5秒").arg(bytesReceived/(1024*1024)).arg(speed*1000 / (1024*1024),0,'f',0).arg(totalBytes / (1024 * 1024)).arg(useTime/1000,0,'f',0).arg(totalBytes/speed/1000 - useTime/1000,0,'f',0));if(bytesReceived == totalBytes){locFile->close();tClnt->close();ui->label->setText(tr("接收文件 %1 成功").arg(fileName));}
}
client::~client()
{delete ui;
}void client::on_cCancleBtn_clicked()
{tClnt->abort();if(locFile->isOpen())locFile->close();}void client::on_cCloseBtn_clicked()
{tClnt->abort();if(locFile->isOpen())locFile->close();close();
}void client::closeEvent(QCloseEvent *)
{on_cCloseBtn_clicked();
}
最后
至此已完成,讀者還可根據自己所需來添加一些拓展功能,更改字體、字號和顏色等等……如果本文對你有所幫助,還請三連支持一下博主!