QT应用编程: 基于UDP协议设计的大文件传输软件

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 1个月
简介: QT应用编程: 基于UDP协议设计的大文件传输软件

一、环境介绍

QT版本: 5.12.6


编译器:   MinGW 32


传输协议: UDP


功能介绍:  软件由客户端和服务器组成,客户端通过 UDP协议不断循环地向服务端发送文件,文件传输速率可以达到10MB/s以上,文件传输后支持自动删除,客户端上可以支持每分钟创建一个文件并以时间戳命名,每个生成的文件可以设置大小,默认大小为6GB; 服务端收到文件之后,将文件进行存储到本地,可以指定时间自动删除文件; 服务端可以动态计算传输速率,并写入日志文件; 服务器可以支持同时接收多个客户端的文件上传。


完整的项目源码下载地址(包含客户端与服务器)-打开即可编译运行: https://download.csdn.net/download/xiaolong1126626497/19006507


二、软件运行效果

image.png

image.png

image.png

三、传输协议介绍

本软件使用的网络传输协议为UDP协议,UDP本身是一个无连接协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段,由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。


UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包而言UDP的额外开销很小。


UDP提供不可靠服务,具有TCP所没有的优势:


UDP无连接,时间上不存在建立连接需要的时延。空间上,TCP需要在端系统中维护连接状态,需要一定的开销。此连接装入包括接收和发送缓存,拥塞控制参数和序号与确认号的参数。UCP不维护连接状态,也不跟踪这些参数,开销小。空间和时间上都具有优势。


本软件的传输层框架采用的是UDT协议,UDT是基于UDP的数据传输协议,UDT是开源软件,主要目的是针对“TCP在高带宽长距离网络上的传输性能差”的问题,尽可能全面支持BDP网络上的海量数据传输。UDT是建立与UDP之上的面向双向的应用层协议,引入了新的拥塞控制算法和数据可靠性控制机制。它不仅可以支持可靠的数据流传输(STREAM 类型TCP)和部分可靠的数据报(DGRAM类似网络上发广播消息)传输,也可以应用在点对点技术,防火墙穿透,多媒体数据传输等领域。


UDT的特性


UDT的特性主要包括在以下几个方面:


1)基于UDP的应用层协议


2)面向连接的可靠协议


3)双工的协议


4)拥有新的拥塞控制算法,并具有可拓展的拥塞控制框架。


此外UDT协议在高BDP网络相对于TCP协议的优势,可以用下面几点来表示:


1)UDT是基于UDP协议,并且是定时器做的发送,不像tcp需要等待ack后才能开始下一轮发送


2)UDT的拥塞控制算法,能够实现在慢启动阶段快速增长抢占带宽,而在接近饱和时逐渐降低增长速度,使它趋于稳定。


3)UDT对包丢失的处理算法,和对噪声链路的容忍性,使得在网络波动比较大的环境中,它比传统的TCP协议更加的稳定


引入UDT的原因


互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差,且无法充分的利用带宽。其原因主要有一下几点:


1)现行的tcp拥塞窗口机制在高带宽长距离的环境下无法很好的工作,拥塞窗口太小,而且增加过于缓慢直接导致吞吐率不高,无法充分利用带宽。


此外TCP的AIMD拥塞控制算法过激地降低拥塞窗口的大小,但是不能快速回复到高位充分利用带宽。


2)目前的tcp拥塞控制算法在BDP网络下具有较差的RTT公平性,rtt会影响拥塞窗口的增长,越不容易达的链接的拥塞


窗口增加得越慢,其发送速度越慢,因此会导致越远的链接发送速率越慢。


UDT网站链接:  https://udt.sourceforge.io/


UDT 项目源码官方下载地址:  https://sourceforge.net/projects/udt/


UDT协议移植到QT工程: https://blog.csdn.net/xiaolong1126626497/article/details/116118320

image.png

四、软件逻辑部分源码

image.png

4.1  UDP客户端文件发送线程

#include "udp_file_send.h"
#ifndef WIN32
void* sendfile(void*);
#else
DWORD WINAPI sendfile(LPVOID);
DWORD WINAPI monitor(LPVOID s);
#endif
UDP_FILE_SEND_THREAD::UDP_FILE_SEND_THREAD()
{
    qDebug()<<"startup---->";
    UDT::startup();
}
UDP_FILE_SEND_THREAD::~UDP_FILE_SEND_THREAD()
{
}
void UDP_FILE_SEND_THREAD::run()
{
    FileInfoSendSuccess=0;
    Send_TotalBytes=0;
    File_TotalBytes=0;
    qint64 send_len=0;
    qint64 time1;
    qint64 time2;
    LogSend(QString("UDP文件发送线程开始运行.\n"));
    //判断文件是否存在
    QFileInfo file(send_file);
    if(file.exists()==false)
    {
        LogSend(QString("%1 文件不存在.停止发送.\n").arg(send_file));
      //  mSocket->close();
       // delete mSocket;
        return;
    }
    //打开文件
    QFile SrcFile(send_file);
    if(!SrcFile.open(QIODevice::ReadOnly))
    {
        LogSend(QString("%1 文件打开失败.停止发送.\n").arg(send_file));
        return;
    }
    //得到文件大小信息
    File_TotalBytes=SrcFile.size();
    //判断端口号
    if(server_port<=0)
    {
        LogSend("没有填写端口号.暂停发送.\n");
        return;
    }
    //判断IP地址
    if(server_ip_addr.isEmpty())
    {
        LogSend("没有填写IP地址.暂停发送.\n");
        return;
    }
    struct addrinfo hints, *local, *peer;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    if (0 != getaddrinfo(nullptr,"9000", &hints, &local))
    {
        LogSend("非法端口号或端口正忙.\n");
        return;
    }
   client = UDT::socket(local->ai_family, local->ai_socktype, local->ai_protocol);
#ifdef WIN32
   UDT::setsockopt(client, 0, UDT_MSS, new int(1052), sizeof(int));
#endif
   freeaddrinfo(local);
     if (0 != getaddrinfo(QString("%1").arg(server_ip_addr).toStdString().c_str(),QString("%1").arg(server_port).toStdString().c_str(), &hints, &peer))
     {
        qDebug() << "incorrect server/peer address. " <<server_ip_addr << ":" << server_port << endl;
        return;
     }
     // 连接到服务器,隐式绑定
     if (UDT::ERROR == UDT::connect(client, peer->ai_addr, peer->ai_addrlen))
     {
        LogSend("连接到服务器,隐式绑定. ERROR\n");
        //qDebug()<< "connect: " << UDT::getlasterror().getErrorMessage() << endl;
        return;
     }
     freeaddrinfo(peer);
     int ssize = 0;
     int ss;
     QString str;
     QFileInfo send_file_info(send_file);
     str=QString("image#%1#%2").arg(send_file_info.fileName()).arg(send_file_info.size());
     LogSend(str+"\n");
     qDebug()<<"str.toStdString().c_str():"<<str.toStdString().c_str()<<",len:"<<str.size();
     if (UDT::ERROR == (ss = UDT::send(client, str.toStdString().c_str(),strlen(str.toStdString().c_str()), 0)))
     {
          // qDebug() << "send_error:" << UDT::getlasterror().getErrorMessage() << endl;
         LogSend("send_error\n");
         return;
     }
     //设置运行状态
     run_flag=true;
     LogSend(QString("准备发送%1文件.目的地:%2:%3\n").arg(send_file).arg(server_ip_addr).arg(server_port));
     //获取系统当前时间
     time1= QDateTime::currentMSecsSinceEpoch();
    //开始发送
    while(run_flag)
    {
        QByteArray byte;
        byte=SrcFile.read(MAX_READ_LEN);
        if (UDT::ERROR == (send_len = UDT::send(client,byte.data(),byte.size(), 0)))
        {
            LogSend("send_error....\n");
            break;
        }
        //记录发送的长度
         Send_TotalBytes+=send_len;
         //获取本地时间
         current_ms_time=QDateTime::currentMSecsSinceEpoch();
         //时间经过了1s
         if(current_ms_time-old_ms_time>1000)
         {
             old_ms_time=current_ms_time;
             //更新状态
             ss_FileSendState(Send_TotalBytes,File_TotalBytes);
         }
        if(Send_TotalBytes>=File_TotalBytes || (byte.size()<MAX_READ_LEN))
        {
            break;
        }
        QThread::msleep(1);
    }
    UDT::close(client);
    //获取系统当前时间
    time2= QDateTime::currentMSecsSinceEpoch();
    //消耗的时间
    time1=time2-time1;
    //更新状态
    ss_FileSendState(Send_TotalBytes,File_TotalBytes);
    LogSend(QString("UDP文件发送完成. 总大小:%1 Byte. 耗时:%2\n").arg(File_TotalBytes).arg(QTime(0, 0, 0,0).addMSecs(int(time1)).toString(QString::fromLatin1("HH:mm:ss:zzz"))));
    Send_TotalBytes=0;
    File_TotalBytes=0;
    //关闭文件
    SrcFile.close();
    //发送完毕
    emit ss_SendComplete();
    LogSend("发送线程已正常退出...\n");
}
void UDP_FILE_SEND_THREAD::exit_thread()
{
    run_flag=false;
    //退出事件循环
    this->exit();
}

4.2 UDP服务器文件接收线程

#include "udp_file_recv.h"
QString recv_save_path=""; //保存接收文件存储的路径
class StringDataQueue log_queue;
UDP_FILE_RECV_THREAD::UDP_FILE_RECV_THREAD()
{
    qDebug()<<"startup---->";
    UDT::startup();
}
UDP_FILE_RECV_THREAD::~UDP_FILE_RECV_THREAD()
{
}
/*
工程: UDP_Server
日期: 2021-04-23
作者: DS小龙哥
环境: win10 QT5.12.6 MinGW32
功能: 开始接收UDP的数据
*/
void UDP_FILE_RECV_THREAD::run(QString ip,int port,QString save_path)
{
     recv_save_path=save_path;
     addrinfo hints;
     addrinfo* res;
     memset(&hints, 0, sizeof(struct addrinfo));
     hints.ai_flags = AI_PASSIVE;
     hints.ai_family = AF_INET;
     hints.ai_socktype = SOCK_STREAM;
     //hints.ai_socktype = SOCK_DGRAM;
     if (0 != getaddrinfo(nullptr,QString("%1").arg(port).toStdString().c_str(), &hints, &res))
     {
        qDebug() << "illegal port number or port is busy.\n" << endl;
        return;
     }
     serv = UDT::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
     if (UDT::ERROR == UDT::bind(serv, res->ai_addr, res->ai_addrlen))
        {
           qDebug() << "bind: " << UDT::getlasterror().getErrorMessage() << endl;
           return;
        }
        freeaddrinfo(res);
        if (UDT::ERROR == UDT::listen(serv, 10))
        {
           qDebug() << "listen: " << UDT::getlasterror().getErrorMessage() << endl;
           return;
        }
        sockaddr_storage clientaddr;
        int addrlen = sizeof(clientaddr);
        UDTSOCKET recver;
        LogSend("开始等待客户端连接....\n");
        while (true)
        {
           if (UDT::INVALID_SOCK == (recver = UDT::accept(serv, (sockaddr*)&clientaddr, &addrlen)))
           {
              LogSend("服务器已停止监听....\n");
              return;
           }
           char clienthost[NI_MAXHOST];
           char clientservice[NI_MAXSERV];
           getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV);
           qDebug() << "new connection: " << clienthost << ":" << clientservice << endl;
           #ifndef WIN32
              pthread_t rcvthread;
              pthread_create(&rcvthread, NULL, recvdata, new UDTSOCKET(recver));
              pthread_detach(rcvthread);
           #else
              LogSend("新的文件已到达,创建新的线程开始接收....\n");
              CreateThread(nullptr, 0, recvdata, new UDTSOCKET(recver), 0, nullptr);
           #endif
        }
        UDT::close(serv);
}
/*
工程: UDP_Server
日期: 2021-04-23
作者: DS小龙哥
环境: win10 QT5.12.6 MinGW32
功能: 退出线程
*/
void UDP_FILE_RECV_THREAD::close_file()
{
    UDT::close(serv);
}
/*
工程: UDP_Server
日期: 2021-05-19
作者: DS小龙哥
环境: win10 QT5.12.6 MinGW32
功能:
数据传输格式:  "image#<文件名称>#<大小>"
数据格式示例:  "image#D:/123.mp4#80000"
*/
#ifndef WIN32
void* recvdata(void* usocket)
#else
DWORD WINAPI recvdata(LPVOID usocket)
#endif
{
   UDTSOCKET recver = *(UDTSOCKET*)usocket;
   delete (UDTSOCKET*)usocket;
   unsigned char* data;
   int size = 1024*1024;
   int rs=0;
   data = new unsigned char[size];
   qint64 file_len=0;
   int rcv_size;
   int var_size = sizeof(int);
   UDT::getsockopt(recver, 0, UDT_RCVDATA, &rcv_size, &var_size);
   qDebug()<<"rcv_size:"<<rcv_size;
   if (UDT::ERROR == (rs = UDT::recv(recver,(char*)data,1024,0)))
   {
      qDebug() << "文件头接收失败:" << UDT::getlasterror().getErrorMessage() << endl;
      return 0;
   }
    qDebug()<<"rs:"<<rs;
   qint64 rx_byte=0;
   qint64 s_file_size=0;
   QString s_file_name;
   std::string std_str_data=(char*)data;
   QString Rx_str_data=QString("%1").fromStdString(std_str_data);
   qDebug()<<"接收的原始第一包数据:"<<Rx_str_data;
   int addr=Rx_str_data.indexOf(QByteArray("image#"));
   QFile *recv_file_p=nullptr;
   if(addr!=-1) //查找成功
   {
       QString str_size=Rx_str_data.section('#',2,2); //取出图片字节大小字符串
       s_file_name=QString("%1/%2").arg(recv_save_path).arg(Rx_str_data.section('#',1,1));
       s_file_size=str_size.toLongLong();   //得到图片字节大小
       //创建文件
       recv_file_p=new QFile(s_file_name);
       if(recv_file_p->open(QIODevice::ReadWrite)==true)
       {
           log_queue.write_queue(QString("%1 文件创建成功.\n").arg(s_file_name));
       }
       else
       {
           recv_file_p=nullptr;
           log_queue.write_queue(QString("%1 文件创建失败.\n").arg(s_file_name));
       }
        log_queue.write_queue(QString("收到新文件:%1:%2\n").arg(s_file_name).arg(s_file_size));
    }
   qint64 current_ms_time=0,old_ms_time=0;
   double speed_M_SEC=0.0;
   while (true)
   {
      int rcv_size;
      int var_size = sizeof(int);
      UDT::getsockopt(recver, 0, UDT_RCVDATA, &rcv_size, &var_size);
      if (UDT::ERROR == (rs = UDT::recv(recver,(char*)data,size,0)))
      {
         qDebug() << "recv:" << UDT::getlasterror().getErrorMessage() << endl;
         log_queue.write_queue("客户端断开连接...");
         break;
      }
      file_len+=rs;
      //qDebug()<<"接收数据:"<<data;
      //获取本地时间
      current_ms_time=QDateTime::currentMSecsSinceEpoch();
     // qDebug()<<"ID:"<<recver<<",接收长度:"<<file_len<<",Time:"<<current_ms_time;
      if(current_ms_time-old_ms_time>1000)
      {
          old_ms_time=current_ms_time;
          //计算速度
          speed_M_SEC=(file_len-rx_byte)/1024/1024.0;
          //如果小于0,表示文件1秒内就传输完毕了
          if(speed_M_SEC<1.0)speed_M_SEC=s_file_size/1024.0/1024.0;
          log_queue.write_queue(QString("%1 M/s").arg(speed_M_SEC));
          rx_byte=file_len;
      }
      //存储数据到文件
      if(recv_file_p)
      {
          //存储数据到文件
          recv_file_p->write((char*)data,rs);
          //接收完毕
          if(file_len>=s_file_size)
          {
              //qDebug()<<"ID:"<<recver<<",接收长度:"<<file_len<<",Time:"<<current_ms_time;
              //qDebug()<<"接收完毕...";
              log_queue.write_queue(QString("接收完毕:%1:%2:%3\n").arg(s_file_name).arg(s_file_size).arg(file_len));
              break;
          }
      }
      else
      {
          break;
      }
   }
   recv_file_p->close();
   delete recv_file_p;
   recv_file_p=nullptr;
   log_queue.write_queue(QString("线程退出:%1:%2:%3\n").arg(s_file_name).arg(s_file_size).arg(file_len));
   delete [] data;
   UDT::close(recver);
   #ifndef WIN32
      return NULL;
   #else
      return 0;
   #endif
}
void UDP_FILE_RECV_THREAD::WriteLog(QString text)
{
    log_queue.write_queue(text);
}


相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
19天前
|
开发框架 Linux API
Qt:构建高效且用户友好的跨平台应用
Qt:构建高效且用户友好的跨平台应用
|
19天前
|
开发框架 网络协议 数据库
Qt:构建跨平台应用的强大框架
Qt:构建跨平台应用的强大框架
|
19天前
|
Windows 安全 C++
Qt字符串类应用与常用基本数据类型
Qt字符串类应用与常用基本数据类型
|
19天前
|
开发框架 物联网 云计算
Qt应用领域分析与实践
Qt应用领域分析与实践
28 0
|
19天前
|
XML 网络协议 关系型数据库
Qt框架概述与应用实例
Qt框架概述与应用实例
21 0
|
19天前
|
算法 编译器 Linux
【Qt4 部署】ARM系统上使用Qt 4 进行开发的QWS 等环境变量部署
【Qt4 部署】ARM系统上使用Qt 4 进行开发的QWS 等环境变量部署
34 0
|
19天前
|
网络协议 C++
C++ Qt开发:QTcpSocket网络通信组件
`QTcpSocket`和`QTcpServer`是Qt中用于实现基于TCP(Transmission Control Protocol)通信的两个关键类。TCP是一种面向连接的协议,它提供可靠的、双向的、面向字节流的通信。这两个类允许Qt应用程序在网络上建立客户端和服务器之间的连接。Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用`QTcpSocket`组件实现基于TCP的网络通信功能。
50 8
C++ Qt开发:QTcpSocket网络通信组件
|
19天前
|
区块链
【qt】最快的开发界面效率——混合编程3
【qt】最快的开发界面效率——混合编程
25 1
|
19天前
【qt】最快的开发界面效率——混合编程2
【qt】最快的开发界面效率——混合编程
25 1
|
19天前
【qt】最快的开发界面效率——混合编程1
【qt】最快的开发界面效率——混合编程
29 0