QT 学习笔记(十六)

由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见QT 学习笔记(二)

一、TCP 传文件流程图

  • 服务器端和客户端进行正常的通信,通信成功后,在服务器端选择需要的文件发送给客户端。

在这里插入图片描述

1. 服务器端流程

  • (1) 选择文件并且获取文件的基本信息(包括文件的名字和大小),这里假设文件大小是 1024,文件的名字是 hello。
  • (2) 组包(“head#hello#1024”)发送文件信息,向客户端发送对应的文件信息。
  • (3) 在组包发送完成后,服务器端开始读文件,发送数据(服务器端读多少发多少),当我发送文件大小和获取到的文件大小相等时,停止读取文件数据。
  • (4) 服务器端连接信号 readyread() 来获取文件是否发送成功。

2. 客户端流程

  • (1) 在服务器端组包发送文件信息,开始接收文件,获取文件信息,分析服务器端发送的组包字符串,获取文件的大小和文件的名字。
  • (2) 在本地创建一个文件,以接收到的文件名字命名。
  • (3) 在服务器端开始发送文件数据后,服务器端发送多少,客户端就接收多少。
  • (4) 客户端将接收到的文件数据放入本地创建的文件当中。
  • (5) 如果想清楚的直到客户端是否成功接收完成文件,可以在客户端真正接收完成后给服务端发送一个消息。

二、TCP 传文件操作实现

  • 生成一个新的项目,具体步骤过程见提示。
  • 在生成新项目的过程中,我们选择基类为 QWidget ,这是因为 QWidget 当中比较干净,不存在别的东西,而 QMainWindow 虽然也可以实现,但其中存在工具栏,菜单栏,核心控件等东西,比较复杂。

1. 服务器端

  • 我们在 TCPfile.pro 当中加入 QT += network 代码,以便后续工作可以顺利开展。
  • 在过程当中要使用 Lambda 表达式,因此,我们提前在 TCPfile.pro 当中加入 CONFIG += C++11 代码。
  • 首先,我们在 ui 界面布置出服务器端的窗口界面,包含一个标签(用来表示标题),两个按钮(选择文件和发送文件)和一个文本编辑区(用来输入和显示),具体界面布局如下图所示。

在这里插入图片描述

  • 在布局完成后,我们选择标签服务器,将其设置为水平居中(在右下角的 QLabel,点击 alignment,其中包含水平的和垂直的,水平的选取 AlignHCenter),将其字体选择为楷体,大小选择为 24。

在这里插入图片描述

  • 完成 ui 界面的设计后,要及时编译,以便于 QT 可以及时地进行关键字信息的补充。
  • TCP 当中,服务器端包括两个套接字,分别是监听套接字 QTcpServer 和通信套接字 QTcpSocket。
  • 在完成头文件的包含和变量的定义之后,对监听套接字和通信套接字进行分配空间。
  • 我们对两个按钮在 ui 界面进行转到槽函数的操作,并进行代码的编写,由于这里需要服务器端和客户端相结合才可以看到现象,故在此处不过多展示实现现象(主要在客户端展示)。
  • 服务器端的实现主要分为以下几步:
  • (1) 建立连接的实现
  • 要进行服务器端和客户端之间的 TCP 通信,无论是传输数据还是文件,首先要建立服务器端和客户端之间的连接。
  • 在连接到客户端的时候,打印出 IP 地址和端口号,并且显示在文本编辑区中,开始之前对按钮进行了一个不使能操作。也就是说,在没有连接的时候是无法点击按钮的。
  • (2) 选择文件的实现
  • 在建立完成服务器端和客户端之间的连接后,我们需要在服务端选择文件并且发送给客户端。
  • 因此,我们需要包含头文件 QFile,并定义文件对象。随后,在 ui 界面当中的选择文件按钮进行转到槽的操作。
  • 在发送文件之前先发送文件的信息,比如文件名,文件大小等。文件信息的获取我们使用 QT 中的 QFileInfo。关于文件名和文件大小,我们需要定义对应的全局变量。
  • 在获取文件信息之前,要先对文件信息进行清空,也就是文件名字 clear(),文件大小设置为 0。
  • 同时,我们还需要定义一个全局变量,用以表示当前文件发送的进度。
  • 在选择文件结束后,将选择文件按钮使能为 false,发送文件按钮使能为 true。
  • (3) 发送文件的实现
  • 在 ui 界面当中的发送文件按钮进行转到槽的操作。
  • 关于发送文件,我们需要先发送文件头信息,再发生真正的文件信息。
  • 在发送头部数据的过程当中,为了防止出现 TCP 连包问题,我们需要通过定时器延时 20ms。
  • 在发送文件结束后,将发送文件按钮使能为 false,选择文件按钮使能为 true。
  • (4) 发送文件数据的实现
  • 对于数据发送为了防止头信息和数据出现相互干扰的情况,需要分开发送,在发送头信息之后,延时(使用定时器)然后发送数据,确保客户端先收到头信息,再收到数据。
  • 在发送完数据后,要断开与客户端的连接。
  • 最后具体实现结果如下图所示。

在这里插入图片描述

2. 客户端

  • 对于客户端来说,主要就是获取发送的头信息,不能和数据相互连包,其余和之前的 TCP 数据传输一样。
  • 客户端通过使用 QT 提供的 QTcpSocket 类可以方便的实现与服务器端的通信。
  • 在完成服务器端后,我们进行客户端的编写,客户端只有一个套接字就是通信套接字 tcpsocket。
  • 这里我们不重新开一个项目,而是将两部分放在一块。但是,服务器端和客户端的 ui 界面并不相同,因此,我们需要在添加一个 QT 设计师界面类,对客户端的 ui 界面进行设计。

在这里插入图片描述

  • 界面模板默认 Widget 即可。

在这里插入图片描述

  • 随后,我们先在 ui 界面布置出客户端的简单窗口界面,包含两个标签(分别是服务器端口和服务器 IP )以及他们对应的行编辑区和一个按钮(连接 connect)。
  • 其中的服务器端口的行编辑区直接默认值是 8888(与服务器端相对应),服务器 IP 的行编辑区直接默认值是 127.0.0.1(本地连接),具体界面布局如下图所示。

在这里插入图片描述

  • 在客户端当中,接收的信息主要分为两部分(头信息和文件信息)。
  • 在接收头信息的过程中,我们需要对文件信息进行初始化,随后打开文件。
  • 在接收完文件信息后,断开与服务器端的连接。

3. TCP 传文件实现现象

  • 在完成全部代码的编写后,运行程序,会得到如下的具体现象。

在这里插入图片描述

  • 我们点击 connect 按钮,服务器端与客户端会完成通信。
  • 此时,服务器端的选择文件按钮会点亮,我们可以选择一个合适的文件进行传输。
  • 然后,点击发送文件按钮,会得到文件发送完毕的提示,表明文件已经成功发送。
  • 这里需要注意,在发送文件的过程中,是无法点击选择文件的,只有在发送完成后,服务器端与客户端重新建立连接,才可以重新选择待发送的文件。

在这里插入图片描述

  • 发送文件成功之后,在对应的文件目录下,会生成我们发送的 test.txt 文件,具体现象如下图所示。

在这里插入图片描述

三、服务器端和客户端实现代码

1. 主函数 main.c

#include "serverwidget.h"
#include <QApplication>
#include "clientwidget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    serverWidget w;
    w.show();

    clientwidget w2;
    w2.show();

    return a.exec();
}

2. 服务器端头文件 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 = nullptr);
    ~serverWidget();

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

private slots:
    void on_pushButtonfile_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::serverWidget *ui;

    QTcpServer *tcpserver; //监听套接字
    QTcpSocket *tcpsocket; //通信套接字

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

    QTimer timer; //定时器
};

#endif // SERVERWIDGET_H

3. 服务器端源文件 serverwidget.cpp

#include "serverwidget.h"
#include "ui_serverwidget.h"
#include <QFileDialog>
#include <QDebug>

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

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

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

    //设置窗口标题
    setWindowTitle("服务器端口为:8888");

    //无连接的时候按钮无效
    ui->pushButtonfile->setEnabled(false);
    ui->pushButton_2->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->setText(str);

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

    connect(&timer,&QTimer::timeout,
            [=]()
            {
                //关闭定时器
                timer.stop();

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

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

//选择文件按钮
void serverWidget::on_pushButtonfile_clicked()
{
    QString filepath = QFileDialog::getOpenFileName(this,"选择文件","../");
     if(filepath.isEmpty() == false) //如果选择文件路径有效
     {
         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(isok == false)
         {
             qDebug() << "只读方式打开文件失败";
         }

         //追加文件路径信息
         ui->textEdit->append(filepath);

         ui->pushButtonfile->setEnabled(false);
         ui->pushButton_2->setEnabled(true);
     }
     else
     {
         qDebug() << "文件路径无效";
     }
}

//发送文件按钮
void serverWidget::on_pushButton_2_clicked()
{
    //先发送文件头信息,文件名##文件大小
    QString headMessage = QString("%1##%2").arg(fileName).arg(fileSize);

    //发送头部信息
    quint64 len = tcpsocket->write(headMessage.toUtf8().data());

    if(len > 0) //发送头信息成功
    {
        //发送真正的文件信息
        //防止TCP连包问题
        //需要通过定时器延时20ms
        timer.start(20);
    }
    else
    {
        qDebug() << "头文件信息发送失败";
        file.close();

        ui->pushButtonfile->setEnabled(true);
        ui->pushButton_2->setEnabled(false);
    }
}

//发送文件数据
void serverWidget::sendData()
{
    qint64 len = 0;
    sendSize = 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();
    }

}

4. 客户端头文件 clientwidget.h

#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H

#include <QWidget>
#include <QTcpSocket> //通信套接字
#include <QFile>

namespace Ui {
class clientwidget;
}

class clientwidget : public QWidget
{
    Q_OBJECT

public:
    explicit clientwidget(QWidget *parent = nullptr);
    ~clientwidget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::clientwidget *ui;

    QTcpSocket *tcpsocket; //通信套接字

    QFile file; //文件对象
    QString fileName; //文件名字
    qint64 fileSize; //文件大小
    qint64 recvSize; //已经接收文件的大小

    bool isstart;
};

#endif // CLIENTWIDGET_H

5. 客户端源文件 clientwidget.cpp

#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QDebug>
#include <QMessageBox>
#include <QHostAddress>

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

    tcpsocket = new QTcpSocket(this);

    isstart = true;

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

                if(isstart == true)
                {
                    //接收头
                    isstart = false;

                    //解析头部信息 buf = "hello##1024"
                    //QString str = "hello##1024#mike";
                    //str.section("##",0,0)

                    //初始化
                    fileName = QString(buf).section("##", 0, 0);
                    fileSize = QString(buf).section("##", 1, 1).toInt();
                    recvSize = 0;

                    //打开文件
                    file.setFileName(fileName);

                    bool isok = file.open(QIODevice::WriteOnly);
                    if(false == isok)
                    {
                        qDebug() << "WriteOnly error";
                    }
                    else //文件信息
                    {
                        qint64 len = file.write(buf);
                        recvSize += len;

                        if(recvSize == fileSize)
                        {
                            file.close();
                            QMessageBox::information(this,"完成","文件接收完成");

                            tcpsocket->disconnectFromHost();
                            tcpsocket->close();
                        }
                    }
                }
            }
            );
}

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

void clientwidget::on_pushButton_clicked()
{
    //获取服务器的IP和端口
    QString ip = ui->lineEditip->text();
    quint16 port = ui->lineEditport->text().toInt();

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚心求知的熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值