当前位置:   article > 正文

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

qt udp文件发送

一、基础知识

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

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

二、服务端

数据结构/配置定义

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

  1. #pragma once
  2. #include <QObject>
  3. using namespace std;
  4. ///请求类型定义
  5. enum reqType
  6. {
  7. finished = -1, ///finished the sending.
  8. file,
  9. folder
  10. };
  11. ///请求定义
  12. struct fileReq
  13. {
  14. int type; //reqType
  15. QString name; //name for file or folder
  16. fileReq()
  17. {
  18. type = reqType::file;
  19. }
  20. };
  21. ///QString转string
  22. string _Q2S(QString &qstr);
  23. ///string转QString
  24. QString _S2Q(string &str);

config.h 定义基础配置信息

  1. #pragma once
  2. #include <QObject>
  3. class config : public QObject
  4. {
  5. Q_OBJECT
  6. public:
  7. config(QObject *parent = nullptr);
  8. ~config();
  9. ///获取本地存储路径
  10. QString getStoreDir() { return m_StoreDir; };
  11. ///获取本地服务端口
  12. QString getPort() { return m_Port; };
  13. ///设置本地存储路径
  14. void setStoreDir(QString dir) { m_StoreDir = dir; };
  15. ///设置本地服务端口
  16. void setPort(QString port) { m_Port = port; };
  17. private:
  18. ///本地存储路径
  19. QString m_StoreDir;
  20. ///本地服务端口
  21. QString m_Port;
  22. };
  23. ///获取全局配置对象
  24. config *getConfig();

监听和接收连接

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

  1. void mainThread::run()
  2. {
  3. emit log("listening...");
  4. sockaddr_storage clientaddr;
  5. int addrlen = sizeof(clientaddr);
  6. UDTSOCKET fhandle;
  7. while (m_start)
  8. {
  9. if (UDT::INVALID_SOCK == (fhandle = UDT::accept(m_serv, (sockaddr *)&clientaddr, &addrlen)))
  10. {
  11. emit log(UDT::getlasterror().getErrorMessage());
  12. break;
  13. }
  14. char clienthost[NI_MAXHOST];
  15. char clientservice[NI_MAXSERV];
  16. getnameinfo((sockaddr *)&clientaddr, addrlen, clienthost, sizeof(clienthost), clientservice, sizeof(clientservice), NI_NUMERICHOST | NI_NUMERICSERV);
  17. QString msg = QString("new connection: %1:%2").arg(clienthost).arg(clientservice);
  18. emit log(msg);
  19. startClientThread(fhandle);
  20. }
  21. emit log("Service end.");
  22. }

处理请求

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

  1. void fileThread::run()
  2. {
  3. getReqs(m_fileReq);
  4. foreach(fileReq req, m_fileReq)
  5. procReq(req);
  6. sendEnd();
  7. }

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

  1. void fileThread::getReqs(QList<fileReq> &reqlist)
  2. {
  3. reqlist.clear();
  4. // aquiring file name information from client
  5. char file[1024];
  6. int len;
  7. if (UDT::ERROR == UDT::recv(m_client, (char*)&len, sizeof(int), 0))
  8. {
  9. QString msg ="getReqs size: " + QString::fromLocal8Bit(UDT::getlasterror().getErrorMessage());
  10. emit log(msg);
  11. return;
  12. }
  13. if (UDT::ERROR == UDT::recv(m_client, file, len, 0))
  14. {
  15. QString msg = "getReqs: " + QString::fromLocal8Bit(UDT::getlasterror().getErrorMessage());
  16. emit log(msg);
  17. return;
  18. }
  19. file[len] = '\0';
  20. QString info = QString::fromUtf8(file);
  21. QStringList reqs = info.split(";");
  22. foreach(QString req, reqs)
  23. {
  24. QStringList file = req.split(",");
  25. if (file.size() > 1)
  26. {
  27. fileReq r;
  28. r.type = file.at(0).toInt();
  29. r.name = file.at(1);
  30. reqlist.append(r);
  31. }
  32. }
  33. }

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

  1. void fileThread::procReq(fileReq &req)
  2. {
  3. if (req.type == reqType::file)
  4. sendFile(req);
  5. else if (req.type == reqType::folder)
  6. sendDir(req);
  7. }

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

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

  1. bool fileThread::sendHeader(fileReq &req, int64_t fileSize)
  2. {
  3. QString header = QString("%1,%2,%3").arg(req.type).arg(req.name).arg(fileSize);
  4. QByteArray arr = header.toUtf8();
  5. int size = arr.size();
  6. // send header size information
  7. if (UDT::ERROR == UDT::send(m_client, (char*)&size, sizeof(int), 0))
  8. {
  9. errUDT( "send header size");
  10. return false;
  11. }
  12. // send header information
  13. if (UDT::ERROR == UDT::send(m_client, arr.data(), size, 0))
  14. {
  15. errUDT("send header");
  16. return false;
  17. }
  18. return true;
  19. }

执行文件发送:

  1. bool fileThread::sendFile(fileReq &req, const QString &root, bool keepDir)
  2. {
  3. QString fullname = joinFullPath(getConfig()->getStoreDir(), root, req.name);
  4. fullname = fullname.replace("/", "\\");
  5. string file = _Q2S(fullname);
  6. //open the file
  7. fstream ifs(file, ios::in | ios::binary);
  8. ifs.seekg(0, ios::end);
  9. int64_t size = ifs.tellg();
  10. ifs.seekg(0, ios::beg);
  11. // send the header
  12. if (!keepDir)
  13. {
  14. QFileInfo info(req.name);
  15. req.name = info.fileName();
  16. }
  17. if(!sendHeader(req, size))
  18. return false;
  19. UDT::TRACEINFO trace;
  20. UDT::perfmon(m_client, &trace);
  21. // send the file
  22. int64_t offset = 0;
  23. if (UDT::ERROR == UDT::sendfile(m_client, ifs, offset, size))
  24. {
  25. errUDT("sendfile");
  26. return false;
  27. }
  28. UDT::perfmon(m_client, &trace);
  29. cout << "speed = " << trace.mbpsSendRate << "Mbits/sec" << endl;
  30. QString msg = QString("speed = %1 Mb/s").arg(trace.mbpsSendRate);
  31. emit log(msg);
  32. ifs.close();
  33. return true;
  34. }

三、客户端

config.h配置定义

  1. #pragma once
  2. #include <QObject>
  3. class config : public QObject
  4. {
  5. Q_OBJECT
  6. public:
  7. config(QObject *parent = nullptr);
  8. ~config();
  9. ///获取主机名/IP
  10. QString getHost() { return m_Host; };
  11. ///获取端口
  12. QString getPort() { return m_Port; };
  13. ///获取本地保存目录
  14. QString getLocalDir() { return m_LocalDir; };
  15. ///获取服务端待下载文件名
  16. QString getServerFile() { return m_ServerFile; };
  17. ///获取服务端待下载目录
  18. QString getServerDir() { return m_ServerDir; };
  19. ///设置主机名/IP
  20. void setHost(QString host) { m_Host = host; };
  21. ///设置端口
  22. void setPort(QString port) { m_Port = port; };
  23. ///设置本地保存目录
  24. void setLocalDir(QString dir) { m_LocalDir = dir; };
  25. ///设置服务端待下载文件名
  26. void setServerFile(QString file) { m_ServerFile = file; };
  27. ///设置服务端待下载目录
  28. void setServerDir(QString dir) { m_ServerDir = dir; };
  29. private:
  30. ///主机名/IP
  31. QString m_Host;
  32. ///端口
  33. QString m_Port;
  34. ///本地保存目录
  35. QString m_LocalDir;
  36. ///服务端待下载文件名
  37. QString m_ServerFile;
  38. ///服务端待下载目录
  39. QString m_ServerDir;
  40. };
  41. config *getConfig();

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

发送请求

  1. bool fileThread::sendReq()
  2. {
  3. // send name information of the requested file
  4. QStringList reqs;
  5. if (!getConfig()->getServerFile().isEmpty())
  6. reqs.append(QString("%1,%2").arg(reqType::file).arg(getConfig()->getServerFile()));
  7. if (!getConfig()->getServerDir().isEmpty())
  8. reqs.append(QString("%1,%2").arg(reqType::folder).arg(getConfig()->getServerDir()));
  9. QByteArray reqFile = reqs.join(";").toUtf8();
  10. int len = reqFile.size();
  11. if (UDT::ERROR == UDT::send(m_client, (char*)&len, sizeof(int), 0))
  12. {
  13. cout << "send: " << UDT::getlasterror().getErrorMessage() << endl;
  14. return false;
  15. }
  16. if (UDT::ERROR == UDT::send(m_client, reqFile.data(), len, 0))
  17. {
  18. cout << "send: " << UDT::getlasterror().getErrorMessage() << endl;
  19. return false;
  20. }
  21. return true;
  22. }

接收文件

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

  1. bool fileThread::recvFile()
  2. {
  3. // get size information
  4. bool result = false;
  5. int len;
  6. if (UDT::ERROR == UDT::recv(m_client, (char*)&len, sizeof(int), 0))
  7. {
  8. errUDT("recv header size");
  9. return result;
  10. }
  11. char buffer[1024];
  12. if (UDT::ERROR == UDT::recv(m_client, buffer, len, 0))
  13. {
  14. errUDT("recv header");
  15. return result;
  16. }
  17. buffer[len] = '\0';
  18. QString info = QString::fromUtf8(buffer);
  19. QStringList ls = info.split(",");
  20. if (ls.size() < 3)
  21. {
  22. emit log("recvFile: illegal params.");
  23. return result;
  24. }
  25. int type = ls.at(0).toInt();
  26. QString filename = ls.at(1);
  27. if (type == reqType::finished)
  28. {
  29. emit log("server side finished.");
  30. return false;
  31. }
  32. else if (type == reqType::folder) ///dir
  33. {
  34. QString path = getConfig()->getLocalDir() + "\\" + filename;
  35. QDir dir;
  36. bool result = dir.mkpath(path);
  37. if (!result)
  38. emit log("failed to make dir: "+ path);
  39. return result;
  40. }
  41. int64_t size = ls.at(2).toInt();
  42. if (size < 0)
  43. {
  44. emit log("no such file: " + filename);
  45. return false;
  46. }
  47. UDT::TRACEINFO trace;
  48. UDT::perfmon(m_client, &trace);
  49. // receive the file
  50. QString path = getConfig()->getLocalDir() + "\\" + filename;
  51. string localFile = _Q2S(path.replace("/","\\"));
  52. fstream ofs(localFile, ios::out | ios::binary | ios::trunc);
  53. int64_t recvsize;
  54. int64_t offset = 0;
  55. if (UDT::ERROR == (recvsize = UDT::recvfile(m_client, ofs, offset, size)))
  56. {
  57. errUDT("recvfile");
  58. }
  59. else
  60. result = true;
  61. UDT::perfmon(m_client, &trace);
  62. emit log(QString("speed = %1 Mb/s").arg(trace.mbpsRecvRate));
  63. ofs.close();
  64. return result;
  65. }

四、程序示例

服务端存储目录

启动服务端

启动客户端

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

接收目录:

 

 可执行程序链接:

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/305247?site
推荐阅读
相关标签
  

闽ICP备14008679号