Qt案例学习:Qt5.14.2使用TCP通讯传输文件

[复制链接]
查看24158 | 回复0 | 2025-1-2 08:22:34 | 显示全部楼层 |阅读模式
“ 回到之前领导让我编写一个电子SOP的功能,在网上查找的一些案例,需要实现办公室的电脑主机向线体的电子SOP面板传输SOP.pdf的功能,因此想到触摸屏的程序下载,可以从编程电脑往触摸屏上传输程序,刚好在《Qt Creator快速入门》第三版中第18.4节中有讲述到TCP传输的功能,接下来讲述如何利用Qt编程实现功能。”

01:什么是TCP?

—————————

传输控制协议(TCP,TransmissionControl Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
https://baike.baidu.com/item/TCP/33012?fr=ge_ala
RFC 793的网址:https://datatracker.ietf.org/doc/html/rfc793。

TCP旨在适应支持多网络应用的分层协议层次结构,互连的计算机通信网络中成对的应用程序进程之间能够依靠TCP提供可靠的通信服务来传输字节流。TCP支持双向数据流,应用程序也可以仅单向发送数据。在主机之间,TCP使用端口号标识应用程序服务并且可以多路传输数据流。
https://baike.baidu.com/item/TCP/33012?fr=ge_ala


02:什么是客户端和服务端?

————————————————
服务器‌:服务器的主要功能是监听来自客户端的连接请求,并在接受连接后进行数据传输。服务器通常绑定到一个固定的IP地址和端口号上,等待客户端的连接请求。一旦接受连接,服务器会创建一个新的套接字与客户端进行通信‌。服务器的操作流程如下:
    ‌创建套接字‌:服务器首先创建一个套接字。绑定套接字‌:将套接字绑定到一个固定的IP地址和端口号上。监听连接请求‌:服务器进入监听状态,等待客户端的连接请求。‌接受连接‌:当收到客户端的连接请求时,服务器接受连接并创建一个新的套接字与客户端通信。‌数据传输‌:通过新创建的套接字与客户端进行数据交换。‌关闭连接‌:数据传输完成后,服务器可以关闭与客户端的连接‌。
客户端‌:客户端的主要功能是主动发起连接请求,连接到服务器并进行数据交换。客户端不固定监听任何端口,而是动态选择一个随机端口号来发起连接请求‌。客户端的操作流程如下:
    ‌创建套接字‌:客户端创建一个套接字。‌发起连接‌:客户端主动连接到服务器的IP地址和端口号,发起连接请求。‌数据传输‌:连接建立后,客户端通过套接字发送请求并接收服务器的响应。‌关闭连接‌:数据交换完成后,客户端可以关闭套接字,结束与服务器的通信‌
所以电子SOP端就作为服务器,负责传输文件的电脑就作为客户端。
03:如何在Qt5.14.2编写客户端的程序

————————————————————

首先,建立一个Client类,以QDialog为父类:第二,在Pro中添加network:QT       += core gui network第三,client.h的代码如下:#ifndef CLIENT_H#define CLIENT_H#include <QDialog>#include <QAbstractSocket>QT_BEGIN_NAMESPACEnamespace Ui { class Client; }QT_END_NAMESPACEclass QTcpSocket;class QFile;class Client : public QDialog{    Q_OBJECTpublic:    Client(QWidget *parent = nullptr);    ~Client();private:    Ui::Client *ui;    QTcpSocket *tcpClient;    QFile *localFile;           //要发送的文件    qint64 totalBytes;          //发送数据的总大小    qint64 bytesWriten;         //已经发送数据的大小    qint64 bytesToWirte;        //剩余数据的大小    qint64 payloadSize;         //每次发送数据的大小    QString fileName;           //保存文件的路径    QByteArray outBlock;        //数据缓冲区,即存放每次要发送的数据块private slots:    void openFile();//打开文件    void send();//开始发送的程序    void startTransfer();//一旦与服务端进行连接了,就开始进行传输    void updateClientProgress(qint64);//读取传输的进度,不断更新进度调    void displayError(QAbstractSocket::SocketError);    void on_openButton_clicked();    void on_sendButton_clicked();};#endif // CLIENT_H#include<QAbstractSocket>该处的<QAbstractSocket>是QTcpSocket的基类,在这里添加该头文件是令void displayError(QAbstractSocket::SocketError)可以实现功能。class QTcpSocket;class QFile;QTcpSocket和QFile是添加的前置声明。之后需要添加几个私有变量:    QTcpSocket *tcpClient;      //客户端的类    QFile *localFile;           //要发送的文件    qint64 totalBytes;          //发送数据的总大小    qint64 bytesWriten;         //已经发送数据的大小    qint64 bytesToWirte;        //剩余数据的大小    qint64 payloadSize;         //每次发送数据的大小    QString fileName;           //保存文件的路径    QByteArray outBlock;        //数据缓冲区,即存放每次要发送的数据块之后再添加私有槽:    void openFile();//打开文件    void send();//开始发送的程序    void startTransfer();//一旦与服务端进行连接了,就开始进行传输    void updateClientProgress(qint64);//读取传输的进度,不断更新进度调    void displayError(QAbstractSocket::SocketError);    void on_openButton_clicked();    void on_sendButton_clicked();第四,client.cpp的程序如下,需要先添加QtNetwork和QFileDialog的头文件:#include<QtNetwork>#include <QFileDialog>构造函数如下:Client::Client(QWidget *parent)    : QDialog(parent)    , ui(new Ui::Client){    ui->setupUi(this);    payloadSize = 64*1024;//64KB 每次发送数据的大小,TCP传输规定,需要分割成64KB    totalBytes = 0;//文件的总大小    bytesWriten = 0;//已经发送的数据的大小    bytesToWirte = 0;//等待发送的数据的大小    tcpClient = new QTcpSocket(this);//建立一个客户端    //连接服务器成功时,会发出connected()信号,开始传送文件    connect(tcpClient,SIGNAL(connected()),this,SLOT(startTransfer()));    //不断写入数据的时候,就会更新进度条    connect(tcpClient,SIGNAL(bytesWritten(qint64)),this,SLOT(updateClientProgress(qint64)));    connect(tcpClient,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocker::SockerError)));    ui->sendButton->setEnabled(false);}析构函数如下:Client::~Client(){    delete ui;}打开文件的函数如下:void Client::openFile(){    fileName = QFileDialog::getOpenFileName(this);    if(!fileName.isEmpty()){        ui->sendButton->setEnabled(true);        ui->clientStatusLabel->setText(QString::fromLocal8Bit("打开文件%1成功").arg(fileName));    }}开启发送的函数如下:void Client::send(){    ui->sendButton->setEnabled(false);    //初始化已发送字节为0    bytesWriten = 0;    ui->clientStatusLabel->setText(QString::fromLocal8Bit("连接中..."));    //调用了connectToHost()函数来连接服务器    tcpClient->connectToHost(ui->hostLineEdit->text(),ui->portLineEdit->text().toUInt());}发送文件信息的函数如下:void Client::startTransfer(){    localFile = new QFile(fileName);    if(!localFile->open(QFile::ReadOnly)){        qDebug()<<"client:open file error!";        return;    }    //获取文件大小    totalBytes = localFile->size();    //使用了QByteArray对象来暂存要发送的数据,然后使用数据流将要发送的数据写到QByteArray对象    //QDataStream类负责以二进制方式读写程序中的对象,输入源和输出目样标可以是QIODevice,QByteArray    //见《Qt5.10 GUI完全参考手册》 第846页    //通过sendOut将数据写入到outBlock    QDataStream sendOut(&outBlock,QIODevice::WriteOnly);    sendOut.setVersion(QDataStream::Qt_4_0);    QString currentFileName = fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);    //保留总大小的信息空间,文件名大小信息空间,然后输入文件名    sendOut<<qint64(0)<<qint64(0)<<currentFileName;    //这里的总大小是总大小信息,文件名大小信息,文件名和时间文件大小的综合    totalBytes += outBlock.size();    //返回outBlockd的开始,用实际的大小信息代替两个qint64(0)的空间    sendOut.device()->seek(0);    sendOut<<totalBytes<<qint64((outBlock.size()-sizeof(qint64)*2));    //发送完文件头结构后剩余数据的大小    bytesToWirte = totalBytes-tcpClient->write(outBlock);//写入文件大小的信息以及文件名    ui->clientStatusLabel->setText(QString::fromLocal8Bit("已连接"));    outBlock.resize(0);}更新进度条和持续写入的函数如下,调试后发现,每一次经过tcpClient->write(const QByteArray &data),都会发出信号bytesWritten(qint64),从而触发了槽函数updateClientProgress(qint64)进行了每一次的更新。:void Client::updateClientProgress(qint64 numBytes){    //已经发送数据的大小    bytesWriten += (int)numBytes;    //如果已经发送了数据    if(bytesWriten>0){        //每次发送payloadSize大小的数据,这里设置为64KB,如果剩余的数据不足64KNB        //就发送剩余数据的大小        outBlock = localFile->read(qMin(bytesToWirte,payloadSize));        //发送完一次数据后还剩余数据的大小        bytesToWirte -= (int)tcpClient->write(outBlock);        //清空发送缓冲区        outBlock.resize(0);    }else{//如果没用发送任何数据,则关闭文件        localFile->close();    }    //更新进度条    ui->clientProgressBar->setMaximum(totalBytes);    ui->clientProgressBar->setValue(bytesWriten);    //如果发送完毕    if(bytesWriten == totalBytes){        ui->clientStatusLabel->setText(QString::fromLocal8Bit("传送文件%1成功").arg(fileName));        localFile->close();        tcpClient->close();    }}异常处理的函数如下:void Client::displayError(QAbstractSocket::SocketError){    qDebug()<<tcpClient->errorString();    tcpClient->close();    ui->clientProgressBar->reset();    ui->clientStatusLabel->setText(QString::fromLocal8Bit("客户端就绪"));    ui->sendButton->setEnabled(true);}最后是两个UI界面的按钮的槽函数:void Client::on_openButton_clicked(){    ui->clientProgressBar->reset();    ui->clientStatusLabel->setText(QString::fromLocal8Bit("状态:等待打开文件!"));    openFile();}void Client::on_sendButton_clicked(){    send();}第五是UI的界面以及控件的变量名:


04:如何在Qt5.14.2服务器的程序

—————————————————
首先,建立一个Server类,以QDialog为父类:第二,在pro文件中添加Qt+= networkQT       += core gui network第三,在server.h中的程序如下:#ifndef SERVER_H#define SERVER_H#include <QDialog>#include <QAbstractSocket>#include <QTcpServer>class QTcpSocket;class QFile;QT_BEGIN_NAMESPACEnamespace Ui { class Server; }QT_END_NAMESPACEclass Server : public QDialog{    Q_OBJECTpublic:    Server(QWidget *parent = nullptr);    ~Server();private:    Ui::Server *ui;    QTcpServer tcpServer;    QTcpSocket *tcpServerConnection;    qint64 totalBytes;      //存放总大小信息    qint64 bytesReceived;   //已收到数据的大小    qint64 fileNameSize;    //文件名的大小信息    QString fileName;       //存放文件名    QFile *localFile;       //本地文件    QByteArray inBlock;     //数据缓冲区private slots:    void start();    void acceptConnection();    void updateServerProgress();    void displayError(QAbstractSocket::SocketError socketError);    void on_startButton_clicked();};#endif // SERVER_H第四,server.cpp的构造函数如下:Server::Server(QWidget *parent)    : QDialog(parent)    , ui(new Ui::Server){    ui->setupUi(this);    //当服务器接收到连接信息时候,就进行接收连接    connect(&tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));}以下是析构函数:Server::~Server(){    delete ui;}以下是监听按钮触发的开始监听的槽函数:void Server::start(){    if(!tcpServer.listen(QHostAddress(ui->IPlineEdit->text()),6666)){        qDebug()<<tcpServer.errorString();        close();        return;    }    ui->startButton->setEnabled(false);    totalBytes=0;    bytesReceived = 0;    fileNameSize = 0;    ui->serverStatusLabel->setText(QString::fromLocal8Bit("监听"));    ui->serverProgressBar->reset();}以下是接收连接的槽函数:void Server::acceptConnection(){    //使用nextPendingConnection()来获取连接的QTcpSocket对象    tcpServerConnection = tcpServer.nextPendingConnection();    //当有数据写入准备读取,则会触发数据读取以及进度条更新    connect(tcpServerConnection,SIGNAL(readyRead()),this,SLOT(updateServerProgress()));    connect(tcpServerConnection,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));    ui->serverStatusLabel->setText(QString::fromLocal8Bit("接受连接"));    //关闭服务器,不再进行监听    tcpServer.close();}以下是数据的接收和进度条的更新:void Server::updateServerProgress(){    //建立从tcpServerConnection中读取数据的数据流in    QDataStream in(tcpServerConnection);    in.setVersion(QDataStream::Qt_4_0);    //如果接收到的数据小于16个字节,保存到来的文件头结构    //第一次bytesReceived=0,得到文件的大小以及文件名的大小    //第二次bytesReceived=sizeof(qint64)*2,得到文件名,同时新建一个文件    if(bytesReceived <= sizeof(qint64)*2){        if((tcpServerConnection->bytesAvailable()>=sizeof(qint64)*2) && (fileNameSize==0)){            //接收数据总大小信息和文件名大小信息            in>>totalBytes>>fileNameSize;            bytesReceived += sizeof(qint64) * 2;        }        if((tcpServerConnection->bytesAvailable()>=fileNameSize) && (fileNameSize != 0)){            //接收文件名,并建立文件            in>>fileName;            ui->serverStatusLabel->setText(QString::fromLocal8Bit("接收文件%1...").arg(fileName));            bytesReceived += fileNameSize;            localFile = new QFile(fileName);            if(!localFile->open(QFile::WriteOnly)){                qDebug()<<"server:open file error!";                return;            }else {                return;            }        }    }    //如果接收的数据小于总数居,那么写入文件    if(bytesReceived<totalBytes){        bytesReceived += tcpServerConnection->bytesAvailable();        inBlock = tcpServerConnection->readAll();        localFile->write(inBlock);        inBlock.resize(0);    }    ui->serverProgressBar->setMaximum(totalBytes);    ui->serverProgressBar->setValue(bytesReceived);    //接收数据完成时    if(bytesReceived == totalBytes){        tcpServerConnection->close();        localFile->close();        ui->startButton->setEnabled(true);        ui->serverStatusLabel->setText(QString::fromLocal8Bit("接收文件%1成功!").arg(fileName));    }}接下来是信息错误的反馈函数:void Server::displayError(QAbstractSocket::SocketError socketError){    qDebug()<<tcpServerConnection->errorString();    tcpServerConnection->close();    ui->serverProgressBar->reset();    ui->serverStatusLabel->setText(QString::fromLocal8Bit("服务端就绪"));    ui->startButton->setEnabled(true);}最后是UI界面开始监听按钮的槽函数:void Server::displayError(QAbstractSocket::SocketError socketError){    qDebug()<<tcpServerConnection->errorString();    tcpServerConnection->close();    ui->serverProgressBar->reset();    ui->serverStatusLabel->setText(QString::fromLocal8Bit("服务端就绪"));    ui->startButton->setEnabled(true);}第五是UI界面及其变量名:


05:演示
—————

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册哦

x
您需要登录后才可以回帖 登录 | 注册哦

本版积分规则