49黑马QT笔记之利用TCP传输文件

49黑马QT笔记之利用TCP传输文件

前提:
黑马视频的代码第二次不能传输的原因是因为:客户端在第一次传输完成后,isStart=false。即第二次无法接收文件信息并打开文件,所以他会出现"write:device not open"。我们在文件接收完毕后,即文件相等那里重置isStart=true即可。

1 服务端流程:
1) 正常连接通信。
2) 选择文件按钮,弹出对话框选择文件,在该槽函数初始化文件信息info。包括文件名,大小,文件对象,已发送数据大小等等。
3) 发送文件按钮,先组包发送一次文件信息。(发送成功就利用定时器延时一定秒数,再发送完整的文件数据。实现延时的操作就是把原本要写的内容写在timeout信号的槽函数中。)
4) 发送完整的文件数据—发送数据先file.read读出来,再write发送过去,循环读和发送。最后再判断发送的数据和文件大小是否相同即可。

注:
1)服务端是发送了两次文件信息,第一次只是文件信息;第二次包括文件信息和数据。延时是为了防止黏包。
2)文件头与文件信息不对等,文件头是文件前54字节,包括文件各种信息。这里的例子文件信息只是自定义指文件名和文件大小。

2 客户端流程:
1 网络连接(tcpsocket,connect按钮)。
2 读文件信息和整个文件:
– 1)若为文件信息 —初始化接收文件的信息并打开接收文件。
– 2)若为整个文件 —写进文件对象file(不用while,因为有readyread),当recvSize==fileSize时,用信息框提示文件接收完成并重置isStart,为下一次连接传输作准备。

注:接收文件信息和整个文件用isStart标志位区分。默认接收文件路径在当前文件夹,不同环境可能不同。

3 代码:
1)服务端头文件:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<QTcpServer>
#include<QTcpSocket>
#include<QFile>
#include<QTimer>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    void sendData();   //自己封装一个发送文件数据的函数

private slots:
    void on_buttonFile_clicked();

    void on_buttonsend_clicked();

private:
    Ui::Widget *ui;
    QTcpServer *tcpServer;
    QTcpSocket *tcpSocket;

    QFile file;         //文件对象
    QString fileName;   //文件名字
    qint64 fileSize;    //文件大小
    qint64 sendSize;   //已经发生文件的大小

    QTimer timer;     //定时器
};

#endif // WIDGET_H

2)服务端实现文件:

#include "widget.h"
#include "ui_widget.h"
#include<QFileDialog>
#include<QIODevice>
#include<qDebug>


//1 TCP连接
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //1监听套接字
    tcpServer=new QTcpServer(this);

    //2监听
    tcpServer->listen(QHostAddress::Any,8888);
    setWindowTitle("服务器端口为:8888");

    //没连接之前两个按钮都不能按
    ui->buttonFile->setEnabled(false);
    ui->buttonsend->setEnabled(false);

    //3如果客户端成功和服务器连接
    //tcpServer会自动触发newConnection()

    connect(tcpServer,&QTcpServer::newConnection,
    [=]()
    {
        //取出建立好连接的套接字
        tcpSocket=tcpServer->nextPendingConnection();

        //获取对方的ip和端口号
        QString ip=tcpSocket->peerAddress().toString();
        quint16 port=tcpSocket->peerPort();

        QString str=QString("[%1:%2] 成功连接").arg(ip).arg(port);
        ui->textEdit->setText(str);                       //1 以上三步为了在文本编辑区显示客户端  ip和端口

        //成功连接后,才能选择文件
        ui->buttonFile->setEnabled(true);
    }

            );
    connect(&timer,&QTimer::timeout,
            [=]()
    {
            //关闭定时器
            timer.stop();

            //发送文件
            sendData();
    }



            );
}

Widget::~Widget()
{
    delete ui;
}


//2 选择文件按钮---初始化
void Widget::on_buttonFile_clicked()
{
    QString filePath=QFileDialog::getOpenFileName(this,"open","../");
    if(false==filePath.isEmpty())   //如果选择文件路径有效
    {
        fileName.clear();
        fileSize=0;

        //获取文件信息
        QFileInfo info(filePath);
        fileName=info.fileName();
        fileSize=info.size();

        sendSize=0;

        //只读方式打开
        //指定文件的名字
        file.setFileName(filePath);

        //打开文件
        bool isOk=file.open(QIODevice::ReadOnly);
        if(false==isOk)
        {
            qDebug() <<"只读方式打开文件失败 75";
            return;
        }

        //提示打开文件的路径
        ui->textEdit->append(filePath);

        ui->buttonFile->setEnabled(false);
        ui->buttonsend->setEnabled(true);

    }
    else
    {
        qDebug()<<"选择文件路径出错 82";
        return;
    }
}


//3 发送文件按钮--文件头
void Widget::on_buttonsend_clicked()
{

    //按下发送给它不能再按 因为发送一次就断开一次连接
    ui->buttonsend->setDisabled(true);

    //先发送文件头信息 文件名##文件大小
    QString head=QString("%1##%2").arg(fileName).arg(fileSize);

    //发送头部信息
    qint64 len=tcpSocket->write(head.toUtf8().data());

    if(len>0)  //头部信息成功发生
    {
        //发生真正的文件信息
        //防止TCP粘包文件
        //需要通过定时器延时20ms

        //实现延时的操作就是把原本要写的内容写在timeout信号的槽函数中。
        timer.start(20);

    }
    else
    {
        qDebug()<<"头部信息大小:"<<len;
        qDebug()<<"头部信息发生失败 137";
        file.close();
//      tcpSocket->disconnectFromHost();
//      tcpSocket->close();                          //2 不用关连接,只需要关闭文件让他在选择文件按钮 发送文件(会跳回send按钮)就好
        ui->buttonFile->setEnabled(true);
        ui->buttonsend->setEnabled(false);
    }


}

//4 开始发送文件--也包括文件头54个字节和真正数据
//  前面发送文件信息是为了给客户端要接收多大的文件
void Widget::sendData()
{
    qint64 len=0;
    do
    {
        //每次发送数据的大小
        char buf[4096]={0};
        len=0;

        //往文件中读数据,返回实际读到的字节数
        len=file.read(buf,sizeof(buf));              //3 读只有这一种,写有单参数和双参数两种
        //发送数据,读多少,发多少
        len=tcpSocket->write(buf,len);               //4 与发送头部信息的单参数有区别,其实上面也可用两个参数的

        //发送的数据需要累积
        sendSize+=len;


    }while(len>0);                                   //5 如果写过去的数据为0,则表示文件写完,do while()结束


    //是否发送文件完毕
    if(sendSize==fileSize)              //6 其实这个判断我感觉多余了,因为已经结束了,提示发送完毕然后直接关闭文件和连接就好了,保险起见吧
    {
        ui->textEdit->append("!!!文件发送完毕!!!");
        file.close();

        //把客户端断开
        tcpSocket->disconnectFromHost();             //7 这次要全部断开了
        tcpSocket->close();
    }




}

3)客户端头文件:

#ifndef CILENT_H
#define CILENT_H

#include <QWidget>
#include<QTcpSocket>
#include<QFile>

namespace Ui {
class cilent;
}

class cilent : public QWidget
{
    Q_OBJECT

public:
    explicit cilent(QWidget *parent = 0);
    ~cilent();

private slots:
    void on_pushButton_clicked();    //connect按钮,忘记改名了

private:
    Ui::cilent *ui;

    QTcpSocket *tcpSocket;

    QFile file;         //文件对象
    QString fileName;   //文件名字
    qint64 fileSize;    //文件大小
    qint64 recvSize;   //已经接收文件的大小

    bool isStart;     //用于区分接收文件头和文件数据
};

#endif // CILENT_H

4)客户端实现文件:

#include "cilent.h"
#include "ui_cilent.h"
#include<QIODevice>
#include<QMessageBox>
#include<QHostAddress>

cilent::cilent(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::cilent)
{
    ui->setupUi(this);

    tcpSocket=new QTcpSocket(this);

    isStart=true;

    //等待对方发送数据---构造时不会执行 只有readyRead触发才会执行--所以connect你可以认为不是顺序执行
    connect(tcpSocket,&QTcpSocket::readyRead,
            [=]()
    {
        //1)取出接收的内容
        QByteArray buf=tcpSocket->readAll();

        //接收文件头
        if(true==isStart)
        {
            //下一次接收整个文件
            isStart=false;


            //初始化
            fileName=QString(buf).section("##",0,0);
            file.setFileName(fileName);
            fileSize=QString(buf).section("##",1,1).toInt();
            recvSize=0;


            //打开文件
            bool isOk=file.open(QIODevice::WriteOnly);
            if(false==isOk)
            {
                qDebug()<<"WriteOnly error 37";
            }
        }
        //2)接收文件信息
        else
        {

               //file.read(buf);                                        //写进file应用write 读进buf应用read
               qint64 len=file.write(buf);                              //写buf里的数据到设备file 
               recvSize+=len;

               if(recvSize==fileSize)                                  //相等则用信息框提示接收完成 然后关闭文件和连接
               {
                   file.close();
                   QMessageBox::information(this,"完成","文件接收完成");

                   tcpSocket->disconnectFromHost();
                   tcpSocket->close();

                   //使第二次仍能打开 解决"write:device not open"
                   isStart=true;
               }
        }
    });


}

cilent::~cilent()
{
    delete ui;
}


//connect按钮只用于连接
void cilent::on_pushButton_clicked()
{
    //获取服务器ip和端口号
    QString ip=ui->lineEdit->text();
    quint16 port=ui->lineEdit_2->text().toInt();


    tcpSocket->connectToHost(QHostAddress(ip),port);
}

4 服务端与客户端的ui界面:
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值