一、環境介紹
QT版本: 5.12.6
編譯器: MinGW 32
傳輸協定: UDP
功能介紹: 軟體由用戶端和伺服器組成,用戶端通過 UDP協定不斷循環地向服務端發送檔案,檔案傳輸速率可以達到10MB/s以上,檔案傳輸後支援自動删除,用戶端上可以支援每分鐘建立一個檔案并以時間戳命名,每個生成的檔案可以設定大小,預設大小為6GB; 服務端收到檔案之後,将檔案進行存儲到本地,可以指定時間自動删除檔案; 服務端可以動态計算傳輸速率,并寫入日志檔案; 伺服器可以支援同時接收多個用戶端的檔案上傳。
完整的項目源碼下載下傳位址(包含用戶端與伺服器)-打開即可編譯運作:
https://download.csdn.net/download/xiaolong1126626497/19006507 二、軟體運作效果
三、傳輸協定介紹
本軟體使用的網絡傳輸協定為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#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);
}