C++Qt局域网聊天工具 开发记录
更新于:A.D.2022.06.27
配置环境
配置Qt开发环境:https://www.qt.io/download
准备工作
建立GitHub仓库 https://github.com/Shiokiri/Qt-LAN-Chat
阅读Qt官方文档 https://doc.qt.io/qt-6/classes.html
开发过程
参考了《Qt及Qt Quick开发实战精解》书籍中的实现方法。
配置.pro文件
首先配置qmake的相关配置
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
ICONS +=
SOURCES += \
main.cpp \
tcpclient.cpp \
tcpserver.cpp \
widget.cpp
HEADERS += \
tcpclient.h \
tcpserver.h \
widget.h
FORMS += \
tcpclient.ui \
tcpserver.ui \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
QT += network
RESOURCES += \
Images.qrc
绘制UI
通过Qt Creator的设计模式进行绘制,参考上述书籍。
选取ICON图标
在 iconfont-阿里巴巴矢量图标库 选取图标。
编写头文件
变量的命名和使用需要和设计模式中命名一致,参考上述书籍。
部分槽函数的建立需要通过设计模式进行,参考上述书籍。
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QColorDialog>
#include <QTextCharFormat>
class QUdpSocket;
class TcpServer;
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
enum MessageType {
Message, // 消息
NewParticipant, // 新用户加入
ParticipantLeft, // 用户退出
FileName, // 文件名
Refuse, // 拒绝接收文件
};
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget * parent = nullptr);
~Widget();
protected:
void newParticipant(QString username,
QString localHostName,
QString ipAddress);
void participantLeft(QString userName,
QString localHostName,
QString time);
void sendMessage(MessageType type,
QString serverAddress = "");
QString getIP();
QString getUserName();
QString getMessage();
void hasPendingFile(QString userName, QString serverAddress,
QString clientAddress, QString fileName);
bool saveFile(const QString &fileName);
void closeEvent(QCloseEvent *);
private:
Ui::Widget *ui;
QUdpSocket * udpSocket;
qint64 port;
QString fileName;
TcpServer * server;
QColor color;
private slots:
void processPendingDatagrams();
void on_sendButton_clicked();
void getFileName(QString);
void on_sendToolBtn_clicked();
void on_fontComboBox_currentFontChanged(const QFont &f);
void on_sizeComboBox_currentTextChanged(const QString &arg1);
void on_boldToolBtn_clicked(bool checked);
void on_italicToolBtn_clicked(bool checked);
void on_underlineToolBtn_clicked(bool checked);
void on_colorToolBtn_clicked();
void currentFormatChanged(const QTextCharFormat &format);
void on_saveToolBtn_clicked();
void on_clearToolBtn_clicked();
void on_exitButton_clicked();
};
#endif // WIDGET_H
tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include<QDialog>
#include<QTcpServer>
#include<QFile>
#include<QTcpSocket>
#include<QTime>
#include<QFileDialog>
#include<QString>
#include<QCloseEvent>
#include<QWidget>
#include<QKeyEvent>
#include<QHostAddress>
#include<QElapsedTimer>
QT_BEGIN_NAMESPACE
namespace Ui
{
class TcpServer;
}
QT_END_NAMESPACE
class QTcpServer;
class QTcpSocket;
class TcpServer : public QDialog
{
Q_OBJECT
public:
explicit TcpServer(QWidget *parent = nullptr);
~TcpServer();
void initServer();
void refused();
protected:
void closeEvent(QCloseEvent *);
signals:
void sendFileName(QString );
private slots:
void on_serverOpenBtn_clicked();
void on_serverSendBtn_clicked();
void on_serverCloseBtn_clicked();
void sendMessage();
void updateClientProgress(qint64);
private:
Ui::TcpServer *ui;
QTcpServer *tcpServer;
QTcpSocket *clientConnection;
qint16 tcpPort;
QFile *localFile ;
qint64 payloadSize ;
qint64 TotalBytes ;
qint64 bytesWritten ;
qint64 bytestoWrite;
QString theFileName;
QString fileName;
QElapsedTimer elapsedTimer;
QByteArray outBlock;
};
#endif
tcpclient.h
#ifndef TCPCLIENT_H
#define TCPCLIENT_H
#include <QDialog>
#include <QTcpServer>
#include <QFile>
#include <QTcpSocket>
#include <QTime>
#include <QFileDialog>
#include <QString>
#include <QCloseEvent>
#include <QHostAddress>
#include <QElapsedTimer>
namespace Ui {
class TcpClient;
}
class TcpClient : public QDialog
{
Q_OBJECT
public:
explicit TcpClient(QWidget *parent = nullptr);
~TcpClient();
void setFileName(QString fileName);
void setHostAddress(QHostAddress address);
void closeEvent(QCloseEvent *);
QFile *localFile;
private slots:
void on_tcpClientCloseBtn_clicked();
void readMessage();
void displayError(QAbstractSocket::SocketError);
void newConnect();
void on_tcpClientCancelBtn_clicked();
private:
Ui::TcpClient *ui;
QTcpSocket *tcpClient;
qint16 tcpPort ;
QHostAddress hostAddress;
qint64 TotalBytes ;
qint64 bytesReceived;
qint64 fileNameSize ;
qint64 blockSize;
QString fileName;
QElapsedTimer time;
QByteArray inBlock;
};
#endif // TCPCLIENT_H
编写源文件
变量的命名和使用需要和设计模式中命名一致,参考上述书籍。
部分槽函数的建立需要通过设计模式进行,参考上述书籍。
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "tcpserver.h"
#include "tcpclient.h"
#include <QFileDialog>
#include <QUdpSocket>
#include <QHostInfo>
#include <QMessageBox>
#include <QscrollBar>
#include <QDateTime>
#include <QNetworkInterface>
#include <QProcess>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui -> setupUi(this);
udpSocket = new QUdpSocket(this);
port = 45454;
udpSocket -> bind(port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
sendMessage(NewParticipant);
server = new TcpServer(this);
connect(server, SIGNAL(sendFileName(QString)), this, SLOT(getFileName(QString)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::sendMessage(MessageType type, QString serverAddress)
{
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
QString localHostName = QHostInfo::localHostName();
QString address = getIP();
out << type << getUserName() << localHostName;
switch(type)
{
case Message:
{
if(ui -> messageTextEdit -> toPlainText() == "")
{
QMessageBox::warning(0,
tr("警告"),
tr("发送内容不能为空"),
QMessageBox::Ok);
return;
}
out << address << getMessage();
ui -> messageBrowser -> verticalScrollBar()
-> setValue(ui -> messageBrowser -> verticalScrollBar() -> maximum());
break;
}
case NewParticipant:
{
out << address;
break;
}
case ParticipantLeft:
{
break;
}
case FileName:
{
int row = ui -> userTableWidget -> currentRow();
QString clientAddress = ui -> userTableWidget -> item(row, 2) -> text();
out << address << clientAddress << fileName;
break;
}
case Refuse:
{
out << serverAddress;
break;
}
}
udpSocket -> writeDatagram(data, data.length(),
QHostAddress::Broadcast,
port);
}
void Widget::processPendingDatagrams()
{
qDebug() << "processPendingDatagrams";
while(udpSocket -> hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket -> pendingDatagramSize());
udpSocket -> readDatagram(datagram.data(), datagram.size());
QDataStream in(&datagram, QIODevice::ReadOnly);
int messageType;
in >> messageType;
QString userName, localHostName, ipAddress, message;
QString time = QDateTime::currentDateTime()
.toString("yyyy-MM-dd hh:mm:ss");
switch(messageType)
{
case Message:
{
in >> userName >> localHostName >> ipAddress >> message;
ui -> messageBrowser -> setTextColor(Qt::blue);
ui -> messageBrowser -> setCurrentFont(QFont("Times New Roman", 12));
ui -> messageBrowser -> append("[ " + userName + " ] " + time);
ui -> messageBrowser -> append(message);
break;
}
case NewParticipant:
{
in >> userName >> localHostName >> ipAddress;
newParticipant(userName, localHostName, ipAddress);
break;
}
case ParticipantLeft:
{
in >> userName >> localHostName;
participantLeft(userName, localHostName, time);
break;
}
case FileName:
{
qDebug() << "FileName1";
in >> userName >> localHostName >> ipAddress;
QString clientAddress, fileName;
in >> clientAddress >> fileName;
qDebug() << "FileName2";
hasPendingFile(userName, ipAddress, clientAddress, fileName);
qDebug() << "FileName end";
break;
}
case Refuse:
{
in >> userName >> localHostName;
QString serverAddress;
in >> serverAddress;
QString ipAddress = getIP();
if(ipAddress == serverAddress)
{
server -> refused();
}
break;
}
}
}
}
void Widget::newParticipant(QString userName, QString localHostName, QString ipAddress)
{
qDebug() << "newParticipant";
bool isEmpty = ui -> userTableWidget
-> findItems(localHostName, Qt::MatchExactly).isEmpty();
if(isEmpty)
{
QTableWidgetItem * user = new QTableWidgetItem(userName);
QTableWidgetItem * host = new QTableWidgetItem(localHostName);
QTableWidgetItem * ip = new QTableWidgetItem(ipAddress);
ui -> userTableWidget -> insertRow(0);
ui -> userTableWidget -> setItem(0, 0, user);
ui -> userTableWidget -> setItem(0, 1, host);
ui -> userTableWidget -> setItem(0, 2, ip);
ui -> messageBrowser -> setTextColor(Qt::gray);
ui -> messageBrowser -> setCurrentFont(QFont("Microsoft YaHei UI", 10));
ui -> messageBrowser -> append(tr("%1在线!").arg(userName));
ui -> userNumLabel -> setText(tr("在线人数:%1")
.arg(ui -> userTableWidget -> rowCount()));
sendMessage(NewParticipant);
}
}
void Widget::participantLeft(QString userName, QString localHostName, QString time)
{
int rowNum = ui -> userTableWidget -> findItems(localHostName,
Qt::MatchExactly).first() -> row();
ui -> userTableWidget -> removeRow(rowNum);
ui -> messageBrowser -> setTextColor(Qt::gray);
ui -> messageBrowser -> setCurrentFont(QFont("Microsoft YaHei UI", 10));
ui -> messageBrowser -> append(tr("%1于%2离开!").arg(userName).arg(time));
ui -> userNumLabel -> setText(tr("在线人数:%1")
.arg(ui -> userTableWidget -> rowCount()));
}
QString Widget::getIP()
{
QList<QHostAddress> list = QNetworkInterface::allAddresses();
foreach(QHostAddress address, list)
{
if(address.protocol() == QAbstractSocket::IPv4Protocol)
return address.toString();
}
return 0;
}
QString Widget::getUserName()
{
QStringList environment = QProcess::systemEnvironment();
QStringList userNameList = environment.filter("USERNAME");
foreach(QString string, userNameList)
{
QStringList stringList = string.split('=');
if(stringList.size() == 2 && stringList.at(0) == "USERNAME")
{
return stringList.at(1);
break;
}
}
return "unknown";
}
QString Widget::getMessage()
{
QString msg = ui -> messageTextEdit -> toHtml();
ui -> messageTextEdit -> clear();
ui -> messageTextEdit -> setFocus();
return msg;
}
void Widget::on_sendButton_clicked()
{
sendMessage(Message);
}
void Widget::getFileName(QString name)
{
qDebug() << "getFileName";
fileName = name;
sendMessage(FileName);
}
void Widget::on_sendToolBtn_clicked()
{
if(ui -> userTableWidget -> selectedItems().isEmpty())
{
QMessageBox::warning(0, tr("选择用户"),
tr("请先从用户列表选择要传送的用户!"), QMessageBox::Ok);
return;
}
server -> show();
server -> initServer();
}
void Widget::hasPendingFile(QString userName,
QString serverAddress,
QString clientAddress,
QString fileName)
{
qDebug() << "hasPendingFile";
QString ipAddress = getIP();
if(ipAddress == clientAddress)
{
int btn = QMessageBox::information(this, tr("接收文件"),
tr("来自%1(%2)的文件:%3,是否接收?")
.arg(userName).arg(serverAddress).arg(fileName),
QMessageBox::Yes, QMessageBox::No);
if(btn == QMessageBox::Yes)
{
qDebug() << "hasPendingFile Yes";
QString name = QFileDialog::getSaveFileName(0, tr("保存文件"), fileName);
if(!name.isEmpty())
{
TcpClient * client = new TcpClient(this);
client -> setFileName(name);
client -> setHostAddress(QHostAddress(serverAddress));
client -> show();
}
}
else
{
sendMessage(Refuse, serverAddress);
}
}
qDebug() << "hasPendingFile end";
}
void Widget::on_fontComboBox_currentFontChanged(const QFont &f)
{
ui -> messageTextEdit -> setCurrentFont(f);
ui -> messageTextEdit -> setFocus();
}
void Widget::on_sizeComboBox_currentTextChanged(const QString &size)
{
ui -> messageTextEdit -> setFontPointSize(size.toDouble());
ui -> messageTextEdit -> setFocus();
}
void Widget::on_boldToolBtn_clicked(bool checked)
{
if(checked)
{
ui -> messageTextEdit -> setFontWeight(QFont::Bold);
}
else
{
ui -> messageTextEdit -> setFontWeight(QFont::Normal);
}
ui -> messageTextEdit -> setFocus();
}
void Widget::on_italicToolBtn_clicked(bool checked)
{
ui -> messageTextEdit -> setFontItalic(checked);
ui -> messageTextEdit -> setFocus();
}
void Widget::on_underlineToolBtn_clicked(bool checked)
{
ui -> messageTextEdit -> setFontUnderline(checked);
ui -> messageTextEdit -> setFocus();
}
void Widget::on_colorToolBtn_clicked()
{
color = QColorDialog::getColor(color, this);
if(color.isValid())
{
ui -> messageTextEdit -> setTextColor(color);
ui -> messageTextEdit -> setFocus();
}
}
void Widget::currentFormatChanged(const QTextCharFormat &format)
{
ui -> fontComboBox -> setCurrentFont(format.font());
if(format.fontPointSize() < 10)
{
ui -> sizeComboBox -> setCurrentIndex(3);
}
else
{
ui -> sizeComboBox -> setCurrentIndex(ui -> sizeComboBox
-> findText(QString::number(format.fontPointSize())));
}
ui -> boldToolBtn -> setChecked(format.font().bold());
ui -> italicToolBtn -> setChecked(format.font().italic());
ui -> underlineToolBtn -> setChecked(format.font().underline());
color = format.foreground().color();
}
void Widget::on_saveToolBtn_clicked()
{
if(ui -> messageBrowser -> document() -> isEmpty())
{
QMessageBox::warning(0, tr("警告"),
tr("聊天记录为空,无法保存!"), QMessageBox::Ok);
}
else
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("保存聊天记录"), tr("聊天记录"), tr("文本(*.txt);;All File(*.*)"));
if(! fileName.isEmpty())
{
saveFile(fileName);
}
}
}
bool Widget::saveFile(const QString &fileName)
{
QFile file(fileName);
if(! file.open(QFile::WriteOnly | QFile::Text))
{
QMessageBox::warning(this, tr("保存文件"),
tr("无法保存文件%1:\n%2")
.arg(fileName).arg(file.errorString()));
return false;
}
QTextStream out(&file);
out << ui -> messageBrowser -> toPlainText();
return true;
}
void Widget::on_clearToolBtn_clicked()
{
ui -> messageBrowser -> clear();
}
void Widget::on_exitButton_clicked()
{
close();
}
void Widget::closeEvent(QCloseEvent * e)
{
sendMessage(ParticipantLeft);
QWidget::closeEvent(e);
}
tcpserver.cpp
#include "tcpserver.h"
#include "ui_tcpserver.h"
#include<QHostInfo>
#include<QMessageBox>
#include<QDateTime>
#include<QProcess>
#include<QDataStream>
#include<QScrollBar>
#include<QFont>
#include<QNetworkInterface>
#include<QStringList>
#include<QDebug>
#include<QApplication>
#include<QTime>
#include<QElapsedTimer>
TcpServer::TcpServer(QWidget *parent) :
QDialog(parent),
ui(new Ui::TcpServer),
localFile(nullptr),
tcpServer(nullptr),
clientConnection(nullptr)
{
ui->setupUi(this);
//setFixedSize(350,180);
tcpPort = 10006;
tcpServer = new QTcpServer(this);
initServer();
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(sendMessage()));
}
TcpServer::~TcpServer()
{
delete ui;
}
void TcpServer::initServer()
{
payloadSize = 64 * 1024;
TotalBytes = 0 ;
bytestoWrite = 0;
bytesWritten = 0;
ui -> serverStatusLabel -> setText(tr("请选择要传送的文件"));
ui -> progressBar -> reset();
ui -> serverOpenBtn -> setEnabled(true);
ui -> serverSendBtn -> setEnabled(false);
tcpServer -> close();
}
void TcpServer :: sendMessage()
{
qDebug() << "sendMessage";
qDebug() << "TCP的链接已建立";
ui -> serverSendBtn -> setEnabled(false);
clientConnection = tcpServer -> nextPendingConnection();
connect(clientConnection, SIGNAL(bytesWritten(qint64)),
this, SLOT(updateClientProgress(qint64)));
ui -> serverStatusLabel -> setText(tr("开始传送文件:\n %1!").arg(theFileName));
localFile = new QFile(fileName);
if(!localFile -> open(QFile::ReadOnly)){
QMessageBox::warning(this, tr("应用程序"), tr("无法读取文件 %1:\n%2").arg(fileName)
.arg(localFile -> errorString()));
return;
}
TotalBytes = localFile -> size();
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);
sendOut.setVersion(QDataStream::Qt_4_0);
elapsedTimer.start();
QString currentFile = fileName.right(fileName.size() - fileName.lastIndexOf('/') - 1);
sendOut << qint64(0) << qint64(0) << currentFile;
TotalBytes += outBlock.size();
sendOut.device() -> seek(0);
sendOut << TotalBytes << qint64((outBlock.size() - sizeof(qint64)*2));
bytestoWrite = TotalBytes - clientConnection -> write(outBlock);
outBlock.resize(0);
}
void TcpServer::updateClientProgress(qint64 numBytes)
{
qDebug() << "updateClientProgress";
qApp -> processEvents();
bytesWritten += (int) numBytes;
if(bytestoWrite > 0)
{
outBlock = localFile -> read(qMin(bytestoWrite,payloadSize));
bytestoWrite -= (int)clientConnection -> write(outBlock);
outBlock.resize(0);
}
else
{
localFile -> close();
}
ui->progressBar -> setMaximum(TotalBytes);
ui->progressBar -> setValue(bytesWritten);
float useTime = elapsedTimer.elapsed();
qDebug() << "uerTime:" << useTime;
double speed = bytesWritten / useTime;
ui->serverStatusLabel->setText(tr("已发送 %1MB( %2MB/s)"
"\n共%3MB 已用时:%4秒\n估计剩余时间:%5秒")
.arg(bytesWritten / (1024*1024))
.arg(speed * 1000 /(1024*1024),0,'f',2)
.arg(TotalBytes /(1024*1024))
.arg(useTime/1000,0,'f',0)
.arg(TotalBytes/speed/1000 - useTime/1000,0,'f',0));
if(bytesWritten == TotalBytes)
{
localFile->close();
tcpServer->close();
ui->serverStatusLabel->setText(tr("传送文件: %1成功").arg(theFileName));
}
}
void TcpServer::on_serverOpenBtn_clicked()
{
fileName = QFileDialog::getOpenFileName(this);
if(!fileName.isEmpty())
{
theFileName = fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);
qDebug() << "文件名" << theFileName;
ui->serverStatusLabel->setText(tr("要发送的文件为:%1").arg(theFileName));
ui->serverSendBtn->setEnabled(true);
ui->serverOpenBtn->setEnabled(false);
}
qDebug() << "openbtn end" << theFileName;
}
void TcpServer::on_serverSendBtn_clicked()
{
if(! tcpServer -> listen(QHostAddress::Any, tcpPort))
{
qDebug() << "send btn error" << theFileName;
//qDebug() << tcpServer -> errorString() << "ERROR";
close();
return;
}
qDebug() << "send btn 1" << theFileName;
ui -> serverSendBtn -> setEnabled(false);
ui -> serverStatusLabel -> setText(tr("等待对方的接受......"));
qDebug() << "send btn 2" << theFileName;
emit sendFileName(theFileName);
qDebug() << "send btn end" << theFileName;
}
void TcpServer::on_serverCloseBtn_clicked()
{
qDebug() << "关闭" ;
if(tcpServer->isListening())
{
qDebug() << "点击了关闭按钮";
tcpServer->close();
if(localFile != nullptr)
{
if(localFile -> isOpen())
{
localFile -> close();
}
}
clientConnection->abort();
}
close();
ui->~TcpServer();
}
void TcpServer::refused()
{
qDebug() << "TcpServer::refused()";
tcpServer->close();
ui->serverStatusLabel->setText(tr("对方拒绝接受!"));
}
void TcpServer::closeEvent(QCloseEvent *)
{
qDebug() << "退出了server";
on_serverCloseBtn_clicked();
}
tcpclient.cpp
#include "tcpclient.h"
#include "ui_tcpclient.h"
#include <QMessageBox>
TcpClient::TcpClient(QWidget *parent) :
QDialog(parent),
tcpClient(nullptr),
localFile(nullptr),
ui(new Ui::TcpClient)
{
ui->setupUi(this);
TotalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
tcpClient = new QTcpSocket(this);
tcpPort = 10006;
connect(tcpClient, SIGNAL(readyRead()),
this, SLOT(readMessage()));
//connect(tcpClient, SIGNAL(QAbstractSocket::SocketError),
// this, SLOT(Dialog(QAbstractSocket::SocketError)));
}
TcpClient::~TcpClient()
{
delete ui;
}
void TcpClient::setFileName(QString fileName)
{
localFile = new QFile(fileName);
}
void TcpClient::setHostAddress(QHostAddress address)
{
hostAddress = address;
newConnect();
}
void TcpClient::newConnect()
{
blockSize = 0;
tcpClient->abort();
tcpClient->connectToHost(hostAddress,tcpPort);
time.start();
}
void TcpClient::readMessage()
{
qDebug() << "readMessage";
QDataStream in(tcpClient);
in.setVersion(QDataStream::Qt_4_0);
float useTime = time.elapsed();
if(bytesReceived <= sizeof(qint64)*2)
{
if((tcpClient -> bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize ==0))
{
in >> TotalBytes >> fileNameSize;
bytesReceived += sizeof(qint64)*2;
}
if((tcpClient -> bytesAvailable() >= fileNameSize) && (fileNameSize!=0))
{
in >> fileName;
bytesReceived += fileNameSize;
if(! localFile -> open(QFile::WriteOnly))
{
QMessageBox::warning(this,tr("应用程序"),tr("无法读取文件 %1:\n%2.")
.arg(fileName)
.arg(localFile->errorString()));
return ;
}
else return ;
}
}
if(bytesReceived < TotalBytes)
{
bytesReceived += tcpClient->bytesAvailable();
inBlock = tcpClient->readAll();
localFile -> write(inBlock);
inBlock.resize(0);
}
ui -> progressBar -> setMaximum(TotalBytes);
ui -> progressBar -> setValue(bytesReceived);
double speed = bytesReceived / useTime;
ui->tcpClientStatusLabel->setText(tr("已接收 %1MB( %2MB/s)"
"\n共%3MB 已用时:%4秒\n估计剩余时间:%5秒")
.arg(bytesReceived / (1024*1024))
.arg(speed *1000/(1024*1024),0,'f',2)
.arg(TotalBytes / (1024*1024))
.arg(useTime/1000,0,'f',0)
.arg(TotalBytes/speed/1000 - useTime/1000,0,'f',0 ));
if(bytesReceived == TotalBytes)
{
localFile -> close();
tcpClient-> close();
ui->tcpClientStatusLabel -> setText(tr("接收文件: %1完毕").arg(fileName));
}
}
void TcpClient::displayError(QAbstractSocket::SocketError sockError)
{
switch (sockError) {
case QAbstractSocket::RemoteHostClosedError:
break;
default:
qDebug() << tcpClient -> errorString();
}
}
void TcpClient::on_tcpClientCancelBtn_clicked()
{
tcpClient -> abort();
if(localFile -> isOpen())
{
localFile -> close();
}
close();
ui -> ~TcpClient();
}
void TcpClient::on_tcpClientCloseBtn_clicked()
{
on_tcpClientCancelBtn_clicked();
}
void TcpClient::closeEvent(QCloseEvent *)
{
on_tcpClientCloseBtn_clicked();
}
处理报错
将QTime类更换为QElapsedTimer类
程序测试
测试消息传输功能:部分UDP丢包导致消息收取不到。
测试文件传输功能:给自身IP地址发送文件会导致卡死。
绘制图表
系统功能模块图
用户流程图
UML类图
打包封装
以 Release 方式编译生成 exe 程序
运行成功后,如果勾选了 “shadow build” 将源码路径和构建路径分开,那么将在 build 文件夹下生成 exe 文件; 否则在源码工程目录下的 release 文件夹下生成 exe 文件。
将exe程序拷贝到别的文件夹中。
生成dll动态链接库
在开始菜单的Qt文件夹中找到Qt 6.3.0 (MinGW 11.2.0 64-bit),打开后进入exe文件目录,输入
windeployqt 程序名.exe
Qt 就会自动把该程序所需要的所有 dll 拷贝过来。
这样即可成功运行,但不能保证dll数量最小化,程序体积可能较大。
在GitHub上发布Release
https://github.com/Shiokiri/Qt-LAN-Chat/releases/tag/v1.0
本文由 落影汐雾 原创,采用 保留署名-非商业性使用-禁止演绎 4.0-国际许可协议
本文链接:https://x.lyxw.xyz/2022/C++Qt-LAN-Chat/