天天看點

QT應用程式設計: 基于UDP協定設計的大檔案傳輸軟體

一、環境介紹

QT版本: 5.12.6

編譯器:   MinGW 32

傳輸協定: UDP

功能介紹:  軟體由用戶端和伺服器組成,用戶端通過 UDP協定不斷循環地向服務端發送檔案,檔案傳輸速率可以達到10MB/s以上,檔案傳輸後支援自動删除,用戶端上可以支援每分鐘建立一個檔案并以時間戳命名,每個生成的檔案可以設定大小,預設大小為6GB; 服務端收到檔案之後,将檔案進行存儲到本地,可以指定時間自動删除檔案; 服務端可以動态計算傳輸速率,并寫入日志檔案; 伺服器可以支援同時接收多個用戶端的檔案上傳。

完整的項目源碼下載下傳位址(包含用戶端與伺服器)-打開即可編譯運作:

https://download.csdn.net/download/xiaolong1126626497/19006507 二、軟體運作效果
QT應用程式設計: 基于UDP協定設計的大檔案傳輸軟體
QT應用程式設計: 基于UDP協定設計的大檔案傳輸軟體
QT應用程式設計: 基于UDP協定設計的大檔案傳輸軟體

三、傳輸協定介紹

本軟體使用的網絡傳輸協定為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
QT應用程式設計: 基于UDP協定設計的大檔案傳輸軟體
四、軟體邏輯部分源碼
QT應用程式設計: 基于UDP協定設計的大檔案傳輸軟體
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);
}      

繼續閱讀