一、流程图
上一篇博客
介绍了Qt下Tcp通信流程,并实现了简单的文本传输,本文主要介绍Tcp文件传输过程。
流程图如下:
- 服务器
- 客户端
二、项目创建
-
服务器
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(); } }
-
客户端
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