赞
踩
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;
- }

服务端存储目录
启动服务端
启动客户端
执行下载“测试”目录后的结果如下图:
接收目录:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。