Qt详解实现TCP文件传输例子(文件下载和上传)附源码

网络通信我们用的很频繁,如文字,语音,文件,图片等,这个些传输方式都差不多

QT文件传输主要考验对传输的控制,还是需要点逻辑的,文件传输的大致框架如下

先看一下简单例子实现的效果(界面有点丑,重点在于内容):

接下来重点讲一下需要用到哪些东西:

1.数据流 QDataStream

通过数据流可以操作各种数据类型,包括类对象,存储到文件中数据可以还原到内存,对QDataStream不懂的,可以去看下我写的这个:QDataStream中 << 和 >> 输入输出重载的理解_qdatastream <<_只是个~小不点的博客-CSDN博客

在这个例子中用来封装传输消息类型,文件名,文件大小等数据

  1. QTcpSocket QTcpServer

QTcpServer当服务器,QTcpSocket当客户端用,用来作为文件传输对象

3.一个服务器和一个客户端(这个例子目前仅支持单向连接),先从简单的一对一开始理解,扩展到一对多,多对多就容易点了

服务端界面如下:

需要一个自定义一个文件对象,存在下载的文件信息:如文件名,文件大小,已经接收的字节数等,如下所示

接下来先将服务端的

定义一个枚举的消息类型,判断客户端想要哪些信息,文件信息还是文件数据

cpp 复制代码
//消息类型
enum MsgType{
    FileInfo,   //文件信息,如文件名,文件大小等信息
    FileData,   //文件数据,即文件内容
};

先看下流程:

服务端接收到客户端连接后,监听客户端消息,如果收到客户端发送接收的消息类型是FileInfo,就发送文件信息给它。收到客户端发送接收的消息类型是FileData,就发送文件数据给它。

传输文件信息时,需要获取要发送的文件信息,如文件名,文件大小等,然后将这些信息发送给客户端。客户端处理存储这些信息即可,接收文件数据的时候需要用到

具体实现如下

cpp 复制代码
void FileServer::transferFileInfo(QTcpSocket *socket)
{
    //获取文件数据,准备发送
    QByteArray  DataInfoBlock = getFileContent(ui->fileEdit->text());

    QThread::msleep(10); //添加延时
    m_fileInfoWriteBytes = socket->write(DataInfoBlock) - typeMsgSize;
    qDebug()<< "传输文件信息,大小:"<< m_sendFileSize;
    //等待发送完成,才能继续下次发送,否则发送过快,对方无法接收
    if(!socket->waitForBytesWritten(10*1000)) {
        ui->textBrowser->append(QString("网络请求超时,原因:%1").arg(socket->errorString()));
        return;
    }

    ui->textBrowser->append(QString("文件信息发送完成,开始对[%1]进行文件传输------------------")
                        .arg(socket->localAddress().toString()));
    qDebug()<<"当前文件传输线程id:"<<QThread::currentThreadId();

    m_localFile.setFileName(m_sendFilePath);
    if(!m_localFile.open(QFile::ReadOnly)){
        ui->textBrowser->append(QString("文件[%1]打开失败!").arg(m_sendFilePath));
        return;
    }
}

QByteArray FileServer::getFileContent(QString filePath)
{
    if(!QFile::exists(filePath)) {
        ui->textBrowser->append(QString("没有要传输的文件!" + filePath));
        return "";
    }
    m_sendFilePath = filePath;
    ui->textBrowser->append(QString("正在获取文件信息[%1]......").arg(filePath));
    QFileInfo info(filePath);

    //获取要发送的文件大小
    m_sendFileSize = info.size();

    ui->textBrowser->append(QString("要发送的文件大小:%1字节,%2M").arg(m_sendFileSize).arg(m_sendFileSize/1024/1024.0));

    //获取发送的文件名
    QString currentFileName=filePath.right(filePath.size()-filePath.lastIndexOf('/')-1);
    QByteArray DataInfoBlock;

    QDataStream sendOut(&DataInfoBlock,QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_12);
    int type = MsgType::FileInfo;
    //封装发送的信息到DataInfoBlock中
    sendOut<<int(type)<<QString(currentFileName)<<qint64(m_sendFileSize);

    ui->textBrowser->append(QString("文件[%1]信息获取完成!").arg(currentFileName));
    //发送的文件总大小中,信息类型不计入
    QString msg;
    if(m_sendFileSize>1024*1024) {
        msg = QString("%1M").arg(m_sendFileSize/1024/1024.0);
    }
    else {
        msg = QString("%1KB").arg(m_sendFileSize/1024.0);
    }
    ui->textBrowser->append(QString("发送的文件名:%1,文件大小:%2").arg(currentFileName).arg(msg));

    return DataInfoBlock;
}

关键在于发送的序列信息封装,依次为:

cpp 复制代码
    QByteArray DataInfoBlock;

    QDataStream sendOut(&DataInfoBlock,QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_12);
    int type = MsgType::FileInfo;
    //封装发送的信息到DataInfoBlock中
        //消息类型             文件名                  //文件大小
    sendOut<<int(type)<<QString(currentFileName)<<qint64(m_sendFileSize);

发送给客户端即可,客户端解析的时候也是按照这个顺序依次解析

发送给客户端后,等待客户端的下一步指令即可。客户端接收到文件信息后,都会发送获取文件数据的消息,服务器即可进行文件传输

变量qint64 payloadSize用来控制每次文件读取的字节数,progressByte用来存储发送的进度,我这里一次只发送1024个字节,因为区域网传输太快了,文明小传输过程不明显,你们可以调大小,比如1024*64个字节

接着就用while循环控制发送流程,直到发送的字节数等于文件的大小,说明文件数据发送完成

在循环中,要添加几微秒的延时来防止发送的文件帧过快,客户端接收不过来,导致丢包

传输文件的代码如下:

cpp 复制代码
void FileServer::transferFileData(QTcpSocket *socket)
{

    qint64 payloadSize = 1024*1; //每一帧发送1024*64个字节,控制每次读取文件的大小,从而传输速度

    double progressByte= 0;//发送进度
    qint64 bytesWritten=0;//已经发送的字节数

    while(bytesWritten != m_sendFileSize) {
        double temp = bytesWritten/1.0/m_sendFileSize*100;
        int  progress = static_cast<int>(bytesWritten/1.0/m_sendFileSize*100);
        if(bytesWritten<m_sendFileSize){

            QByteArray DataInfoBlock = m_localFile.read(qMin(m_sendFileSize,payloadSize));

            qint64 WriteBolockSize = socket->write(DataInfoBlock, DataInfoBlock.size());

            //QThread::msleep(1); //添加延时,防止服务端发送文件帧过快,否则发送过快,客户端接收不过来,导致丢包
            QThread::usleep(3); //添加延时,防止服务端发送文件帧过快,否则发送过快,客户端接收不过来,导致丢包
            //等待发送完成,才能继续下次发送,
            if(!socket->waitForBytesWritten(3*1000)) {
                ui->textBrowser->append("网络请求超时");
                return;
            }
            bytesWritten += WriteBolockSize;
            ui->sendProgressBar->setValue(progress);
        }

        if(bytesWritten==m_sendFileSize){
            //LogWrite::LOG_DEBUG(QString("当前更新进度:100%,发送总次数:%1").arg(count), "server_"+socket->localAddress().toString());
            ui->textBrowser->append(QString("当前上传进度:%1/%2 -> %3%").arg(bytesWritten).arg(m_sendFileSize).arg(progress));
            ui->textBrowser->append(QString("-------------对[%1]的文件传输完成!------------------").arg(socket->peerAddress().toString()));
            ui->sendProgressBar->setValue(100);
            m_localFile.close();
            return;
        }
        if(bytesWritten > m_sendFileSize) {
            ui->textBrowser->append("意外情况!!!");
            return;
        }

        if(bytesWritten/1.0/m_sendFileSize > progressByte) {
            ui->textBrowser->append(QString("当前上传进度:%1/%2 -> %3%").arg(bytesWritten).arg(m_sendFileSize).arg(progress));
            progressByte+=0.1;
        }

    }

}

服务端的核心代码讲完了,接下来将客户端的代码,界面如下:

先定义一个文件类对象,用来存储接收文件的对象,每个下载的文件就是一个文件类对象

当连接上服务器后,点击下载文件后,客户端会先发送获取文件的消息

服务端收到后,就会获取文件信息(流程上面讲过了),将文件信息发送给客户端

然后客户端根据服务器发送的消息类型处理消息

收到服务器发送的文件信息消息后,进行读取,获取文件名,文件大小,用文件类对象进行存储,新建准备写入一个要下载的文件,准备工作完成后,向服务器发送获取文件数据的消息

然后设置下载标识为true

复制代码
bool isDownloading; //是否正在下载标识

标识接下来接下来收到的将全是文件数据,接收即可,直到文件全部接收完成,在将其设为false

文件数据接收的代码流程这样子:

实现代码如下:

cpp 复制代码
void FileManager::fileDataRead()
{
    qint64 readBytes = m_tcpSocket->bytesAvailable();
    if(readBytes <0) return;

    int progress = 0;
    // 如果接收的数据大小小于要接收的文件大小,那么继续写入文件
    if(myFile->bytesReceived < myFile->fileSize) {
        // 返回等待读取的传入字节数
        QByteArray data = m_tcpSocket->read(readBytes);
        myFile->bytesReceived+=readBytes;
        ui->textBrowser->append(QString("接收进度:%1/%2(字节)").arg(myFile->bytesReceived).arg(myFile->fileSize));
        progress =static_cast<int>(myFile->bytesReceived*100/myFile->fileSize);
        myFile->progressByte = myFile->bytesReceived;
        myFile->progressStr = QString("%1").arg(progress);
        ui->progressBar->setValue(progress);
        myFile->localFile.write(data);
    }

    // 接收数据完成时
    if (myFile->bytesReceived==myFile->fileSize){
        ui->textBrowser->append(tr("接收文件[%1]成功!").arg(myFile->fileName));
        progress = 100;
        myFile->localFile.close();

        ui->textBrowser->append(QString("接收进度:%1/%2(字节)").arg(myFile->bytesReceived).arg(myFile->fileSize));
        myFile->progressByte = myFile->bytesReceived;
        ui->progressBar->setValue(progress);
        isDownloading = false;
        myFile->initReadData();
    }

    if (myFile->bytesReceived > myFile->fileSize){
        qDebug()<<"myFile->bytesReceived > m_fileSize";
    }
}

最终就达到这个效果啦:

所有源代码在这里,只有15kB,直接下载就行了

链接: https://pan.baidu.com/s/1xOuNstwxgEbRbzqQdJA2Aw?pwd=8888 提取码: 8888

有跟多小白私信我说,已运行就提示这个

这是因为服务器没有开的原因,连接不是导致的,因为我这是一个总工程,里面有两个子项目,一个是客户端,一个是服务器端,教一下怎么切项目吧,如下所示

除了这样切运行程序,也可以去生成程序的目录下启动

也可以在每个子项目的pro文件里面设置程序生成的位置,依赖库的位置等,这个一般要打包时程序给别的电脑用时必备的,这里就不说了

总结:这个文件传输的例子完成了文件传输的基本流程,基本传输能完成。但是如果要实现文件上传到服务器,并同时下载文件,就需要多线程了,同时,对编码能力也是个提升。界面可以做得好看点,获取到服务器文件列表后,可以选择下载,上传文件,删除文件等操作。难度会瞬间上升。

如下:

相关推荐
lisw053 小时前
网络化:DevOps 工程的必要基础(Networking: The Essential Foundation for DevOps Engineering)
网络·devops
驱动小百科5 小时前
WiFi出现感叹号上不了网怎么办 轻松恢复网络
网络·智能路由器·wifi出现感叹号怎么解决·wifi无法上网·电脑wifi
好多知识都想学5 小时前
协议路由与路由协议
网络·智能路由器
SZ1701102315 小时前
中继器的作用
服务器·网络·智能路由器
Huazzi.7 小时前
Ubuntu 22虚拟机【网络故障】快速解决指南
linux·网络·学习·ubuntu·bash·编程
熙曦Sakura7 小时前
【Linux网络】HTTP
linux·网络·http
毒果7 小时前
网络安全:账号密码与诈骗防范
网络·安全·web安全
八股文领域大手子7 小时前
SSL/TLS 证书与数字签名:构建互联网信任的详解
网络·网络协议·ssl
学渣676568 小时前
TCP/IP 模型每层的封装格式
网络