C++Qt局域网聊天工具 开发记录

Author Avatar
落影汐雾 6月 27, 2022
  • 在其它设备中阅读本文章

更新于: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();
}

处理报错

参考 Qt项目升级到Qt6吐血经验总结

将QTime类更换为QElapsedTimer类

程序测试

测试消息传输功能:部分UDP丢包导致消息收取不到。

测试文件传输功能:给自身IP地址发送文件会导致卡死。

绘制图表

系统功能模块图

用户流程图

UML类图

打包封装

参考 Qt之程序发布以及打包成exe安装包

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/