知识点
服务端与客户端的通信文件读写文件窗口QFileDialogQString字符串的切分section每次只发送4比特数据大小先发送文件信息,再传数据,防止TCP黏包 QTimer使用总结:出了一个非常粗心的bug:在h里面声明的变量,特别是int类型的,刚开始要进行初始化,比如sendSize += len; len是整型数据,最终sendSize是等于一个非常大的数据,我的理解是一个地址,地址+整型len,lsendSize在h文件声明了,但在cpp文件没有初始化造成的。
完整项目github地址:
/taw19960426/Qt_study/tree/main/QTcpFile
结果演示
流程图
tcpserverwidget.cpp
#include "tcpserverwidget.h"#include "ui_tcpserverwidget.h"#include <QFileInfo>#include <QFileDialog>#include <QFile>#define cout qDebug() << "[" << __FILE__ <<":" << __LINE__ << "]"TcpServerWidget::TcpServerWidget(QWidget *parent) :QWidget(parent),ui(new Ui::TcpServerWidget){ui->setupUi(this);setWindowTitle("服务器端口:8888");//两个按钮都不能按ui->buttonFile->setEnabled(false);ui->buttonSend->setEnabled(false);//监听套接字tcpServer=new QTcpServer(this);//监听tcpServer->listen(QHostAddress::Any,8888);connect(tcpServer,&QTcpServer::newConnection,[=](){//取出建立好连接的套接字tcpSocket=tcpServer->nextPendingConnection();//获取对方的IP和端口QString ip=tcpSocket->peerAddress().toString();qint16 port =tcpSocket->peerPort();QString ipDate=QString("[ip=%1 port=%2] 建立好连接了!!").arg(ip).arg(port);ui->textEdit->append(ipDate);ui->buttonFile->setEnabled(true);});myTimer=new QTimer(this);connect(myTimer,&QTimer::timeout,[=](){//关闭定时器myTimer->stop();//发送文件sendDate();});}TcpServerWidget::~TcpServerWidget(){delete ui;}//打开文件 获取文件的基本信息void TcpServerWidget::on_buttonFile_clicked(){QString filePath = QFileDialog::getOpenFileName(this,tr("Open File"),"../");//cout<<filePath;fileName.clear();fileSize=0;//获取文件的基本信息//QFileInfo获取文件信息QFileInfo FileDate(filePath);//qDebug()<<FileDate.exists();if(FileDate.exists()){qDebug() << "文件名字:" <<FileDate.fileName();qDebug() << "文件大小:" << FileDate.size()<<"bit";fileName=FileDate.fileName();//cout<<fileName;fileSize=FileDate.size();//cout<<fileSize;}//打开文件if(!filePath.isEmpty()){//只读方式打开文件//指定文件的名字file.setFileName(filePath);//打开文件bool openOk=file.open(QIODevice::ReadOnly);if(openOk){//提示打开文件的路径ui->textEdit->append("文件打开成功了。");}else{cout<<"文件打开失败了。";}}ui->buttonFile->setEnabled(false);ui->buttonSend->setEnabled(true);}//先发送文件信息 再发送数据void TcpServerWidget::on_buttonSend_clicked(){//文件信息先发送 “fileName_fileSize”QString head=QString("%1**%2").arg(fileName).arg(fileSize);//给对方发送数据, 使用套接字是tcpSocket//cout<<head;qint64 len;len=tcpSocket->write(head.toUtf8().data());//发送成功了if(len>0){//再发送数据//发送真正的文件信息//防止TCP黏包//需要通过定时器延时 20 msmyTimer->start(20);}else{//如果发送失败file.close();ui->buttonFile->setEnabled(true);ui->buttonSend->setEnabled(false);}}void TcpServerWidget::sendDate(){ui->textEdit->append("正在发送数据");qint64 len=0;sendSize=0;do{//每次发送数据的大小char buf[4*1024] = {0};len = 0;//往文件中读数据len = file.read(buf, sizeof(buf));//cout<<len;//发送数据,读多少,发多少len=tcpSocket->write(buf,len);//cout<<len;//发送的数据需要累积sendSize += len;cout<<"sendSize="<<sendSize;}while(len>0);//cout<<"是否发送文件完毕";//cout<<"sendSize="<<sendSize;//cout<<"fileSize"<<fileSize;//是否发送文件完毕if(sendSize==fileSize){ui->textEdit->append("数据发送完毕");file.close();//主动和客户端端口连接tcpSocket->disconnectFromHost();tcpSocket->close();tcpSocket = NULL;}}
tcpserverwidget.h
#ifndef TCPSERVERWIDGET_H#define TCPSERVERWIDGET_H#include <QWidget>#include <QTcpServer>#include <QTcpSocket>#include <QFile>#include <QTimer>namespace Ui {class TcpServerWidget;}class TcpServerWidget : public QWidget{Q_OBJECTpublic:explicit TcpServerWidget(QWidget *parent = 0);~TcpServerWidget();private slots:void on_buttonFile_clicked();void on_buttonSend_clicked();private:Ui::TcpServerWidget *ui;QTcpServer *tcpServer; //监听套接字QTcpSocket *tcpSocket; //通信套接字QString fileName;//文件名字qint64 fileSize;//文件大小qint64 sendSize;//已经发了多少数据//文件对象QFile file;//发送数据的函数void sendDate();QTimer *myTimer;};#endif // TCPSERVERWIDGET_H
tcpclientwidget.cpp
#include "tcpclientwidget.h"#include "ui_tcpclientwidget.h"#include <QMessageBox>#include <QHostAddress>#define cout qDebug() << "[" << __FILE__ <<":" << __LINE__ << "]"TcpClientWidget::TcpClientWidget(QWidget *parent) :QWidget(parent),ui(new Ui::TcpClientWidget){ui->setupUi(this);setWindowTitle("客户端");startInfo=true;tcpSocket=new QTcpSocket(this);ui->progressBar->setValue(0); //当前值connect(tcpSocket,&QTcpSocket::connected,[=](){ui->textEdit->setText("与服务端已经成功连接!");});//从通信套接字里面取内容connect(tcpSocket,&QTcpSocket::readyRead,[=](){//获取对方发送的内容QByteArray array = tcpSocket->readAll();if(startInfo){startInfo=false;receSize=0;//字符串切割“fileName_fileSize”QString temp=QString(array);// fileName=temp.section("_",0,0);// //cout<<fileName;// fileSize=temp.section("_",1,1).toInt();//文件名fileName = QString(array).section("**", 0, 0);//文件大小fileSize = QString(array).section("**", 1, 1).toInt();//cout<<"fileSize="<<fileSize;//指定文件的名字file.setFileName(fileName);//打开文件bool openOk=file.open(QIODevice::WriteOnly);if(!openOk){cout<<"文件打开失败";tcpSocket->disconnectFromHost(); //断开连接tcpSocket->close(); //关闭套接字return; //如果打开文件失败,中断函数}ui->progressBar->setMinimum(0);ui->progressBar->setMaximum(fileSize/1024);ui->progressBar->setValue(0); //当前值}else{//是文件内容qint64 len = file.write(array);if(len>0){receSize+=len;}//更新进度条ui->progressBar->setValue(receSize/1024);if(receSize==fileSize){ui->textEdit->append("文件接收完成");QMessageBox::information(this, "完成", "文件接收完成");tcpSocket->disconnectFromHost(); //断开连接tcpSocket->close(); //关闭套接字file.close(); //关闭文件}}});}TcpClientWidget::~TcpClientWidget(){delete ui;}//与服务器建立连接void TcpClientWidget::on_buttonConnect_clicked(){//获取服务器ip和端口QString ip=ui->lineEditIP->text();qint16 port=ui->lineEditPort->text().toInt();//主动和服务器建立连接tcpSocket->connectToHost(QHostAddress(ip),port);}
tcpclientwidget.h
#ifndef TCPCLIENTWIDGET_H#define TCPCLIENTWIDGET_H#include <QWidget>#include <QTcpSocket>#include <QFile>namespace Ui {class TcpClientWidget;}class TcpClientWidget : public QWidget{Q_OBJECTpublic:explicit TcpClientWidget(QWidget *parent = 0);~TcpClientWidget();private slots:void on_buttonConnect_clicked();private:Ui::TcpClientWidget *ui;QTcpSocket *tcpSocket; //通信套接字QString fileName;//文件名字qint64 fileSize;//文件大小qint64 receSize;//已经收了多少数据bool startInfo;//判断是否是接收到文件信息了//文件对象QFile file;};#endif // TCPCLIENTWIDGET_H