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

本文涉及的产品
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 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);
}


相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
10天前
|
XML JSON 算法
【JavaEE】——自定义协议方案、UDP协议
自定义协议,序列化,xml方案,json方案,protobuffer方案,UDP协议,校验和,比特翻转,CRC算法,md5算法
|
23天前
|
存储 网络协议 安全
用于 syslog 收集的协议:TCP、UDP、RELP
系统日志是从Linux/Unix设备及网络设备生成的日志,可通过syslog服务器集中管理。日志传输支持UDP、TCP和RELP协议。UDP无连接且不可靠,不推荐使用;TCP可靠,常用于rsyslog和syslog-ng;RELP提供可靠传输和反向确认。集中管理日志有助于故障排除和安全审计,EventLog Analyzer等工具可自动收集、解析和分析日志。
|
2月前
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
63 3
|
2月前
|
网络协议 SEO
TCP连接管理与UDP协议IP协议与ethernet协议
TCP、UDP、IP和Ethernet协议是网络通信的基石,各自负责不同的功能和层次。TCP通过三次握手和四次挥手实现可靠的连接管理,适用于需要数据完整性的场景;UDP提供不可靠的传输服务,适用于低延迟要求的实时通信;IP协议负责数据包的寻址和路由,是网络层的重要协议;Ethernet协议定义了局域网的数据帧传输方式,广泛应用于局域网设备之间的通信。理解这些协议的工作原理和应用场景,有助于设计和维护高效可靠的网络系统。
50 4
|
3月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
60 1
|
3月前
|
网络协议 算法 数据格式
【TCP/IP】UDP协议数据格式和报文格式
【TCP/IP】UDP协议数据格式和报文格式
210 3
|
3月前
|
JavaScript 安全 Java
谈谈UDP、HTTP、SSL、TLS协议在java中的实际应用
下面我将详细介绍UDP、HTTP、SSL、TLS协议及其工作原理,并提供Java代码示例(由于Deno是一个基于Node.js的运行时,Java代码无法直接在Deno中运行,但可以通过理解Java示例来类比Deno中的实现)。
92 1
|
6月前
|
数据安全/隐私保护 C++ 计算机视觉
Qt(C++)开发一款图片防盗用水印制作小工具
文本水印是一种常用的防盗用手段,可以将文本信息嵌入到图片、视频等文件中,用于识别和证明文件的版权归属。在数字化和网络化的时代,大量的原创作品容易被不法分子盗用或侵犯版权,因此加入文本水印成为了保护原创作品和维护知识产权的必要手段。 通常情况下,文本水印可以包含版权声明、制作者姓名、日期、网址等信息,以帮助识别文件的来源和版权归属。同时,为了增强防盗用效果,文本水印通常会采用字体、颜色、角度等多种组合方式,使得水印难以被删除或篡改,有效地降低了盗用意愿和风险。 开发人员可以使用图像处理技术和编程语言实现文本水印的功能,例如使用Qt的QPainter类进行文本绘制操作,将文本信息嵌入到图片中,
209 1
|
5月前
|
监控 C++ 容器
【qt】MDI多文档界面开发
【qt】MDI多文档界面开发
128 0
|
4月前
|
开发工具 C++
qt开发技巧与三个问题点
本文介绍了三个Qt开发中的常见问题及其解决方法,并提供了一些实用的开发技巧。
102 0