QT 学习笔记(十六)

news2024/11/16 18:09:30

文章目录

  • 一、TCP 传文件流程图
    • 1. 服务器端流程
    • 2. 客户端流程
  • 二、TCP 传文件操作实现
    • 1. 服务器端
    • 2. 客户端
    • 3. TCP 传文件实现现象
  • 三、服务器端和客户端实现代码
    • 1. 主函数 main.c
    • 2. 服务器端头文件 serverwidget.h
    • 3. 服务器端源文件 serverwidget.cpp
    • 4. 客户端头文件 clientwidget.h
    • 5. 客户端源文件 clientwidget.cpp

由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150749.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

某医院的实战渗透测试(组合拳)

实战渗透一、前言二、Spring信息泄露三、Redis写公钥四、文章来源一、前言 项目是内网环境下进行&#xff0c;所以通过vpn接入内网之后进行目标系统的测试。&#xff08;信息泄露redis写公钥&#xff09; 二、Spring信息泄露 访问客户给的目标地址通过代理把流量转给了BurpS…

零基础学软件测试有前途吗?

随着软件工程活动的不断演化&#xff0c;测试工作某种程度上是可以很大幅度提高软件的产品质量以及提升用户的使用满意度&#xff0c;因此软件测试工程师的地位在企业中也越来越受到重视。不少零基础学IT的朋友也开始把软件测试作为一个绝佳的选择对象&#xff0c;那么零基础学…

leetcode.1806 还原排列的最少操作步数 - 模拟 + lcm

​​​​​​1806. 还原排列的最少操作步数 本题是数论题 共介绍4种解题方法 目录 1、所有置换环长度的最小公倍数 2、最小操作数是最大环长度 3、1或n-2所在环长度即为最大置换环长度 4、暴力模拟 思路&#xff1a; 因为数据范围很小 所以可以直接模拟 也可以优化一下—…

Python 模型训练:LSTM 时间序列销售额预测(训练、保存、调用)

LSTM (long short-term memory) 长短期记忆网络&#xff0c;具体理论的就不一一叙述&#xff0c;直接开始 流程一、数据导入二、数据归一化三、划分训练集、测试集四、划分标签和属性五、转换成 LSTM 输入格式六、设计 LSTM 模型6.1 直接建模6.2 找最好七、测试与图形化展示八、…

JavaSE-07

字节流输入输出数据&#xff1a; InputStream和OutputStream作为字节流输入输出流的超类。 字节流写数据时千万记得close关闭资源&#xff0c;可设置追加写为true 字节流读数据时&#xff0c;FileInputStream a new FileInputStream (“”); int by a.read(); char b (char…

隐蔽信道学习

隐蔽信道作为一种能够在不被系统感知的情况下稳定窃取秘密信息的通信手段&#xff0c;尽管其带宽通常较低&#xff0c;但其设计上的复杂性和多样性&#xff0c;使得常规的流量审计系统难以对抗或检测。同时&#xff0c;隐蔽信道也是密钥、身份认证、商业机密等秘密信息传输的重…

基于JAVA SSM框架的新闻管理系统源码+数据库,实现 登录 、 注册、 新闻内容、类别、评论、个人信息、系统管理等功能

[基于SSM框架的新闻管理系统] 前言 下载地址&#xff1a;基于JAVA SSM框架的新闻管理系统源码数据库 基于SSM框架的新闻管理系统&#xff1b; 实现 登录 、 注册 、 新闻内容、类别、评论、个人信息、系统管理等功能 &#xff1b; 可继续完善增加前端、等其他功能等&#x…

federated引擎实现mysql跨服务器表连接

&#x1f4e2;作者&#xff1a; 小小明-代码实体 &#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/as604049322 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 欢迎讨论&#xff01; &#x1f4e2;本文链接&#xff1a;https://xxmdmst.blog.csdn.n…

IU5066 高耐压带OVP保护1.2A单节锂电池线性充电IC

概要 IU5066E是面向空间受限便携应用的&#xff0c;高度集成锤离子和锤聚合物线性充电器器件。该器件由USB端口或交流适配器供电。带输入过压保护的高输入电压范围支持低成本、非稳压适配器。 电池充电经历以下三个阶段&#xff1a; 涓流、电流、恒压。在所有充电阶段&#x…

JSON对象(javascript)

本文内容主要包括了对于JS中JSON对象的一些内容。我们知道JSON格式是前后端进行信息交换的中介信息格式。适用于取代XML格式的一种格式&#xff0c;在多数编程语言中都有关于JSON的处理方法。那么javascript也提供了JSON对象用于处理相应的数据。 1. 什么是JSON格式&#xff1…

mac安装jdkidea配置jdk

第一步&#xff1a;mac安装jdk1、下载jdk&#xff0c;下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/#java82、安装后&#xff0c;终端输入java -version查看java是否安装成功3、配置环境变量a.在终端输入 /usr/libexec/java_home 可以得到JAVA_HOM…

【矩阵论】5. 线性空间与线性变换——线性映射与自然基分解,线性变换

矩阵论 1. 准备知识——复数域上矩阵,Hermite变换) 1.准备知识——复数域上的内积域正交阵 1.准备知识——Hermite阵&#xff0c;二次型&#xff0c;矩阵合同&#xff0c;正定阵&#xff0c;幂0阵&#xff0c;幂等阵&#xff0c;矩阵的秩 2. 矩阵分解——SVD准备知识——奇异值…

深度学习人体解析

人体解析旨在将图像或视频中的人体分割成多个像素级的语义部分。在过去的十年中&#xff0c;它在计算机视觉社区中获得了极大的兴趣&#xff0c;并在广泛的实际应用中得到了应用&#xff0c;从安全监控到社交媒体&#xff0c;再到视觉特效&#xff0c;这只是其中的一小部分。尽…

Markdown语法大全(够你用一辈子)

标题 # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 文本样式 > 引用文本 > 最外层 > > 第一层嵌套 > > > 第二层嵌套引用文本 最外层 第一层嵌套 第二层…

js中的call和apply

js中的call和apply1.call()可以调用某一函数2.call()可以这个函数的this指向3.call()也可以接受参数每次看到js中的call方法&#xff0c;都是懵逼的要去查查百度&#xff0c;自己研究记录下1.call()可以调用某一函数 testCall() {let person {fullName: function () {console.…

webpack基本使用

1、内置模块path &#xff08;1&#xff09;path模块用于对路径和文件进行处理&#xff0c;提供了很多好用的方法。 &#xff08;2&#xff09;我们知道在Mac OS、Linux和window上的路径时不一样的 window上会使用 \或者 \\ 来作为文件路径的分隔符&#xff0c;当然目前也支…

SpringBoot+VUE前后端分离项目学习笔记 - 【17 SpringBoot文件上传下载功能 MD5实现文件唯一标识】

Sql 数据库新建sys_file用来保存上传文件信息 CREATE TABLE sys_file (id int(11) NOT NULL AUTO_INCREMENT COMMENT id,name varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 文件名称,type varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 文…

STM32——I2C通信

文章目录I2C通信使用I2C通信的硬件设备硬件电路I2C时序基本单元起始与终止发送接收发送应答与接收应答I2C时序指定地址写当前地址读指定地址读连续读与写MPU6050简介MPU6050参数硬件电路MPU6050框图系统时钟MPU6050的中断源寄存器映像软件I2C读写MPU6050电路设计关键代码I2C通信…

C语言-扫雷

文章目录完整扫雷1. 说明2. 思路3. 各个功能实现3.1 雷盘初始化与打印1&#xff09;雷盘定义2&#xff09; 随机布置雷3.2 玩家排查雷1&#xff09; 获取坐标周围雷数2&#xff09; 递归展开3&#xff09;胜负判断3&#xff09; 显示雷位置4. 游戏试玩5. 游戏完整代码game.htes…

【定时任务】---- xxl-job、@Scheduled

一、Scheduled注解实现的定时任务 要实现计划任务&#xff0c;首先通过在配置类注解EnableScheduling来开启对计划任务的支持&#xff0c;然后在要执行计划任务的方法上注解Scheduled&#xff0c;声明这是一个计划任务。 在Spring Boot 的入口类 XXXApplication 中,必然会有S…