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

一、基础知识

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文库

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值