Qt实现文件传输服务器端(图文详解+代码详细注释)

Qt实现文件传输服务器

1、前言

|-------------------------------------------------------------------------------------------------------|
| 记录自己对于Qt实现文件传输的学习过程,方便自己日后回顾,也可以给别人提供参考借鉴,目录就是实现的过程,大家可以按照目录逐步浏览,也可以点击目录跳转到所需部分,这个是服务器端,客户端在主页另外一篇博客。 |

2、服务器

2.1 服务器UI界面

2.2添加网络模块和头文件

|-------------------|
| 添加完网络模块构建一下,添加头文件 |

cpp 复制代码
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>

2.3、创建服务器对象

cpp 复制代码
#define PORT 8000       //端口号

QTcpServer *tcpServer;          //创建服务器对象指针
QTcpSocket *tcpSocket;          //通信套接字

|----------------|
| 创建服务器对象,监听连接对象 |

cpp 复制代码
tcpServer = new QTcpServer(this);           //创建服务器对象
tcpServer->listen(QHostAddress::Any,PORT);          //监听任意ip地址和端口号

2.4 连接有新连接的信号与槽

|-------|
| 创建槽函数 |

cpp 复制代码
private slots:
    void newConnectionHandler();        //新连接处理槽函数

|---------------|
| 连接有新连接的信号和槽函数 |

cpp 复制代码
connect(tcpServer,&QTcpServer::newConnection,this,&Widget::newConnectionHandler);    //处理新连接

2.5实现有新连接处理的槽函数

|----------------------------|
| 鼠标放在函数上,ALT+Enter,点击添加定义 |

|---------------|
| 未连接成功前设置按钮不可按 |

cpp 复制代码
//设置按钮不可按
ui->selectPushButton->setEnabled(false);        //选择文件按钮不可按
ui->sendPushButton->setEnabled(false);          //发送文件按钮不可按

|----------|
| 新连接槽函数实现 |

cpp 复制代码
//处理新连接
void Widget::newConnectionHandler()
{
    //建立Tcp连接
    tcpSocket = tcpServer->nextPendingConnection();     //取出下一个连接的套接字   
    
    //获取ip地址和端口号
    QString ip = tcpSocket->peerAddress().toString();   //ip地址
    qint16 Port = tcpSocket->peerPort();                //端口号
    
    //显示ip地址和端口号
    ui->ipLineEdit->setText(ip);            //在ip输入框上显示
    ui->portLineEdit->setText(QString::number(Port));        //在Port输入框中显示
    
    QString connectTemp = QString("[%1:%2] 已连接").arg(ip).arg(Port);     //已连接文本
    ui->messageTextEdit->setText(connectTemp);          //显示已连接信息
    
    //设置文件按钮为可按
    ui->selectPushButton->setEnabled(true);             //设置选择文件按钮位可按
    ui->sendPushButton->setEnabled(true);               //设置发送文件按钮位可按
}

2.6 选择文件按钮实现

2.6.1 连接按钮点击的信号与槽

|----------------------------|
| 把鼠标放到选择文件的按钮控件上,右键,点击转到槽 |

|---------------|
| 点击clicked() |

2.6.2 添加头文件

|---------------------------------------|
| 添加QMessageBox是为了弹窗消息框,添加QDebug是为了打印信息 |

2.6.3 创建所需对象

cpp 复制代码
QFile file;                     //创建文件对象

QString fileName;               //文件名字
qint64 fileSize;                //文件大小
qint64 senddFileSize;           //发送文件大小

2.6.3 选择文件按钮实现

cpp 复制代码
//选择文件按钮
void Widget::on_selectPushButton_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this,"open","../");   //准备打开文件的路径
    //打开的文件路径不为空
    if(!filePath.isEmpty())
    {
        //获取文件信息
        QFileInfo fileInfo(filePath);       //文件信息
        
        fileName = fileInfo.fileName();     //文件名字
        fileSize = fileInfo.size();         //文件大小
        
        senddFileSize = 0;         //发送文件大小为0
        
        //以只读打开文件
        file.setFileName(filePath);     //准备打开的文件
        
        bool fileisOk = file.open(QIODevice::ReadOnly);         //以只读形式打开
        //打开文件失败
        if(fileisOk==false)
        {
            QMessageBox::warning(this,"打开文件","打开文件失败");     //显示打开文件失败消息框
        }
        
        //文件路径显示
        ui->messageTextEdit->setText(filePath);         //显示文件路径
        
        ui->selectPushButton->setEnabled(false);       //打开文件成功后选择文件按钮失效
        ui->sendPushButton->setEnabled(true);          //发送按钮文件生效  
    }
    else
    {
        QMessageBox::warning(this,"打开文件失败","文件不能为空");      //显示打开文件错误消息框
    }
    
}

2.7 发送文件按钮实现

|---------------------------|
| 跟选择文件一样,转到槽,点击clicked() |

2.7.1添加定时器解决粘包问题

|-------|
| 添加头文件 |

|---------|
| 创建定时器对象 |

cpp 复制代码
QTimer sendFileTimer;           //发送文件定时器

2.7.2 连接定时器结束信号与槽

|--------------|
| 创建定时器结束处理槽函数 |

cpp 复制代码
void sendFileTimeoutHandler();          //发送文件结束定时器处理
cpp 复制代码
sendFileTimer = new QTimer(this);           //创建定时器对象

|-------------|
| 连接定时器结束信号与槽 |

cpp 复制代码
connect(sendFileTimer,&QTimer::timeout,this,&Widget::sendFileTimeoutHandler); //定时器结束处理       

2.7.3定时器结束槽函数实现

cpp 复制代码
//定时器结束处理
void Widget::sendFileTimeoutHandler()
{
    sendFileTimer->stop();          //停止定时器
    char buf[4*1024] = {0};       //缓冲区
    qint64 bytesRead = file.read(buf, sizeof(buf)); // 读取实际字节数

    //成功读取到数据
    if(bytesRead>0)
    {
        qint64 bytesWritten = tcpSocket->write(buf, bytesRead); // 发送实际读取的字节
        //发送错误
        if (bytesWritten == -1) {
            QMessageBox::warning(this, "错误", "发送失败");
            return;
        }
        senddFileSize += bytesWritten;     //发送的文件大小

        // 未发送完则继续触发定时器
        if (senddFileSize < fileSize) {
            sendFileTimer->start(20);       //20ms触发一次定时器
        } 
        else {
            QMessageBox::information(this, "完成", "文件发送完毕");
            file.close();
        }
    }
    else if(senddFileSize < fileSize)
    {
        QMessageBox::warning(this, "错误", "文件读取失败");
    }
}

3、头文件和.cpp文件

3.1 Widget.h文件

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QFileDialog>
#include <QFileInfo>
#include <QFile>
#include <QMessageBox>
#include <QDebug>
#include <QTimer>

#define PORT 8000       //端口号

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void newConnectionHandler();            //新连接处理槽函数

    void sendFileTimeoutHandler();          //发送文件结束定时器处理

    void on_selectPushButton_clicked();     //选择文件按钮实现

    void on_sendPushButton_clicked();       //发送文件按钮实现

private:
    Ui::Widget *ui;

    QTcpServer *tcpServer;          //创建服务器对象指针
    QTcpSocket *tcpSocket;          //通信套接字

    QFile file;                     //创建文件对象

    QString fileName;               //文件名字
    qint64 fileSize;                //文件大小
    qint64 senddFileSize;           //发送文件大小

    QTimer *sendFileTimer;           //发送文件定时器
};
#endif // WIDGET_H

3.2 .cpp文件

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
//    this->setWindowFlag(Qt::FramelessWindowHint);       //去边框

    //设置按钮不可按
    ui->selectPushButton->setEnabled(false);        //选择文件按钮不可按
    ui->sendPushButton->setEnabled(false);          //发送文件按钮不可按

    tcpServer = new QTcpServer(this);           //创建服务器对象
    tcpServer->listen(QHostAddress::Any,PORT);          //监听任意ip地址和端口号

    sendFileTimer = new QTimer(this);           //创建定时器对象

    connect(tcpServer,&QTcpServer::newConnection,this,&Widget::newConnectionHandler);    //处理新连接

    connect(sendFileTimer,&QTimer::timeout,this,&Widget::sendFileTimeoutHandler);        //定时器结束处理
}

Widget::~Widget()
{
    delete ui;
}

//处理新连接
void Widget::newConnectionHandler()
{
    //建立Tcp连接
    tcpSocket = tcpServer->nextPendingConnection();     //取出下一个连接的套接字

    //获取ip地址和端口号
    QString ip = tcpSocket->peerAddress().toString();   //ip地址
    qint16 Port = tcpSocket->peerPort();                //端口号

    //显示ip地址和端口号
    ui->ipLineEdit->setText(ip);            //在ip输入框上显示
    ui->portLineEdit->setText(QString::number(Port));        //在Port输入框中显示

    QString connectTemp = QString("[%1:%2] 已连接").arg(ip).arg(Port);     //已连接文本
    ui->messageTextEdit->setText(connectTemp);          //显示已连接信息

    //设置文件按钮为可按
    ui->selectPushButton->setEnabled(true);             //设置选择文件按钮位可按
    ui->sendPushButton->setEnabled(true);               //设置发送文件按钮位可按

}

//定时器结束处理
void Widget::sendFileTimeoutHandler()
{
    sendFileTimer->stop();          //停止定时器
    char buf[4*1024] = {0};       //缓冲区
    qint64 bytesRead = file.read(buf, sizeof(buf)); // 读取实际字节数

    //成功读取到数据
    if(bytesRead>0)
    {
        qint64 bytesWritten = tcpSocket->write(buf, bytesRead); // 发送实际读取的字节
        //发送错误
        if (bytesWritten == -1) {
            QMessageBox::warning(this, "错误", "发送失败");
            return;
        }
        senddFileSize += bytesWritten;     //发送的文件大小

        // 未发送完则继续触发定时器
        if (senddFileSize < fileSize) {
            sendFileTimer->start(20);       //20ms触发一次定时器
        } 
        else {
            QMessageBox::information(this, "完成", "文件发送完毕");
            file.close();
        }
    }
    else if(senddFileSize < fileSize)
    {
        QMessageBox::warning(this, "错误", "文件读取失败");
    }
}

//选择文件按钮
void Widget::on_selectPushButton_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this,"open","../");   //准备打开文件的路径
    //打开的文件路径不为空
    if(!filePath.isEmpty())
    {
        //获取文件信息
        QFileInfo fileInfo(filePath);       //文件信息

        fileName = fileInfo.fileName();     //文件名字
        fileSize = fileInfo.size();         //文件大小

        senddFileSize = 0;         //发送文件大小为0

        //以只读打开文件
        file.setFileName(filePath);     //准备打开的文件

        bool fileisOk = file.open(QIODevice::ReadOnly);         //以只读形式打开
        //打开文件失败
        if(fileisOk==false)
        {
            QMessageBox::warning(this,"打开文件","打开文件失败");     //显示打开文件失败消息框
        }

        //文件路径显示
        ui->messageTextEdit->setText(filePath);         //显示文件路径

        ui->selectPushButton->setEnabled(false);       //打开文件成功后选择文件按钮失效
        ui->sendPushButton->setEnabled(true);          //发送按钮文件生效
    }
    else
    {
        QMessageBox::warning(this,"打开文件失败","文件不能为空");      //显示打开文件错误消息框
    }

}

//发送文件按钮
void Widget::on_sendPushButton_clicked()
{
    //发送文件信息
    QString fileMes = QString("%1##%2\n").arg(fileName).arg(fileSize);       //发送文件的名字和大小

    qint64 fileLength = tcpSocket->write(fileMes.toUtf8().data());     //发送文件长度
    //发送文件成功
    if(fileLength > 0)
    {
        //发送文件的真正内容
        //距离太久会产生粘包问题
        //使用定时器解决粘包问题
        sendFileTimer->start(20);    //间隔20ms触发timeout定时器结束信号
    }
    else
    {
        QMessageBox::warning(this,"发送文件","发送文件失败");          //显示发送文件失败消息框

        file.close();           //关闭文件
        tcpSocket->disconnectFromHost();        //断开连接
        tcpSocket->close();       //关闭连接
    }
}

4、总结

|-------------------------------------------------------------------------------------------------------------|
| 以上就是Qt实现文件传输服务器端的整个过程了,浏览过程中,如若发现错误,欢迎大 家指正,有问题的可以评论区留言或者私信。最后,如果大家觉得有所帮助,可以点一 下赞,谢谢大家!未来是未知的,愿大家保持冷静,继续前行! |

相关推荐
крон2 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
陈丹阳(滁州学院)2 小时前
若依添加添加监听容器配置(删除键,键过期)
数据库·oracle
zh_xuan2 小时前
c++ 单例模式
开发语言·c++·单例模式
远方16093 小时前
14-Oracle 23ai Vector Search 向量索引和混合索引-实操
数据库·ai·oracle
老胖闲聊3 小时前
Python Copilot【代码辅助工具】 简介
开发语言·python·copilot
Blossom.1183 小时前
使用Python和Scikit-Learn实现机器学习模型调优
开发语言·人工智能·python·深度学习·目标检测·机器学习·scikit-learn
曹勖之3 小时前
基于ROS2,撰写python脚本,根据给定的舵-桨动力学模型实现动力学更新
开发语言·python·机器人·ros2
豆沙沙包?4 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
GUIQU.4 小时前
【Oracle】数据仓库
数据库·oracle
军训猫猫头4 小时前
96.如何使用C#实现串口发送? C#例子
开发语言·c#