C++ QtTCP传输文件
2020 年 05 月 25 日 213 950 字 暂无评论

1.TCP传输文件

  • 上一篇博客提到,QT上TCP、UDP的简单使用,但TCP的不止发送文字,一般用来发送文件。

具体流程图如下:

  • 主要在于,发送文件时先发送头部文件,然后为了防止TCP黏包,后面会讲如何开一个定时器,来防止黏包。
  • 这里我设置一次发送4k数据,当发送文件大小和获取文件大小相同时,停止发送。

1.1UI界面设计

服务器端

  • 界面很简单,一个lab显示服务器名字,一个textEdit显示信息,两个按钮分别为选择文件和发送文件,但点击选择文件时,会跳出文件对话框。

客户端

  • 客户端比服务器端的设计多了IP和Port的编辑,加了进度条显示文件传输进度

1.2代码实现

pro文件里要添加

QT       += core gui network //network为使用TCP、UDP必须添加
CONFIG += C++11              //为了支持Lambd表达式

serverwidget.h

#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H

#include <QWidget>
#include <QTcpServer> //监听套接字
#include <QTcpSocket> //通信套接字
#include <QFile>
#include <QTimer>

namespace Ui {
class ServerWidget;
}

class ServerWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ServerWidget(QWidget *parent = 0);
    ~ServerWidget();

    void sendData(); //发送文件数据

private slots:
    void on_btn_file_clicked();

    void on_btn_send_clicked();

private:
    Ui::ServerWidget *ui;

    QTcpServer *tcpServer; //监听套接字
    QTcpSocket *tcpSocket; //通信套接字

    QFile file; //文件对象
    QString fileName; //文件名字
    qint64 fileSize;  //文件大小
    qint64 sendSize;  //已经发生文件的大小

    QTimer timer; //定时器
};

#endif // SERVERWIDGET_H

serverwidget.cpp

#include "serverwidget.h"
#include "ui_serverwidget.h"
#include <QFileDialog>
#include <QFileInfo>
#include <QTimer>

ServerWidget::ServerWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ServerWidget)
{
    ui->setupUi(this);

    //监听套接字
    tcpServer = new QTcpServer(this);

    setWindowTitle("服务器端口为:8888");

    //监听
    tcpServer->listen(QHostAddress::Any,8888);

    //两个按钮都不能按
    ui->btn_file->setEnabled(false);
    ui->btn_send->setEnabled(false);

    //如果客户端成功和服务器连接
    //tcpServer会自动触发 newConnection()
    connect(tcpServer,&QTcpServer::newConnection,
            [=]()
            {
                //取出建立好连接的套接字
                tcpSocket = tcpServer->nextPendingConnection();
                //获取对方的ip和端口
                QString ip = tcpSocket->peerAddress().toString();
                quint16 port = tcpSocket->peerPort();
                //组包
                QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
                //显示到编辑区
                ui->textEdit->append(str);

                //成功连接后,才能选择文件
                ui->btn_file->setEnabled(true);

                connect(tcpSocket,&QTcpSocket::readyRead,
                        [=]()
                        {
                           //取客户端的信息
                            QByteArray buf = tcpSocket->readAll();
                            if(QString(buf) == "file done")
                            {
                                //文件接收完毕
                                ui->textEdit->append("文件发送完毕");
                                file.close();

                                //断开客户端
                                tcpSocket->disconnectFromHost();
                                tcpSocket->close();
                            }
                        }
                        );
            }
            );
   connect(&timer,&QTimer::timeout,
           [=]()
           {
                //关闭定时器
                timer.stop();

                //发送文件
                sendData();
           }
           );
}

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

//选择文件按钮
void ServerWidget::on_btn_file_clicked()
{
    QString filePath = QFileDialog::getOpenFileName(this,"open","../");
    //如果文件不为空
    if(false == filePath.isEmpty())
    {
        fileName.clear();
        fileSize = 0;
        //获取文件信息
        QFileInfo info(filePath);
        fileName = info.fileName(); //获取文件名字
        fileSize = info.size();     //获取文件大小

        sendSize = 0; //发送文件的大小
        //只读方式打开文件
        //指定文件的名字
        file.setFileName(filePath);

        //打开文件
        bool isOk = file.open(QIODevice::ReadOnly);
        if(false == isOk)
        {
            ui->textEdit->append("只读方式打开失败!");
        }
        //提示打开文件路径
        ui->textEdit->append(filePath);

        ui->btn_file->setEnabled(false);
        ui->btn_send->setEnabled(true);
    }
    else
    {
        ui->textEdit->append("选择文件无效!");
    }


}
//发送文件按钮
void ServerWidget::on_btn_send_clicked()
{
    //发送文件头信息   文件名##文件大小
    QString head = QString("%1##%2").arg(fileName).arg((fileSize));
    //发送头部信息
    qint64 len = tcpSocket->write(head.toUtf8());
    if(len > 0)//头部信息发送成功
    {
        //发送真正的文件信息
        //防止TCP黏包文件
        //需要通过定时器延时 20 ms
        timer.start(20);
    }
    else
    {
         ui->textEdit->append("头部信息发送失败!");
         file.close();
         ui->btn_file->setEnabled(true);
         ui->btn_send->setEnabled(false);
    }

}

void ServerWidget::sendData()
{
    qint64 len = 0;
    do
    {
        //每次发送数据的大小
        char buf[4*1024] = {0};
        len = 0;
        //往文件中读数据
        len = file.read(buf,sizeof(buf));
        //发送数据,读多少,发多少
        len = tcpSocket->write(buf,len);

        //发送数据累加
        sendSize += len;
    }
    while(len > 0);

    //是否发送文件完毕
    if(sendSize == fileSize)
    {
        ui->textEdit->append("文件发送完毕!");
        file.close();

        //把客户端断开
        tcpSocket->disconnectFromHost();
        tcpSocket->close();
    }
}

clinetwidget.h

#ifndef CLINETWIDGET_H
#define CLINETWIDGET_H

#include <QWidget>
#include <QTcpSocket>
#include <QFile>

namespace Ui {
class ClinetWidget;
}

class ClinetWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ClinetWidget(QWidget *parent = 0);
    ~ClinetWidget();

private slots:
    void on_btn_connect_clicked();

private:
    Ui::ClinetWidget *ui;

    QTcpSocket *tcpSocket; //通信套接字

    QFile file; //文件对象
    QString fileName; //文件名字
    qint64 fileSize;  //文件大小
    qint64 receiveSize;  //已经发生文件的大小

    bool isStart;
};

#endif // CLINETWIDGET_H

clinetwidget.cpp

#include "clinetwidget.h"
#include "ui_clinetwidget.h"
#include <QMessageBox>
#include <QHostAddress>
#include <QProgressBar>

ClinetWidget::ClinetWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ClinetWidget)
{
    ui->setupUi(this);

    tcpSocket = new QTcpSocket(this);

    isStart = true;

    connect(tcpSocket,&QTcpSocket::readyRead,
            [=]()
            {
                //取出接收的内容
                QByteArray buf = tcpSocket->readAll();

                if(true == isStart)
                {
                    //接收头
                    isStart = false;
                    //解析头部信息 buf = "hello##1024"
                    fileName = QString(buf).section("##",0,0);
                    fileSize = QString(buf).section("##",1,1).toInt();
                    receiveSize = 0;
                    //打开文件
                    file.setFileName(fileName);

                    bool isOk = file.open(QIODevice::WriteOnly);
                    if(false == isOk)
                    {
                        ui->textEdit->append("打开出错!");
                        return;
                    }
                    //显示接收文件的信息
                    QString str = QString("接收的文件: [%1 : %2kb]").arg(fileName).arg(fileSize/1024);
//                    QMessageBox::information(this,"文件信息",str);
                    ui->textEdit->append(str);

                    //设置进度条
                    ui->progressBar->setMinimum(0);//最小值
                    ui->progressBar->setMaximum(fileSize/1024);//最大值
                }
                else
                {
                    //文件信息
                    qint64 len = file.write(buf);
                    if(len > 0)
                    {
                        receiveSize += len;
                    }
                    //更新进度条
                    ui->progressBar->setValue(receiveSize/1024);
                    if(receiveSize == fileSize)//文件接收完毕
                    {
                        //给服务器发送接收完成信息
                        tcpSocket->write("file done");
                        file.close();//文件关闭
                        QMessageBox::information(this,"完成","文件接收完成");
                        //断开连接
                        tcpSocket->disconnectFromHost();
                        tcpSocket->close();
                    }
                }
            }
            );
}

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

void ClinetWidget::on_btn_connect_clicked()
{
    //获取服务器的ip和端口
    QString ip = ui->lineEditIP->text();
    quint16 port = ui->lineEditPort->text().toInt();

    tcpSocket->connectToHost(QHostAddress(ip),port);
}

代码打了注释,应该不难理解

1.3运行结果

  • 输入端口和IP,这里我是本地IP,然后建立连接

  • 选择文件

  • 点击发送,显示发送完成

2.QT定时器简单使用

  • 因为TCP有个黏包问题,防止头部信息和要发送的信息黏包,这里用了定时器来延时20ms
  • 使用简单使用头文件
#include <QTimer>
QTimer timer; //定时器 头文件定义定时器对象

通过timer.start(value)//value中是ms为单位,设置定时器开始,以及定时时间

if(len > 0)//头部信息发送成功
    {
        //发送真正的文件信息
        //防止TCP黏包文件
        //需要通过定时器延时 20 ms
        timer.start(20);
    }

定时器开始后触发&QTimer::timeout信号,然后就可以根据这个信号,触发相应的槽函数操作

connect(&timer,&QTimer::timeout,
           [=]() //这里使用Lambd表达式
           {
                //关闭定时器
                timer.stop();

                //发送文件
                sendData();
           }
           );

timer.stop()为关闭定时器,下次使用可以继续打开,非常方便


版权属于:zfh

本文链接:http://zfhblog.com/index.php/archives/81/



评论已关闭