2020.10.21更新,这个问题已经解决,接收端解析数据问题,发送端的延时可以去掉了!我稍后再写新文章(文章已发布《基于QT的TCP传输连续多个文件(目录)的实现》),详细更新方法和内容,因为项目原因,代码无法全部公开,但是只要认真看了文章和代码的人,肯定知道如何解决问题了。
================分割线===============
这个问题困扰了我一阵子,目前虽然解决了,但是依然存在几个问题。
首先感谢一下这篇文章在QT中使用TCP协议进行文件传输(可以单向循环传输),这篇文章提到了连续传输文件,我在此基础之上修改为连续发送文件和连续接收文件,但是发现一个问题,就是连续传送文件之间必须加上延迟,经过个人测试,在Windows上延迟间隔不要小于10ms,而在Linux下延迟间隔不要小于20ms,否则可能导致传输出错,具体表现在,下一个文件的传输数据会覆盖到上一个文件,而造成传输错误!
说起来这个问题表现很怪,为什么延时就能解决问题呢,难道改变了传输逻辑吗?
这可能需要对TCP/IP协议有较深刻的理解,而且对socket传输也要有较深刻的理解,而目前我经过反复测试,发现出错的时候,QTcpServer似乎把多余的数据引入到QTcpSocket中来了,而这个多余的数据很可能就是协议部分的内容,而导致文件传输失败,那么其实有一个解决办法就是,手动解析多余的数据,而直到有用的数据接收成功,这只是理论上可行的,但是实际可能存在解析效率的问题,如果解析效率太低,则不如使用延时的方式解决问题。
不知道有没有谁能提供QT传输文件夹的范例呢,如果没有延时处理是最好的,欢迎赐教。
下面是我使用的源代码,给需要的读者参考,可以设置保存文件的位置,需要接收端配合。
头文件:
//author:autumoon
//联系QQ:4589968
//日期:2020-10-20
#ifndef TCPUPLOADCLIENT_H
#define TCPUPLOADCLIENT_H
#include <QObject>
#include <QTcpSocket>
#include <QFile>
#include <QLabel>
#include <QLineEdit>
#include <QElapsedTimer>
class TcpUploadClient : public QObject
{
Q_OBJECT
public:
explicit TcpUploadClient(QObject *parent = nullptr);
void SetLableStatus(QLabel *lableStatus){m_lbStatus = lableStatus;}
void SetHostAndPort(const QString& strHost, const quint16& nPort){m_strHost = strHost; m_nPort = nPort;}
//需要用QLineEdit显示主机和端口调用
void SetLineEditHostAndPort(QLineEdit *leHost, QLineEdit *lePort){m_leHost = leHost; m_lePort = lePort;}
public:
void StartUpload(const QString& strLoaclPath, const QString& strServerFilePath);
//vServerFilePaths 用于指定要存储到服务器上的全路径
void StartUpload(const QStringList& lLocalPaths, const QStringList& lServerFilePaths);
signals:
void progress(qint64, qint64);
void progress_file(qint64, qint64);
void finished(bool);
void finished_file(bool);
private slots:
void displayError(QAbstractSocket::SocketError);
void goOnSend(qint64); //传送文件内容
void send(); //传送文件头信息
private:
bool setUploadFilePath(const QString& strFilePath, const QString& strServerPath);
void finishedAll(); //全部文件传输完成
void initialize();
void release();
void sleep(int msec);
private:
//界面相关
QLineEdit *m_leHost;
QLineEdit *m_lePort;
QLabel *m_lbStatus;
QElapsedTimer m_timer;
QString m_strHost;
quint16 m_nPort;
QTcpSocket *m_tcpClient;
QFile *m_localFile;
qint64 m_totalBytes;
qint64 m_bytesWritten;
qint64 m_bytesToWrite;
qint64 m_payloadSize;
QString m_filePath;
QString m_strServerFilePath;
QByteArray m_outBlock;
//传输多个文件
int m_nFileIndex;
QStringList m_lLocalPaths;
QStringList m_lServerFilePaths;
};
#endif // TCPUPLOADCLIENT_H
实现文件:
//author:autumoon
//联系QQ:4589968
//日期:2020-10-20
#include "TcpUploadClient.h"
#include <QTextCodec>
#include <QDataStream>
#include <QEventLoop>
#include <QTimer>
TcpUploadClient::TcpUploadClient(QObject *parent) : QObject(parent)
{
//界面相关
m_leHost = nullptr;
m_lePort = nullptr;
m_lbStatus = nullptr;
m_nPort = 9999;
m_tcpClient = nullptr;
m_localFile = nullptr;
m_totalBytes = 0;
m_bytesWritten = 0;
m_bytesToWrite = 0;
//每次传输量
m_payloadSize = 64 * 1024;
//传输多个文件
m_nFileIndex = 0;
QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
}
void TcpUploadClient::initialize()
{
m_tcpClient = new QTcpSocket(this);
m_localFile = nullptr;
m_totalBytes = 0;
m_bytesWritten = 0;
m_bytesToWrite = 0;
//传输多个文件
m_nFileIndex = 0;
connect(m_tcpClient, SIGNAL(connected()), this, SLOT(send())); //当连接成功时,就开始传送文件
connect(m_tcpClient, SIGNAL(bytesWritten(qint64)), this, SLOT(goOnSend(qint64)));
}
void TcpUploadClient::release()
{
if (m_tcpClient)
{
disconnect(m_tcpClient, SIGNAL(connected()), this, SLOT(send()));
disconnect(m_tcpClient, SIGNAL(bytesWritten(qint64)), this, SLOT(goOnSend(qint64)));
m_tcpClient->close();
m_tcpClient->deleteLater();
}
}
void TcpUploadClient::StartUpload(const QString &strLoaclPath, const QString &strServerFilePath)
{
m_lLocalPaths.clear();
m_lLocalPaths.push_back(strLoaclPath);
m_lServerFilePaths.clear();
m_lServerFilePaths.push_back(strServerFilePath);
initialize();
if (setUploadFilePath(m_lLocalPaths[m_nFileIndex], m_lServerFilePaths[m_nFileIndex]))
{
m_tcpClient->connectToHost(m_strHost, m_nPort);
}
}
void TcpUploadClient::StartUpload(const QStringList &lLocalPaths, const QStringList &lServerFilePaths)
{
m_lLocalPaths = lLocalPaths;
m_lServerFilePaths = lServerFilePaths;
initialize();
if (setUploadFilePath(m_lLocalPaths[m_nFileIndex], m_lServerFilePaths[m_nFileIndex]))
{
m_tcpClient->connectToHost(m_strHost, m_nPort);
}
}
void TcpUploadClient::send()
{
if (m_localFile == nullptr || m_tcpClient == nullptr)
{
return;
}
if (m_lbStatus)
{
m_timer.restart();
}
m_bytesToWrite = m_localFile->size(); //剩余数据的大小
m_totalBytes = m_bytesToWrite;
QDataStream out(&m_outBlock, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_6);
out<<qint64(0)<<qint64(0)<<m_strServerFilePath;
m_totalBytes += m_outBlock.size(); //总大小为文件大小加上文件名等信息大小
m_bytesToWrite += m_outBlock.size();
out.device()->seek(0); //回到字节流起点来写好前面连个qint64,分别为总大小和文件名等信息大小
out<<m_totalBytes<<qint64(m_outBlock.size());
m_tcpClient->write(m_outBlock); //将读到的文件发送到套接字
if (m_lbStatus)
{
m_lbStatus->setText("已连接");
}
m_outBlock.clear();
}
void TcpUploadClient::goOnSend(qint64 numBytes)
{
m_bytesToWrite -= numBytes; //剩余数据大小
m_outBlock = m_localFile->read(qMin(m_bytesToWrite, m_payloadSize));
m_tcpClient->write(m_outBlock);
if(m_bytesToWrite == 0) //发送完毕
{
if (m_lbStatus)
{
QString strInfo = QString("传送文件 %1 成功").arg(m_filePath);
m_lbStatus->setText(strInfo);
qDebug() << strInfo;
}
m_localFile->close();
m_localFile = nullptr;
int nFileCount = m_lLocalPaths.size();
//传输进度
emit progress(m_nFileIndex + 1, nFileCount);
emit finished_file(true);
if (++m_nFileIndex < nFileCount)
{
sleep(10);
if (setUploadFilePath(m_lLocalPaths[m_nFileIndex], m_lServerFilePaths[m_nFileIndex]))
{
send();
}
}
else
{
finishedAll();
}
}
//发送进度
if (m_lbStatus)
{
float useTime = m_timer.elapsed();
double speed = m_bytesWritten / useTime;
m_lbStatus->setText(tr("已上传 %1MB (%2MB/s) \n共%3MB 已用时:%4秒\n估计剩余时间:%5秒")
.arg(m_bytesWritten / (1024*1024))//已上传
.arg(speed*1000/(1024*1024),0,'f',2)//速度
.arg(m_totalBytes / (1024 * 1024))//总大小
.arg(useTime/1000,0,'f',0)//用时
.arg(m_totalBytes/speed/1000 - useTime/1000,0,'f',0));//剩余时间
}
emit progress_file(m_bytesWritten, m_totalBytes);
}
bool TcpUploadClient::setUploadFilePath(const QString& strFilePath, const QString& strServerPath)
{
m_bytesToWrite = 0;
m_totalBytes = 0;
m_outBlock.clear();
m_filePath = strFilePath;
m_strServerFilePath = strServerPath;
m_localFile = new QFile(m_filePath);
if(!m_localFile->open(QFile::ReadOnly))
{
qDebug()<<"client:open file error!";
return false;
}
qDebug() << "正在传输:" << strFilePath;
return true;
}
void TcpUploadClient::sleep(int msec)
{
QEventLoop loop;//定义一个新的事件循环
QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数
loop.exec();//事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
}
void TcpUploadClient::displayError(QAbstractSocket::SocketError)
{
qDebug()<<m_tcpClient->errorString();
m_tcpClient->close();
emit finished(false);
if (m_lbStatus)
{
m_lbStatus->setText("上次传输出现错误!客户端重新就绪!");
}
}
void TcpUploadClient::finishedAll()
{
release();
emit finished(true);
}
然后是接收端:
//author:autumoon
//联系QQ:4589968
//日期:2020-10-20
#ifndef TCPUPLOADSERVER_H
#define TCPUPLOADSERVER_H
#include <QObject>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QTcpSocket>
#include <QFile>
#include <QLabel>
#include <QElapsedTimer>
class TcpUploadServer : public QObject
{
Q_OBJECT
public:
explicit TcpUploadServer(QObject *parent = nullptr);
bool StartServer();
void SetLableStatus(QLabel *lableStatus){m_lbStatus = lableStatus;}
void SetPort(const quint16& nPort){m_nPort = nPort;}
signals:
void begin();
void progress(qint64, qint64);
void finished(bool bSuccess);
private slots:
void acceptConnection();
void readClient();
void displayError(QAbstractSocket::SocketError socketError);
private:
void initialize();
void release();
private:
//界面相关
QLabel *m_lbStatus;
quint16 m_nPort;
QElapsedTimer m_timer;
QTcpServer *m_tcpServer;
QTcpSocket *m_tcpReceivedSocket;
qint64 m_totalBytes;
qint64 m_bytesReceived;
qint64 m_filePathSize;
QString m_filePathName;
QFile *m_localFile;
QByteArray m_inBlock;
};
#endif // TCPUPLOADSERVER_H
实现文件:
//author:autumoon
//联系QQ:4589968
//日期:2020-10-20
#include "TcpUploadServer.h"
#include <QTextCodec>
#include <QDataStream>
#include <QFileInfo>
#include "QStdDirFile.h"
TcpUploadServer::TcpUploadServer(QObject *parent) : QObject(parent)
{
//界面相关
m_lbStatus = nullptr;
m_nPort = 9999;
m_tcpServer = nullptr;
m_tcpReceivedSocket = nullptr;
m_totalBytes = 0;
m_bytesReceived = 0;
m_filePathSize = 0;
m_localFile = 0;
QTextCodec::setCodecForLocale(QTextCodec::codecForName("GBK"));
}
void TcpUploadServer::initialize()
{
m_totalBytes = 0;
m_bytesReceived = 0;
m_filePathSize = 0;
m_tcpServer = new QTcpServer(this);
connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
}
void TcpUploadServer::release()
{
if (m_tcpReceivedSocket)
{
disconnect(m_tcpReceivedSocket, SIGNAL(readyRead()), this, SLOT(readClient()));
disconnect(m_tcpReceivedSocket, SIGNAL(error(QAbstractSocket::SocketError)), this ,SLOT(displayError(QAbstractSocket::SocketError)));
m_tcpReceivedSocket->close();
m_tcpReceivedSocket->deleteLater();
}
if (m_tcpServer)
{
disconnect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
m_tcpServer->close();
m_tcpServer->deleteLater();
}
}
bool TcpUploadServer::StartServer()
{
if (m_tcpServer)
{
release();
}
initialize();
qDebug()<<"正在启动服务...";
if(!m_tcpServer->listen(QHostAddress("localhost"), m_nPort))
{
qDebug()<<m_tcpServer->errorString();
return false;
}
qDebug() << "监听端口" << m_nPort << "成功!";
if (m_lbStatus)
{
m_lbStatus->setText("正在监听...");
}
return true ;
}
void TcpUploadServer::acceptConnection()
{
emit begin();
if (m_lbStatus)
{
m_timer.restart();
}
m_tcpReceivedSocket = m_tcpServer->nextPendingConnection();
connect(m_tcpReceivedSocket, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(m_tcpReceivedSocket, SIGNAL(error(QAbstractSocket::SocketError)), this ,SLOT(displayError(QAbstractSocket::SocketError)));
if (m_lbStatus)
{
m_lbStatus->setText("接受连接");
}
}
void TcpUploadServer::readClient()
{
if(m_bytesReceived<= sizeof(qint64)*2) //才刚开始接收数据,此数据为文件信息
{
QDataStream in(m_tcpReceivedSocket);
in.setVersion(QDataStream::Qt_5_6);
//in>>m_totalBytes>>m_bytesReceived>>m_filePathName;
if((m_tcpReceivedSocket->bytesAvailable()>=sizeof(qint64)*2)&&(m_filePathSize==0))
{
// 接收数据总大小信息和带路径的文件名大小信息
in>>m_totalBytes>>m_filePathSize;
m_bytesReceived +=sizeof(qint64)*2;
}
if((m_tcpReceivedSocket->bytesAvailable()>=m_filePathSize)&&(m_filePathSize!=0))
{
// 接收文件名,并建立文件
in>>m_filePathName;
//传输出现错误
if (m_filePathName.length() == 0)
{
return;
}
//可能需要建立文件夹
if (m_filePathName.indexOf("/") != -1)
{
QString strFileName = CStdStr::GetNameOfFile(m_filePathName, '/');
QString strSaveDir = CStdStr::GetDirOfFile(m_filePathName);
QFileInfo fiDir(strSaveDir);
if (!fiDir.exists()&& !CStdDir::createDirectory(strSaveDir))
{
if (m_lbStatus)
{
m_lbStatus->setText(tr("接收文件 %1 失败!").arg(m_filePathName));
}
qDebug() << (tr("接收文件 %1 失败!").arg(m_filePathName));
emit finished(false);
return;
}
}
m_localFile = new QFile(m_filePathName);
if (!m_localFile->open(QFile::WriteOnly))
{
qDebug() << (tr("创建文件 %1 失败!").arg(m_filePathName));
return;
}
//注意此处是赋值而不是+=
m_bytesReceived = m_filePathSize;
if (m_lbStatus)
{
m_timer.restart();
}
}
}
else //正式读取文件内容
{
m_inBlock = m_tcpReceivedSocket->readAll();
m_bytesReceived += m_inBlock.size();
m_localFile->write(m_inBlock);
m_inBlock.clear();
if (m_lbStatus)
{
float useTime = m_timer.elapsed();
double speed = m_bytesReceived / useTime;
m_lbStatus->setText(tr("已接收 %1MB (%2MB/s) \n共%3MB 已用时:%4秒\n估计剩余时间:%5秒")
.arg(m_bytesReceived / (1024*1024))//已接收
.arg(speed*1000/(1024*1024),0,'f',2)//速度
.arg(m_totalBytes / (1024 * 1024))//总大小
.arg(useTime/1000,0,'f',0)//用时
.arg(m_totalBytes/speed/1000 - useTime/1000,0,'f',0));//剩余时间
}
emit progress(m_bytesReceived, m_totalBytes);
}
if(m_bytesReceived == m_totalBytes)
{
m_localFile->flush();
m_localFile->close();
m_localFile = nullptr;
m_inBlock.clear();
m_bytesReceived = 0;
m_totalBytes = 0;
m_filePathSize = 0;
emit finished(true);
}
else if (m_bytesReceived > m_totalBytes)
{
qDebug() << "超量接收!请增加发送延迟!";
}
}
void TcpUploadServer::displayError(QAbstractSocket::SocketError socketError)
{
Q_UNUSED(socketError)
qDebug()<<m_tcpReceivedSocket->errorString();
if (m_lbStatus)
{
m_lbStatus->setText(m_tcpReceivedSocket->errorString());
}
}
欢迎交流。