一、环境介绍
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
四、软件逻辑部分源码
4.1 UDP客户端文件发送线程
代码语言:javascript复制#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("连接到服务器,隐式绑定. ERRORn");
//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_errorn");
return;
}
//设置运行状态
run_flag=true;
LogSend(QString("准备发送%1文件.目的地:%2:%3n").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. 耗时:%2n").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服务器文件接收线程
代码语言:javascript复制#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:%2n").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:%3n").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:%3n").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);
}