QT 学习笔记(十六)

news/2025/2/14 1:54:15/

文章目录

  • 一、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_OBJECTpublic: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连包问题//需要通过定时器延时20mstimer.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_OBJECTpublic: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.ppmy.cn/news/10662.html

相关文章

如何用智能地教狗狗上厕所

背景 22年养了一只很可爱的小狗狗&#xff0c;我其实就一个问题&#xff1a;为啥这么可爱的狗狗会拉屎撒尿呀&#xff1f; 自从崽崽来了我们家之后&#xff0c;最让我们头疼的就是它乱拉、乱尿的问题了&#xff0c;以前会在家里到处乱来&#xff0c;最近一段时间好了很多&…

自动化测试Seleniums~1

一.什么是自动化测试 1.自动化测试介绍 自动化测试指软件测试的自动化&#xff0c;在预设状态下运行应用程序或者系统&#xff0c;预设条件包括正常和异常&#xff0c;最后评估运行结果。将人为驱动的测试行为转化为机器执行的过程。 将测试人员双手解放&#xff0c;将部分测…

【博客579】netfilter network flow 和 routing decision的网络流处理交互关系

netfilter网络流转&#xff08;network flow&#xff09;与路由决策&#xff08;routing decision&#xff09;的网络流处理交互关系 1、场景&#xff1a; 我们可以通过iptables来基于netfilter机制下发我们的hook处理函数&#xff0c;那么我们平时iptables的四表五链与报文的…

yarn包管理器

快速、可靠、安全的依赖管理工具。和 npm 类似, 都是包管理工具, 可以用于下载包, 就是比npm快 中文官网地址: Yarn 中文文档 1、下载yarn 使用node下载 npm install --global yarn 官方推荐下载&#xff1a;地址 2、使用yarn 与npm类似, 可以试试, 新建一个空白文件夹, …

python初级教程十二 uWSGI 安装配置

uWSGI 安装配置 本文主要介绍如何部署简单的 WSGI 应用和常见的 Web 框架。 以 Ubuntu/Debian 为例&#xff0c;先安装依赖包&#xff1a; apt-get install build-essential python-dev Python 安装 uWSGI 1、通过 pip 命令&#xff1a; pip install uwsgi 2、下载安装脚本&…

网易云MUSIC年终奖0.5?听到消息我扔了耳机

一、百度 1.MEG开始突然抓考勤&#xff0c;多个团队口头通知&#xff0c;要求早上10点之前到公司。同时工作日下班免费打车的时间将从9点改成10点&#xff0c;预计本周开始实行&#xff0c;这意味着“早十晚十”成为了公司倡导的主旋律。没错&#xff0c;为什么要有下班呢&…

基于K8s的DevOps平台实践(三)

文章目录前言1. Jenkins与k8s集成&#x1f351; 插件安装及配置&#x1f351; 演示动态slave pod&#x1f351; Pod-Template中容器镜像的制作&#x1f351; 实践通过Jenkinsfile实现demo项目自动发布到kubenetes环境2. Jenkins集成Sonarqube&#x1f351; sonarqube架构简介&a…

基于Promethus+Grafana搭建监控系统

简介 ● 监测数据类型&#xff1a;JVM数据、在线人数、消息时延等 ● 接入Prometheus性能监测工具&#xff0c;暴露服务器性能监测数据 ○ 模式&#xff1a;pull/push ● 接入Grafana可视化数据 搭建流程 准备环境 Java客户端配置 <!-- The client --> <depende…