Qt之TCP传输文件

概述:

功能:
TCP(Transmission Control Protocol传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。
连接建立:
TCP的三次握手
TCP的三次握手
TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。
TCP三次握手的过程如下:
客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。
可靠性:
TCP提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据包之前必须先建立一个TCP连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。在一个TCP连接中,仅有两方进行彼此通信。广播和多播不能用于TCP。

代码示例:

.pro

QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = TCPFile
TEMPLATE = app


SOURCES += main.cpp\
        serverwidget.cpp \
    clientwidget.cpp

HEADERS  += serverwidget.h \
    clientwidget.h

FORMS    += serverwidget.ui \
    clientwidget.ui

CONFIG += C++11

client.h

#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H

#include <QWidget>
#include <QtNetwork>
#include <QFile>

namespace Ui {
class ClientWidget;
}

class ClientWidget : public QWidget
{
    Q_OBJECT

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

private slots:
    /**
     * @brief ifConnected   连接成功槽函数
     */
    void slot_Connected();

    /**
     * @brief slotReadyRead 数据接收槽函数
     */
    void slot_ReadyRead();

private slots:
    void on_buttonConnect_clicked();//客户端链接按钮

private:
    Ui::ClientWidget *ui;

    QTcpSocket *tcpSocket;  //通信套接字
    QFile file;             //文件对象
    QString fileName;       //文件名字
    qint64 fileSize;        //文件大小
    qint64 recvSize;        //已接收文件的大小
    bool isFile;            //接收文件数据标志
};

#endif // CLIENTWIDGET_H

client.cpp

#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QMessageBox>
#include <QDebug>

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

    //创建TCP套接字
    tcpSocket = new QTcpSocket(this);
    //进度条初始化
    ui->progressBar->setValue(0);
    //设置标题
    setWindowTitle("客户端");

    //连接成功
    connect(tcpSocket,&QTcpSocket::connected,this,&ClientWidget::slot_Connected);
    //数据接收
    connect(tcpSocket,&QTcpSocket::readyRead,this,&ClientWidget::slot_ReadyRead);
}

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

void ClientWidget::slot_Connected()
{
    //初始化数据
    fileName = "";
    fileSize = 0;
    recvSize = 0;
    isFile = false;

    ui->buttonConnect->setEnabled(false);
    ui->textEdit->clear();
    ui->textEdit->append("和服务器连接成功");
}

void ClientWidget::slot_ReadyRead()
{
    QByteArray buf = tcpSocket->readAll();
    //buf.resize(tcpSocket->bytesAvailable());

    if(false == isFile)//先接收头部信息
    {
        isFile = true;

        //文件名字
        fileName = QString(buf).section("#", 1, 1);
        //文件大小
        fileSize = QString(buf).section("#", 2, 2).toInt();
        qDebug() << fileName << fileSize;

        recvSize = 0;

        file.setFileName(fileName);
        if(false == file.open(QIODevice::WriteOnly)){
            //初始化数据
            fileName = "";
            fileSize = 0;
            recvSize = 0;
            isFile = false;

            QMessageBox::warning(this, "警告", "创建文件失败");
            return;
        }

        ui->progressBar->setMinimum(0);
        ui->progressBar->setMaximum(fileSize/1024);

        ui->textEdit->append(QString("正在接收文件:\n%1").arg(fileName));

    }
    else//文件数据
    {
        //写入数据
        qint64 len = file.write(buf);
        recvSize += len;
        qDebug() << len;
        ui->progressBar->setValue(recvSize/1024);

    }

    if(recvSize == fileSize)//如果接收数据长度和发送数据长度相等做接收后处理
    {
        file.close();
        ui->buttonConnect->setEnabled(true);
        tcpSocket->disconnectFromHost();

        QMessageBox::information(this, "ok", "文件接收完毕");
        ui->textEdit->append("文件接收完毕");
    }
}

//连接按钮
void ClientWidget::on_buttonConnect_clicked()
{
    ui->progressBar->setValue(0);

    QString ip = ui->lineEditIP->text();
    qint16 port = ui->lineEditPort->text().toInt();
    if(ip.isEmpty() == true || port == 0)
    {
        QMessageBox::warning(this, "警告", "ip或端口不能为空");
        return;
    }

    tcpSocket->abort(); //取消已有的连接

    //连接服务器
    tcpSocket->connectToHost(QHostAddress(ip), port);
}

server.h

#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H

#include <QWidget>
#include <QtNetwork> //网络相关头文件
#include <QFile>
#include <QTimer>

namespace Ui {
class ServerWidget;
}

class ServerWidget : public QWidget
{
    Q_OBJECT

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

    /**
     * @brief sendFileData  发送文件数据函数
     */
    void sendFileData();

private slots:
    /**
     * @brief slot_Timerstart   定时器启动*毫秒调用此槽函数
     */
    void slot_TimerStart();

    /**
     * @brief slot_NewConnection    当有新数据到来时调用此槽函数
     */
    void slot_NewConnection();

private slots:
    /**
     * @brief on_buttonChoose_clicked   选择文件按钮
     */
    void on_buttonChoose_clicked();

    /**
     * @brief on_buttonSend_clicked     发送文件按钮
     */
    void on_buttonSend_clicked();



private:
    Ui::ServerWidget *ui;

    QTcpServer *tcpServer;  //监听套接字
    QTcpSocket *tcpSocket;  //通信套接字
    QFile file;             //文件对象
    QString fileName;       //文件名字
    qint64 fileSize;        //文件大小
    qint64 sendSize;        //已发送文件的大小

    QTimer timer;           //定时器
};

#endif // SERVERWIDGET_H

server.cpp

#include "serverwidget.h"
#include "ui_serverwidget.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QTimer>
#include <QThread>

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

    //创建套接字
    tcpServer = new QTcpServer(this);

    //监听,端口:8888
    bool isOk = tcpServer->listen(QHostAddress::Any, 8888);
    if(false == isOk)//监听失败
    {
        QMessageBox::warning(this, "监听", "监听失败");
        return;
    }

    //设置标题
    setWindowTitle("服务器:8888");

    //设置按钮(变灰)
    ui->buttonChoose->setEnabled(false);
    ui->buttonSend->setEnabled(false);

    //当有客户端链接时,触发信号:newConnection
    connect(tcpServer,&QTcpServer::newConnection,this,&ServerWidget::slot_NewConnection);
    //定时器处理函数
    connect(&timer,&QTimer::timeout,this,&ServerWidget::slot_TimerStart);
}

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

//选择文件按钮
void ServerWidget::on_buttonChoose_clicked()
{
    QString path = QFileDialog::getOpenFileName(this, "请选择所要发送的文件", "../");
    if(path.isEmpty() == false)//路径有效
    {
        //file为成员变量
        file.setFileName(path); //设置文件路径
        //只读方式打开文件
        bool isOk = file.open(QIODevice::ReadOnly);
        if(false == isOk)//打开文件失败
        {
            QMessageBox::warning(this, "警告", "打开文件失败");
            return;
        }
        else
        {
            ui->textEdit->append("发送的文件:");
            ui->textEdit->append(path);

            //初始化数据
            fileName = "";
            fileSize = 0;
            sendSize = 0;

            //获取发送文件的信息
            QFileInfo info(path);
            fileName = info.fileName();  //文件名
            fileSize = info.size();      //文件大小

            ui->buttonSend->setEnabled(true); //恢复发送文件按钮
            ui->buttonChoose->setEnabled(false); //选择文件按钮变灰
        }
    }
}

//发送文件按钮
void ServerWidget::on_buttonSend_clicked()
{
    // 发送文件按钮变灰
    ui->buttonSend->setEnabled(false);

    //先发送文件头,自定义的数据,不是文件数据
    //先发送头,自定义组包, 文件名#文件大小
    QString buf = QString("head#%1#%2").arg(fileName).arg(fileSize);

    //先发头
    qint64 len = tcpSocket->write( buf.toUtf8().data());
    tcpSocket->waitForBytesWritten(); //等待数据发送完毕

    ui->textEdit->append("已经在发送文件!!!");

    if(len > 0) //如果头部信息发送成功,开始发送文件数据
    {
        //10毫秒后再发送文件数据
        //启动定时器,定时器内容发送文件数据
        //防止TCP黏包问题
        this->timer.start(1);
    }
    else
    {
        file.close(); //关闭文件
    }
}

void ServerWidget::sendFileData()
{
    //循环读取数据发送
    qint64 len = 0;
    do{
        // 每次发送 2kb 大小的数据,如果剩余的数据不足 2kb,就发送剩余数据的大小
        char buf[2*1024] = {0};

        len = 0;
        len = file.read( buf, sizeof(buf) ); //读数据
        len = tcpSocket->write(buf, len);    //发数据

        sendSize +=len; //已发送的文件数据大小
    }while(len > 0);

    //文件数据发送完毕
    if(sendSize == fileSize)
    {
        //QMessageBox::information(this, "ok", "文件发送完毕");
        ui->textEdit->append("文件发送完毕");

        //关闭文件
        file.close();
        //关闭客户端
        tcpSocket->disconnectFromHost();
        tcpSocket->close();
    }
}

void ServerWidget::slot_TimerStart()
{
    this->timer.stop(); //关闭定时器
    sendFileData();     //发送文件数据
}

void ServerWidget::slot_NewConnection()
{
    //取出链接套接字
    tcpSocket = tcpServer->nextPendingConnection();

    //客户端IP和端口
    QString ip = tcpSocket->peerAddress().toString();
    qint16 port = tcpSocket->peerPort();
    QString str = QString("[%1:%2]和服务器连接成功").arg(ip).arg(port);
    ui->textEdit->setText(str); //设置内容

    //恢复选择按钮状态
    ui->buttonChoose->setEnabled(true);

    QMessageBox::information(this, "允许", "连接成功,可以选择文件发送");
}

测试结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

over:

欢迎大家关注作者在文末评论、点赞、转发以及批评指正!
如果大家有更好的方法或有问题可以在文末评论一起讨论!
共同学习!
共同进步!

文末一句话:

如今最好,别说来日方长,时光难留,只有一去不返。。

  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Qt提供了Qt Network模块来支持基于TCP协议的文件传输。在使用Qt进行TCP传输文件时,确实可能会遇到传输文件过大的问题。 传输文件的大小受到网络传输速度和传输协议的限制。如果文件过大,无论是在服务器端还是客户端端,都可能导致传输过程的延迟或失败。 为了解决TCP传输文件过大的问题,可以考虑以下方法: 1. 文件分割:将大文件分割成较小的块进行传输。客户端可以按顺序接收这些块,并在接收完所有块后重新组合成完整的文件。 2. 断点续传:如果传输过程中发生中断,可以记录已经传输文件的断点,再次连接后从断点处继续传输,而不是重新开始传输整个文件。 3. 数据压缩:在文件传输之前,可以将文件进行压缩,以减小文件的大小。在接收端,再解压缩文件恢复到原始大小。 4. 分段传输:将大文件分为多个小段,每次传输一个小段,传输完毕后再传下一个小段,使传输过程更加稳定和可靠。 5. 使用多线程:通过创建多个线程同时传输文件的不同部分,可以加速文件传输的速度。但是要注意线程间的同步问题,确保传输的正确性和完整性。 综上所述,Qt可以通过文件分割、断点续传、数据压缩、分段传输和使用多线程等方法来处理TCP传输文件过大的问题。根据实际需求选择合适的方法可以提高传输效率和可靠性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值