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()); } }欢迎交流。