“ 回到之前领导让我编写一个电子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:演示
————— |