27Tcp文件传输

前面介绍了TCP和UDP的通信,只是文体通信,只能传送文字。本次介绍文件传输,也就是文件读写和TCP通信的结合。

解析:根据之前的TCP通信,建立彼此的连接。服务器选择文件,首先将文件的基本信息发送给客户端。如:文件名,文件大小(用于进度条使用)。如上显示,“head#hello#1024”,即头部信息,hello就是文件名,1024是文件大小。如此客户端可以在某个磁盘创建好文件,便于后面的写入。服务端每次读取MK大小的数据,每读取一次,传输一次,客户端写入一次但要累计发送文件的大小,便于与原文件大小比较。

 

 

黏包问题:
如果头部信息和文件内容重合或追加。比如”head#hello#1024abdjsjdas”,那么“abdjsjdas”本质是内容信息而不是头部。

解决方式:设定一个定时器,在发出头部信息后,间隔N毫秒后再发送内容信息。

 

 

服务端

FileServer.h

#ifndef FILESERVER_H

#define FILESERVER_H

 

#include <QWidget>

#include <QTcpServer>

#include <QTcpSocket>

#include <QFile>

#include <QFileDialog>

#include <QFileInfo>

#include <QTimer>

 

 

namespace Ui {

class Widget;

}

 

class Widget : public QWidget

{

    Q_OBJECT

 

public:

    explicit Widget(QWidget *parent = 0);

    void SendData();

 

    ~Widget();

 

private slots:

    void on_pushButton_clicked();       //选择文件

    void on_pushButton_2_clicked();     //发送文件

 

private:

    Ui::Widget *ui;

 

    QTcpServer *m_listenSock;   //监听套接字

    QTcpSocket *m_serverSock;   //通信套接字

 

    QFile m_file;           //文件

    QString m_strFileName;  //文件名

    qint64 m_fileSize;      //文件大小

    qint64 m_sendSize;      //已发送的大小

 

    QTimer timer;           //定时器

};

 

#endif // FILESERVER_H

 

FileServer.cpp

#include "FileServer.h"

#include "ui_widget.h"

#include <QMessageBox>

 

Widget::Widget(QWidget *parent) :

    QWidget(parent),

    ui(new Ui::Widget)

{

    ui->setupUi(this);

    this->setWindowTitle("服务器端口:888");

 

    //将选择文件和发送文件的灰度

    m_listenSock=new QTcpServer(this);

    ui->pushButton->setEnabled(false);

    ui->pushButton_2->setEnabled(false);

 

    //监听

    bool bIsOk=m_listenSock->listen(QHostAddress::Any,888);

    if(bIsOk==true)

    {

        connect(m_listenSock,&QTcpServer::newConnection,

                [=]()

                {

                    //获取客户端的通信套接字

                    m_serverSock=m_listenSock->nextPendingConnection();

                    QString IP=m_serverSock->peerAddress().toString();

                    quint16 port=m_serverSock->peerPort();

                    QString strTemp=QString("[%1:%2]接入成功").arg(IP).arg(port);

 

                    ui->textEdit->setText(strTemp);

                    //激活选择文件按钮

                    ui->pushButton->setEnabled(true);

 

                }

        );

    }

    connect(&timer,&QTimer::timeout,

            [=]()

           {

                //停止定时器,并开始发送文件内容

                timer.stop();

                SendData();

            }

    );

}

 

Widget::~Widget()

{

    delete ui;

 

    delete m_listenSock;

    m_listenSock=NULL;

    m_serverSock=NULL;

}

 

void Widget::on_pushButton_clicked()

{

    QString strFilePath=QFileDialog::getOpenFileName(this,"Open","../");

    if(strFilePath.isEmpty()==false)

    {

         //文件基本信息

         QFileInfo info(strFilePath);

         m_strFileName=info.fileName();

         m_fileSize=info.size();

         m_sendSize=0;

         //qDebug()<<m_strFileName<<m_fileSize;

         //指定文件名称

         m_file.setFileName(strFilePath);

         //打开文件

         bool bIsOpen=m_file.open(QIODevice::ReadOnly);

         if(bIsOpen==true)

         {

            ui->textEdit->append(strFilePath);

            //激活发送文件按钮,灰度选择文件按钮

            ui->pushButton->setEnabled(false);

            ui->pushButton_2->setEnabled(true);

         };

 

    }

    else

    {

        //打开失败

    }

 

 

}

 

void Widget::on_pushButton_2_clicked()

{

    //发送头部信息

    QString strHead=QString("%1##%2")

            .arg(m_strFileName)

            .arg(m_fileSize);

    qint64 len=m_serverSock->write(strHead.toUtf8());

    //发送成功

    if(len>0)

    {

        //防止黏包,头部信息和内容信息混淆

        timer.start(1000);

    }

    else

    {

        //发送失败,重新选择文件

        m_file.close();

        ui->pushButton->setEnabled(true);

        ui->pushButton_2->setEnabled(false);

    }

}

 

void Widget::SendData()

{

    qint64 len=0;       //每次读取的大小

    do

    {

        //每次读取、发送4K

        char pBuf[4*1024]={0};

        len=0;

 

        //读多少,发送多少

        len=m_file.read(pBuf,sizeof(pBuf));

 

        m_serverSock->write(pBuf,len);

 

        //累加发送了多少

        m_sendSize+=len;

 

    }while(len>0);

    //len>0,表明有数据读取。0数据为空,<0读取失败。

 

    if(m_fileSize==m_sendSize)

    {

        //QMessageBox::information(this,"提示","发送完成!");

        ui->textEdit->append("发送完成!");

        //激活选择文件按钮,灰度发送文件按钮

        ui->pushButton->setEnabled(true);

        ui->pushButton_2->setEnabled(false);

 

        m_file.close();

        //m_serverSock->disconnectFromHost();

        //m_serverSock->close();

    }

 

}

 

 

同一程序添加两个窗口,且有设计器。新建一个文件,但不是C++类文件,而是QT且带有界面类的,最后在main中使用show()直接显示。如下图:

 

 

 

client.h

#ifndef CLIENT_H

#define CLIENT_H

 

#include <QWidget>

#include <QTcpSocket>

#include <QFile>

#include <QProgressBar>

 

namespace Ui {

class Client;

}

 

class Client : public QWidget

{

    Q_OBJECT

 

public:

    explicit Client(QWidget *parent = 0);

    ~Client();

 

private slots:

    void on_pushButton_clicked();       //请求连接

    void on_pushButton_2_clicked();     //关闭连接

 

private:

    Ui::Client *ui;

 

    QTcpSocket *m_clientSock;   //通信套接字

 

    QProgressBar m_proBar;  //进度条

 

    QFile m_file;           //文件

    QString m_strFileName;  //文件名

    qint64 m_fileSize;      //文件大小

    qint64 m_recvSize;      //已接收的大小

};

 

#endif // CLIENT_H

 

 

 

client.cpp

#include "client.h"

#include "ui_client.h"

#include <QMessageBox>

#include <QFileDialog>

 

 

Client::Client(QWidget *parent) :

    QWidget(parent),

    ui(new Ui::Client)

{

    ui->setupUi(this);

    this->setWindowTitle("客户端");

 

    m_clientSock=new QTcpSocket(this);

 

    //初始化进度条

    ui->progressBar->setValue(0);

 

    connect(m_clientSock,&QTcpSocket::connected,

            [=]()

            {

                ui->textEdit->setText("连接成功!");

            }

            );

 

    //判断文件头部信息还是内容信息

    static bool bIsHead=true;

 

    //通信

    connect(m_clientSock,&QTcpSocket::readyRead,

            [=]()

            {

                //获取服务器发来的内容

                QByteArray byteBuf=m_clientSock->readAll();

                //取出文件头部信息

                if(bIsHead)

                {

                    bIsHead=false;

                    //"filename##9999"

                    //解析头部信息

                    m_strFileName=QString(byteBuf).section("##",0,0);

                    m_fileSize=QString(byteBuf).section("##",1,1).toInt();

                    m_recvSize=0;

                    //根据文件的大小,设置进度条范围

                    ui->progressBar->setRange(0,m_fileSize/1024);

 

                    //m_strFileName="G:\\"+m_strFileName;   //自拟文件路径

 

                    m_file.setFileName(m_strFileName);      //默认源程序上一级目录

                    bool bIsOpen=m_file.open(QIODevice::WriteOnly);

                    if(bIsOpen==false)

                    {

                        //打开失败

                    }

                }

                //将读取的内容写入新文件

                else

                {

                    //写入文件

                    qint64 len=m_file.write(byteBuf);

                    m_recvSize+=len;

                    //更新进度条

                    ui->progressBar->setValue(m_recvSize/1024);

                    //接收完毕

                    if(m_recvSize==m_fileSize)

                    {

                        QMessageBox::information(this,"提示","接收完成!");

                        QString strTemp=QString("%1,接收完成").arg(m_strFileName);

                        ui->textEdit->append(strTemp);

                        ui->progressBar->setValue(0);

                        m_file.close();

                        //此处一定改,不然发下一文件失败,无法获取头部信息

                        bIsHead=true;

 

                    }

                }

 

            }

            );

 

}

 

Client::~Client()

{

    delete ui;

    delete m_clientSock;

    m_clientSock=NULL;

}

 

void Client::on_pushButton_clicked()

{

    //设置对方的IP和端口

    QString ip=ui->IP->text();

    quint16 port=ui->Port->text().toInt();

    //请求连接

    m_clientSock->connectToHost(ip.toUtf8(),port);

}

 

void Client::on_pushButton_2_clicked()

{

    //断开连接

    m_clientSock->disconnectFromHost();

    m_clientSock->close();

}

 

 

main.cpp

#include "FileServer.h"

#include <QApplication>

#include "client.h"

 

int main(int argc, char *argv[])

{

    QApplication a(argc, argv);

    Widget w;

    w.show();

 

    Client c;

    c.show();

 

    return a.exec();

}

 

程序结果图:

 

 

posted @ 2018-06-22 22:58  gd_沐辰  阅读(797)  评论(0编辑  收藏  举报