Qt网络编程之TCP通信(二)文件传输

  • Post author:
  • Post category:其他




一、流程图


上一篇博客

介绍了Qt下Tcp通信流程,并实现了简单的文本传输,本文主要介绍Tcp文件传输过程。

流程图如下:

  1. 服务器
Created with Raphaël 2.2.0 开始 创建监听套接字对象 和通信套接字对象 监听套接字监听指定的Ip和端口 是否有新的 连接请求? 获取客户端信息 选择文件,并 发送文件信息 是否收 到应答? 发送文件 是否发 送完成? 结束 yes no yes no yes no
  1. 客户端
Created with Raphaël 2.2.0 开始 创建通信套接字对象 连接到指定IP和端口 是否接收 到数据? 是否是第一 帧数据? 发送应答信息 选取保存路径 数据写入文件 文件是否写 入完毕? 结束 yes no yes no yes no



二、项目创建

  1. 服务器

    在这里插入图片描述

    在这里插入图片描述

    1)创建监听套接字和通信套接字:

    tcpServer = new QTcpServer(this);
    tcpSocket = NULL;
    

    2)监听是否有新的连接请求:

     tcpServer->listen(QHostAddress::Any,6666);
     connect(tcpServer,SIGNAL(newConnection()),this,SLOT(HaveNewConnection()));
    

    3)获取客户端的信息:

     void Widget::HaveNewConnection()
     {
         tcpSocket = tcpServer->nextPendingConnection();
         QString ip = tcpSocket->peerAddress().toString();
         quint16 port = tcpSocket->peerPort();
         QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
         connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(RcvData()));
         QMessageBox::information(this,"Info",str,QMessageBox::Ok);
     }
    

    4)选择文件

    void Widget::on_pushButton_choose_clicked()
    {
        QString filePath = QFileDialog::getOpenFileName(this,"open","../");
        if(!filePath.isEmpty())
        {
            fileName.clear();
            fileSize = 0;
            sendSize = 0;
            QFileInfo info(filePath);
            fileName = info.fileName();
            fileSize = info.size();
            file.setFileName(filePath);
            if(file.open(QIODevice::ReadOnly))
            {
                ui->textEdit->setText(tr("打开文件 ").append(filePath).append("\n"));
            }
            else
            {
                qDebug()<<"文件打开失败";
            }
        }
        else
        {
            qDebug()<<"文件路径出错";
        }
    }
    

    其中

    fileSize

    是文件总的字节数,

    sendSize

    是已经发送的字节数,打开文件时二者先清零,当

    sendSize == fileSize

    时表明文件已经发送完成。

    5)发送文件信息

    void Widget::on_pushButton_send_clicked()
    {
        QString head = tr("%1##%2").arg(fileName).arg(fileSize);
        quint64 len = tcpSocket->write(head.toUtf8().data());
        if(len<=0)
        {
            qDebug()<<"头部信息发送失败 ";
            file.close();
        }
    }
    

    包括文件名和文件大小,

    这一帧数据应独立于文件数据之外

    ,换句话说,应该确定客户端收到文件信息之后再发送文件数据。

    6)发送文件数据

    void Widget::SendFile()
    {
        qint64 len = 0;
        do{
            char buf[64*1024] = {0};
            len = 0;
            len = file.read(buf,sizeof(buf));
            len = tcpSocket->write(buf,len);
            sendSize += len;
        }while(len > 0);
        if(sendSize == fileSize)
        {
            ui->textEdit->setText("文件发送完毕 \n");
            file.close();
            tcpSocket->disconnect();
            tcpSocket->close();
        }
    }
    
  2. 客户端

    在这里插入图片描述

    在这里插入图片描述

    1)创建通信套接字,并监听是否接收到数据

    tcpSocket = new QTcpSocket(this);
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(RcvFile()));
    

    2)连接到指定服务器IP和端口

    void Client::on_pushButton_connect_clicked()
    {
        QString ip = ui->lineEdit_ip->text();
        quint16 port = ui->lineEdit_port->text().toInt();
        tcpSocket->connectToHost(ip,port);
    }
    

    3)接收数据并处理

    void Client::RcvFile()
    {
        QByteArray buf = tcpSocket->readAll();
        if(isStart)
        {
            isStart = false;
            QString dir = QFileDialog::getExistingDirectory(this,"选择文件夹 ","./"); 
            fileName = dir.append("/").append(QString(buf).section("##",0,0));
            fileSize = QString(buf).section("##",1,1).toInt();
            QString info = tr("文件名称:").append(QString(buf).section("##",0,0)).append("\n")
                      .append("保存路径:").append(dir).append("   \n")
                      .append("文件大小:").append(QString::number(fileSize/1024.0/1024.0,'f',2)).append("Mb");
            rcvSize = 0;
            qDebug()<<fileName<<fileSize;
            ui->textEdit_filemsg->setText(info);
            tcpSocket->write(QString(buf).section("##",0,0).toUtf8().data());
            file.setFileName(fileName);
            if(file.open(QIODevice::WriteOnly))
            {
    
            }
            else
            {
                qDebug()<<"write only error";
            }
    
            mTime.start();
        }
        else
        {
            qint64 tmp = 0;
            static qint64 cnt = 0;
            cnt++;
            qint64 len = file.write(buf);
            rcvSize += len;
            tmp += len;
            int progress = (int)((rcvSize*1.0)/(fileSize*1.0) * 100);
            if(cnt%10 == 0)
            {
                float speed;
                QTime current_time = QTime::currentTime();
                int msec = current_time.msec();
                if(last_time != 0)
                {
                    if(msec<last_time)
                        speed = tmp * 1000.0/((msec + 1000-last_time)*1024.0*1024.0);
                    else if(msec == last_time)
                        speed = 100.0;
                    else
                        speed = tmp * 1000.0/((msec-last_time)*1024.0*1024.0);
                }
                last_time = msec;
                tmp = 0;
                ui->progressBar->setValue(progress);
                if(cnt%100 == 0)
                    ui->label_speed->setText(QString::number(speed,'f',2).append("Mb/s"));
    
            }
            if(rcvSize == fileSize)
            {
                QString str = ("下载时间:");
                ui->textEdit_filemsg->append(str.append(QString::number(mTime.elapsed()/1000.0,'f',1)).append("s"));
                ui->progressBar->setValue(100);
                ui->label_speed->setText("0.00");
                file.close();
                QMessageBox::information(this,"完成","文件接收完成");
                tcpSocket->disconnectFromHost();
                tcpSocket->close();
                isStart = true;
            }
        }
    }
    

    首先判断数据是不是第一帧,如果是,则说明是文件信息,对应应该选择文件存储路径并向服务器发送应答(服务器接收到应答才开始发送文件);如果不是第一帧 则说明是文件数据,对应执行写文件操作,另外还有一些进度和速度显示操作。



    三、实例测试

    由于Qt的跨平台特性,项目中将客户端移植到虚拟机中的linux系统下,服务器留在windows中,测试传输效果如下:
    在这里插入图片描述

    项目连接:

    https://gitee.com/Mr-Yslf/BlogResources.git



版权声明:本文为qq_34578785原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。