基于UDP的可靠传输,文件+目录(C++,Qt)

news2024/12/24 21:12:53

一、基础知识

UDP(UserDatagramProtocol)是一个简单的面向消息的传输层协议,尽管UDP提供标头和有效负载的完整性验证(通过校验和),但它不保证向上层协议提供消息传递,并且UDP层在发送后不会保留UDP 消息的状态。因此,UDP有时被称为不可靠的数据报协议。如果需要传输可靠性,则必须在用户应用程序中实现。

所以为了利用UDP的传输速度,并保证传输的可靠性,需要在UDP的基础上实现可靠的数据传输协议。为了开发的效率,作者选择了开源的UDT做为基础,并在其基础上构建文件和目录的传输。

二、服务端

数据结构/配置定义

pub.h 定义通用数据类型和转换方法

#pragma once

#include <QObject>

using namespace std;

///请求类型定义
enum reqType
{
	finished = -1, ///finished the sending.
	file, 
	folder
};

///请求定义
struct fileReq
{
	int type; //reqType
	QString name; //name for file or folder
	fileReq()
	{
		type = reqType::file;
	}
};

///QString转string
string _Q2S(QString &qstr);
///string转QString
QString _S2Q(string &str);

config.h 定义基础配置信息

#pragma once

#include <QObject>

class config : public QObject
{
	Q_OBJECT

public:
	config(QObject *parent = nullptr);
	~config();
	///获取本地存储路径
	QString getStoreDir() { return m_StoreDir; };
	///获取本地服务端口
	QString getPort() { return m_Port; };
	///设置本地存储路径
	void setStoreDir(QString dir) { m_StoreDir = dir; };
	///设置本地服务端口
	void setPort(QString port) { m_Port = port; };
private:
	///本地存储路径
	QString m_StoreDir;
	///本地服务端口
	QString m_Port;
};

///获取全局配置对象
config *getConfig();

监听和接收连接

建立主线程,接收客户端的连接,并对每个连接开启新的子线程。

void mainThread::run()
{
	emit log("listening...");
	sockaddr_storage clientaddr;
	int addrlen = sizeof(clientaddr);

	UDTSOCKET fhandle;
	while (m_start)
	{
		if (UDT::INVALID_SOCK == (fhandle = UDT::accept(m_serv, (sockaddr *)&clientaddr, &addrlen)))
		{
			emit log(UDT::getlasterror().getErrorMessage());
			break;
		}

		char clienthost[NI_MAXHOST];
		char clientservice[NI_MAXSERV];
		getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST | NI_NUMERICSERV);
		QString msg = QString("new connection: %1:%2").arg(clienthost).arg(clientservice);
		emit log(msg);
		startClientThread(fhandle);
	}
	emit log("Service end.");
}

处理请求

子线程执行主要为三部分:1. 接收和解析请求 2. 处理请求 3. 传输结束

void fileThread::run()
{
	getReqs(m_fileReq);
	foreach(fileReq req, m_fileReq)
		procReq(req);
	sendEnd();
}

接收请求方法中,将会从客户端接收请求头的大小,然后接收请求的具体内容,并填充至请求列表。请求支持单个文件或目录类型。

void fileThread::getReqs(QList<fileReq> &reqlist)
{
	reqlist.clear();
	// aquiring file name information from client
	char file[1024];
	int len;

	if (UDT::ERROR == UDT::recv(m_client, (char*)&len, sizeof(int), 0))
	{
		QString msg ="getReqs size: " + QString::fromLocal8Bit(UDT::getlasterror().getErrorMessage());
		emit log(msg);
		return;
	}

	if (UDT::ERROR == UDT::recv(m_client, file, len, 0))
	{
		QString msg = "getReqs: " + QString::fromLocal8Bit(UDT::getlasterror().getErrorMessage());
		emit log(msg);
		return;
	}
	file[len] = '\0';
	QString info = QString::fromUtf8(file);
	QStringList reqs = info.split(";");
	foreach(QString req, reqs)
	{
		QStringList file = req.split(",");
		if (file.size() > 1)
		{
			fileReq r;
			r.type = file.at(0).toInt();
			r.name = file.at(1);
			reqlist.append(r);
		}
	}
}

获取请求列表后,按照文件和目录类型以此处理。

void fileThread::procReq(fileReq &req)
{
	if (req.type == reqType::file)
		sendFile(req);
	else if (req.type == reqType::folder)
		sendDir(req);
}

针对目录类型,需要进行递归遍历,获取文件并发送。

每次发送文件之前,需要先发送头信息,以告知客户端发送类型、文件名称和文件大小。

bool fileThread::sendHeader(fileReq &req, int64_t fileSize)
{
	QString header = QString("%1,%2,%3").arg(req.type).arg(req.name).arg(fileSize);
	QByteArray arr = header.toUtf8();
	int size = arr.size();
	// send header size information
	if (UDT::ERROR == UDT::send(m_client, (char*)&size, sizeof(int), 0))
	{
		errUDT( "send header size");
		return false;
	}
	// send header information
	if (UDT::ERROR == UDT::send(m_client, arr.data(), size, 0))
	{
		errUDT("send header");
		return false;
	}
	return true;
}

执行文件发送:

bool fileThread::sendFile(fileReq &req, const QString &root, bool keepDir)
{
	QString fullname = joinFullPath(getConfig()->getStoreDir(), root, req.name);
	fullname = fullname.replace("/", "\\");
	string file = _Q2S(fullname);
	//open the file
	fstream ifs(file, ios::in | ios::binary);

	ifs.seekg(0, ios::end);
	int64_t size = ifs.tellg();
	ifs.seekg(0, ios::beg);
	// send the header
	if (!keepDir)
	{
		QFileInfo info(req.name);
		req.name = info.fileName();
	}	
	if(!sendHeader(req, size)) 
		return false;

	UDT::TRACEINFO trace;
	UDT::perfmon(m_client, &trace);

	// send the file
	int64_t offset = 0;
	if (UDT::ERROR == UDT::sendfile(m_client, ifs, offset, size))
	{
		errUDT("sendfile");
		return false;
	}

	UDT::perfmon(m_client, &trace);
	cout << "speed = " << trace.mbpsSendRate << "Mbits/sec" << endl;
	QString msg = QString("speed = %1 Mb/s").arg(trace.mbpsSendRate);
	emit log(msg);
	ifs.close();
	return true;
}

三、客户端

config.h配置定义

#pragma once

#include <QObject>

class config : public QObject
{
	Q_OBJECT

public:
	config(QObject *parent = nullptr);
	~config();
	///获取主机名/IP
	QString getHost() { return m_Host; };
	///获取端口
	QString getPort() { return m_Port; };
	///获取本地保存目录
	QString getLocalDir() { return m_LocalDir; };
	///获取服务端待下载文件名
	QString getServerFile() { return m_ServerFile; };
	///获取服务端待下载目录
	QString getServerDir() { return m_ServerDir; };
	///设置主机名/IP
	void setHost(QString host) { m_Host = host; };
	///设置端口
	void setPort(QString port) { m_Port = port; };
	///设置本地保存目录
	void setLocalDir(QString dir) { m_LocalDir = dir; };
	///设置服务端待下载文件名
	void setServerFile(QString file) { m_ServerFile = file; };
	///设置服务端待下载目录
	void setServerDir(QString dir) { m_ServerDir = dir; };
private:
	///主机名/IP
	QString m_Host;
	///端口
	QString m_Port;
	///本地保存目录
	QString m_LocalDir;
	///服务端待下载文件名
	QString m_ServerFile;
	///服务端待下载目录
	QString m_ServerDir;
};

config *getConfig();

客户端负责实现请求的发送和文件接收。

发送请求

bool fileThread::sendReq()
{
	// send name information of the requested file
	QStringList reqs;
	if (!getConfig()->getServerFile().isEmpty())
		reqs.append(QString("%1,%2").arg(reqType::file).arg(getConfig()->getServerFile()));
	if (!getConfig()->getServerDir().isEmpty())
		reqs.append(QString("%1,%2").arg(reqType::folder).arg(getConfig()->getServerDir()));
	QByteArray reqFile = reqs.join(";").toUtf8();
	int len = reqFile.size();

	if (UDT::ERROR == UDT::send(m_client, (char*)&len, sizeof(int), 0))
	{
		cout << "send: " << UDT::getlasterror().getErrorMessage() << endl;
		return false;
	}

	if (UDT::ERROR == UDT::send(m_client, reqFile.data(), len, 0))
	{
		cout << "send: " << UDT::getlasterror().getErrorMessage() << endl;
		return false;
	}
	return true;
}

接收文件

先接收头信息,然后接收文件数据。

bool fileThread::recvFile()
{
	// get size information
	bool result = false;
	int len;
	if (UDT::ERROR == UDT::recv(m_client, (char*)&len, sizeof(int), 0))
	{
		errUDT("recv header size");
		return result;
	}
	char buffer[1024];
	if (UDT::ERROR == UDT::recv(m_client, buffer, len, 0))
	{
		errUDT("recv header");
		return result;
	}
	buffer[len] = '\0';

	QString info = QString::fromUtf8(buffer);
	QStringList ls = info.split(",");
	if (ls.size() < 3)
	{
		emit log("recvFile: illegal params.");
		return result;
	}
	int type = ls.at(0).toInt();
	QString filename = ls.at(1);
	if (type == reqType::finished)
	{
		emit log("server side finished.");
		return false;
	}
	else if (type == reqType::folder) ///dir
	{
		QString path = getConfig()->getLocalDir() + "\\" + filename;
		QDir dir;
		bool result = dir.mkpath(path);
		if (!result)
			emit log("failed to make dir: "+ path);
		return result;
	}
	
	int64_t size = ls.at(2).toInt();
	if (size < 0)
	{
		emit log("no such file: " + filename);
		return false;
	}

	UDT::TRACEINFO trace;
	UDT::perfmon(m_client, &trace);
	// receive the file
	QString path = getConfig()->getLocalDir() + "\\" + filename;
	string localFile = _Q2S(path.replace("/","\\"));
	fstream ofs(localFile, ios::out | ios::binary | ios::trunc);
	int64_t recvsize;
	int64_t offset = 0;

	if (UDT::ERROR == (recvsize = UDT::recvfile(m_client, ofs, offset, size)))
	{
		errUDT("recvfile");
	}
	else
		result = true;
	UDT::perfmon(m_client, &trace);
	emit log(QString("speed = %1 Mb/s").arg(trace.mbpsRecvRate));
	ofs.close();
	return result;
}

四、程序示例

服务端存储目录

启动服务端

启动客户端

执行下载“测试”目录后的结果如下图:

接收目录:

 

 可执行程序链接:

(27条消息) 【免费】【可执行程序】基于UDT的文件+目录可靠传输(C++,Qt)资源-CSDN文库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/779291.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Tomcat】无法将位于-的资源添加到Web应用程序-的缓存中,因为在清除过期缓存条目后可用空间仍不足 - 请考虑增加缓存的最大空间

1、问题 org.apache.catalina.webresources.Cache.getResource Unable to add the resource at [xxx] to the cache for web application [/xxx] because there was insufficient free space available after evicting expired cache entries - consider increasing the maxim…

为Android构建现代应用——设计原则

为Android构建现代应用——设计原则 - 掘金 state”是声明性观点的核心 在通过Compose或SwiftUI等框架设计声明性视图时&#xff0c;我们必须明确的第一个范式是State(状态)。UI组件结合了它的图形表示(View)和它的State(状态)。UI组件中发生变化的任何属性或数据都可以…

Kotlin~Observer观察者模式

概念 定义一对多的依赖关系&#xff0c;让多个观察者同时监听一个主题对象。 角色介绍 Subject&#xff1a;主题&#xff0c;也称被观察者&#xff0c;它是具有状态的对象维护着一个观察者列表。提供添加、删除和通知观察者的方法。ConcreteSubject&#xff1a;具体主题&…

mfc140.dll丢失的解决方法(最新解决方法)

一&#xff1a;mfc140.dll的作用&#xff1a; mfc140.dll的主要作用是提供了&#xff08;简称MFC&#xff09;的函数和资源&#xff0c;它是用于构建Windows应用程序的一组C类库。MFC是微软开发环境中提供的一个工具集&#xff0c;它封装了Windows操作系统的底层API&#xff0…

2.多线程-初阶(中)

文章目录 4. 多线程带来的的风险-线程安全 (重点)4.1 观察线程不安全4.2 线程安全的概念4.3 线程不安全的原因4.3.1原子性4.3.2可见性4.3.3代码顺序性 4.4 解决之前的线程不安全问题 5. synchronized[ˈsɪŋkrənaɪzd] 关键字-监视器锁monitor lock5.1 synchronized 的特性5.…

【限流】4 种常见的限流实现方案

在微服务应用中&#xff0c;考虑到技术栈的组合&#xff0c;团队人员的开发水平&#xff0c;以及易维护性等因素&#xff0c;一个比较通用的做法是&#xff0c;利用 AOP 技术 自定义注解实现 对特定的方法或接口进行限流。 下面基于这个思路来分别介绍下几种常用的限流方案的…

OceanBase 压测时为什么冻结阈值在变化?

本文从源码角度分析了 OceanBase 压测中冻结阈值动态变化的原因&#xff0c;并给出运维建议。 作者&#xff1a;张乾 外星人2号&#xff0c;兼任五位喵星人的铲屎官。 本文来源&#xff1a;原创投稿 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转…

Redis两种持久化机制RDB和AOF详解(面试常问,工作常用)

redis是一个内存数据库&#xff0c;数据保存在内存中&#xff0c;但是我们都知道内存的数据变化是很快的&#xff0c;也容易发生丢失。幸好Redis还为我们提供了持久化的机制&#xff0c;分别是RDB(Redis DataBase)和AOF(Append Only File)。 在这里假设你已经了解了redis的基础…

微信小程序上,实现图片右上角数字显示

微信小程序上&#xff0c;实现图片右上角数字显示 直接上代码&#xff1a; 样式代码index.wxss如下&#xff1a; .circle_rednum {position: absolute;color: white;font-size: 13px;background-color: #EC2F43;width: 23px;height: 23px;line-height: 23px;left: 80%;top: …

RuntimeError: DataLoader worker (pid 2105929) is killed by signal: Killed.

PyTorch DataLoader num_workers Test - 加快速度 可以利用PyTorch DataLoader类的多进程功能来加快神经网络训练过程。 加快训练进程 为了加快训练过程&#xff0c;我们将利用DataLoader类的num_workers可选属性。 num_workers属性告诉DataLoader实例要使用多少个子进程进…

pytorch工具——pytorch中的autograd

目录 关于torch.tensor关于tensor的操作关于梯度gradients 关于torch.tensor 关于tensor的操作 x1torch.ones(3,3) xtorch.ones(2,2,requires_gradTrue) print(x1,\n,x)yx2 print(y) print(x.grad_fn) print(y.grad_fn)zy*y*3 outz.mean() print(z,out)注意 atorch.randn(2,…

SQL调优教程

SQL调优教程 基础方法论 任何计算机应用系统性能问题最终都可以归结为 1.cpu消耗 2.内存使用 3.对磁盘&#xff0c;网络或其他I/O设备的输入/输出(I/O)操作 遇到性能问题时&#xff0c;要判断的第一点就是“在这三种资源中&#xff0c;是否有哪一种资源达到了有问题的程度”&…

通过四点分析CRM系统的发展趋势

CRM系统企业管理客户的有力武器&#xff0c;为企业发展、降本增效奠定了基础&#xff0c;国内CRM经过十几年的发展产品已经十分成熟&#xff0c;很多企业会问CRM系统未来发展趋势是什么&#xff1f;今天小编就分享几个CRM未来的趋势。 1.AI人工智能 去年席卷全球的ChatGPT让大…

【数据结构】链表是否有环相关问题

文章目录 快指针走3、4、5步甚至更多可以吗为什么快慢指针一定在入口点相遇![在这里插入图片描述](https://img-blog.csdnimg.cn/ba346dbc9fee425dbb895ae2962e99ce.png) 快指针走3、4、5步甚至更多可以吗 部分情况下可以。 如果这样&#xff0c;相对&#xff08;追及&#xf…

在VSCode中实现Rust编程调试指南

在 VS Code 中调试 Rust&#xff1a;终极指南 在本教程中&#xff0c;您将学习如何使用 VS Code 调试 Rust。可用于使用 VS Code 调试 Rust 的操作。设置 VS Code 来调试 Rust Rust因其易用性、安全性和高性能而继续保持其作为最受欢迎的编程语言的地位。随着 Rust 的流行&…

paddle尝试PP-Vehicle-属性识别

PP-Vehicle-属性识别 windows11环境&#xff0c;conda虚拟环境中运行。 首先安装环境 conda create -n paddle python3.7conda activate paddlepip install paddlepaddle-gpupip install pyyamlpip install opencv-pythonpip install scipypip install matplotlibpip install…

关于Integer类的一个有趣的面试问题

相信很多人觉得答案是false&#xff0c;false&#xff0c;因为Integer是一个类&#xff0c;把int类型的数据传给Integer类型的数据会创建一个对象&#xff0c;而a,b,c,d作为引用指向的是不同的地址&#xff0c;所以判断相同得到的结果应该是false 但这个想法就正中下怀了&#…

链表 --- C语言实现

本篇文章来详细介绍一下数据结构中的链表。 目录 1.链表的概念及结构 2.链表的分类 3.单链表的实现 4.链表的面试题 5.双向链表的实现 6.顺序表和链表的区别 1.链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素…

【C++】多态(举例+详解,超级详细)

本篇文章会对C中的多态进行详解。希望本篇文章会对你有所帮助。 文章目录 一、多态的定义及实现 1、1 多态的概念 1、2 多态的构成条件 1、2、1 虚函数 1、2、2 虚函数的重写 1、2、3 析构函数构成重写特例原因 1、3 多态的实例练习 1、3、1 例1 1、3、2 例2 1、3、3 例3 1、4…

【C语言】指针进阶(2)

接上期文章指针进阶&#xff08;1&#xff09;指针进阶&#xff08;1&#xff09; 目录 1.函数指针 2.函数指针数组 3.指向函数指针数组的指针 4.回调函数 4.1qsort的用法 void*类型的指针介绍 使用qsort对数组进行排序 使用qsort对结构体进行排序&#xff1a; 4.2使用…