1.TFTP简介
TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为69。
FTP是一个传输文件的简单协议,它基于UDP协议而实现,但是我们也不能确定有些TFTP协议是基于其它传输协议完成的。此协议设计的时候是进行小文件传输的。因此它不具备通常的FTP的许多功能,它只能从文件服务器上获得或写入文件,不能列出目录,不进行认证,它传输8位数据。传输中有三种模式:netascii,这是8位的ASCII码形式,另一种是octet,这是8位源数据类型;最后一种mail已经不再支持,它将返回的数据直接返回给用户而不是保存为文件。
参考链接:https://blog.csdn.net/weixin_43996145/article/details/134265843?spm=1001.2014.3001.5501
2.TFTP Client
2.1 界面
2.2 源码
uint32_t ClientSocket::write(const char* data, size_t size)
{return socket_->write(data, size);
}TFtpClient::TFtpClient(QObject *parent): QObject(parent), socket(new QUdpSocket(this))
{connect(socket, &QUdpSocket::readyRead,this, &TFtpClient::readPendingDatagrams);connect(socket, &QUdpSocket::connected,this, &TFtpClient::connected);connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(connectError(QAbstractSocket::SocketError)));
}void TFtpClient::setHostPort(QString const& host, quint16 port)
{host_ = host;port_ = port;
}void TFtpClient::getFile(QString const& localFileName, QString const& remoteFileName)
{isPut_ = false;localFileName_ = localFileName;remoteFileName_ = remoteFileName;socket->connectToHost(host_, port_);
}void TFtpClient::putFile(QString const& localFileName, QString const& remoteFileName)
{isPut_ = true;localFileName_ = localFileName;remoteFileName_ = remoteFileName;socket->connectToHost(host_, port_);
}void TFtpClient::stopFileTransfer()
{socket->disconnectFromHost();
}void TFtpClient::connected()
{ClientSocket* udp = new ClientSocket(socket);tftpFile_ = TFtpClientFile::Ptr(new TFtpClientFile(udp));bool isOK = true;if(isPut_)isOK = tftpFile_->putFile(localFileName_.toStdString(),remoteFileName_.toStdString(), TFtp::BINARY);elseisOK = tftpFile_->getFile(localFileName_.toStdString(),remoteFileName_.toStdString(), TFtp::BINARY);if(!isOK)emit error("Local File not Found");elseemit started();
}void TFtpClient::connectError(QAbstractSocket::SocketError)
{emit error("Connect host is failed");
}void TFtpClient::readPendingDatagrams()
{while (socket->hasPendingDatagrams()) {QNetworkDatagram datagram = socket->receiveDatagram();QByteArray d = datagram.data();if(tftpFile_){tftpFile_->process((uint8_t *)d.data(), d.size());emit progress(tftpFile_->file_bytes(), tftpFile_->filesize());if(tftpFile_->is_finished()){if(tftpFile_->is_error())emit error(QString::fromStdString(tftpFile_->error_msg()));elseemit finished();}}}
}TFtpClientWidget::TFtpClientWidget(QWidget *parent): QWidget(parent), ui(new Ui::TFtpClientWidget), tftpClient(new TFtpClient(this))
{ui->setupUi(this);connect(ui->btnBrowse, SIGNAL(clicked()), this, SLOT(onSelectLocalFile()));connect(ui->btnGet, SIGNAL(clicked()), this, SLOT(onGetFile()));connect(ui->btnPut, SIGNAL(clicked()), this, SLOT(onPutFile()));connect(ui->btnBreak, SIGNAL(clicked()), this, SLOT(onStop()));connect(tftpClient, &TFtpClient::started, this, &TFtpClientWidget::onStarted);connect(tftpClient, &TFtpClient::progress, this, &TFtpClientWidget::onProgress);connect(tftpClient, &TFtpClient::finished, this, &TFtpClientWidget::onFinished);connect(tftpClient, &TFtpClient::error, this, &TFtpClientWidget::onError);
}TFtpClientWidget::~TFtpClientWidget()
{delete ui;
}void TFtpClientWidget::onGetFile()
{tftpClient->stopFileTransfer();tftpClient->setHostPort(ui->lineEditHost->text(), ui->spinBoxPort->value());tftpClient->getFile(ui->lineEditLocalFile->text(), ui->lineEditRemoteFile->text());ui->progressBar->setValue(0);
}void TFtpClientWidget::onPutFile()
{tftpClient->stopFileTransfer();tftpClient->setHostPort(ui->lineEditHost->text(), ui->spinBoxPort->value());tftpClient->putFile(ui->lineEditLocalFile->text(), ui->lineEditRemoteFile->text());ui->progressBar->setValue(0);
}void TFtpClientWidget::onStarted()
{enableButtons(false);
}void TFtpClientWidget::onProgress(quint64 bytes, quint64 total)
{if(total > 0)ui->progressBar->setValue(bytes * 100 / total);else{int value = ui->progressBar->value();ui->progressBar->setValue(QRandomGenerator(value).bounded(value, 99));}
}void TFtpClientWidget::onStop()
{enableButtons(true);tftpClient->stopFileTransfer();
}void TFtpClientWidget::onFinished()
{if(tftpClient->isPut())QMessageBox::information(this, "TFtpClient", "Put is done!");elseQMessageBox::information(this, "TFtpClient", "Get is done!");enableButtons(true);
}void TFtpClientWidget::onError(QString const& error)
{QMessageBox::critical(this, "TFtpClient", error);enableButtons(true);
}void TFtpClientWidget::onSelectLocalFile()
{static QString filePath;QFileDialog dialog(this, tr("Select File"), filePath, tr("All files (*.*)"));if(dialog.exec() == QDialog::Rejected)return;QStringList fileNames = dialog.selectedFiles();if(fileNames.isEmpty())return;QString fileName = fileNames.first();filePath = QFileInfo(fileName).filePath();ui->lineEditLocalFile->setText(fileName);
}void TFtpClientWidget::enableButtons(bool enable)
{ui->btnGet->setEnabled(enable);ui->btnPut->setEnabled(enable);ui->btnBreak->setDisabled(enable);
}
3.TFTP Server
3.1 界面
3.2 源码
uint32_t ServerSocket::write(const char* data, size_t size)
{return socket_->writeDatagram(data, size, host_, port_);
}TFtpServer::TFtpServer(QObject *parent): QObject(parent), socket(new QUdpSocket(this)), fileManager_(new TFtpFileManager())
{connect(socket, &QUdpSocket::readyRead,this, &TFtpServer::readPendingDatagrams);
}void TFtpServer::setFilePath(QString const& filePath)
{if(!filePath.endsWith("/"))filePath_ = filePath + "/";
}void TFtpServer::start()
{if(!socket->bind(TFTP_PORT))emit bindError();
}void TFtpServer::stop()
{socket->close();
}void TFtpServer::readPendingDatagrams()
{while (socket->hasPendingDatagrams()) {QNetworkDatagram datagram = socket->receiveDatagram();QByteArray d = datagram.data();QString transferId = QString("%1:%2").arg(datagram.senderAddress().toString()).arg(datagram.senderPort());TFtpServerFile::Ptr file = fileManager_->find(transferId.toStdString());if(file)file->process((uint8_t *)d.data(), d.size());else{ServerSocket* udp = new ServerSocket(socket, datagram.senderAddress(), datagram.senderPort());file = TFtpServerFile::Ptr(new TFtpServerFile(udp, filePath_.toStdString(), transferId.toStdString()));fileManager_->add(file);file->process((uint8_t *)d.data(), d.size());emit startFile(transferId, QString::fromStdString(file->filename()));}if(!file->is_finished()){if(file->type() == TFtpServerFile::Read)emit statusText(QString("Downloding file: %1, progress: %4% blockNumber(%2/%3)").arg(QString::fromStdString(file->filename())).arg(file->block_number()).arg(file->block_numbers()).arg(file->block_number() * 100 / file->block_numbers()));elseemit statusText(QString("Uploading file: %1, blockNumber(%2)").arg(QString::fromStdString(file->filename())).arg(file->block_number()));emit progress(transferId, file->file_bytes(), file->filesize());}else{if(file->is_error())emit statusText(QString("%1:%2").arg((int)file->error()).arg(QString::fromStdString(file->error_msg())));elseemit statusText(QString());emit progress(transferId, file->file_bytes(), file->filesize());emit stopFile(transferId);fileManager_->remove(file->transfer_id());}}
}TFtpServerWidget::TFtpServerWidget(QWidget *parent): QWidget(parent), ui(new Ui::TFtpServerWidget), tftpServer(new TFtpServer(this))
{ui->setupUi(this);loadSettings();connect(ui->btnBrowse, SIGNAL(clicked()), this, SLOT(selectTFtpDir()));connect(ui->currentDir, SIGNAL(currentIndexChanged(QString)), this, SLOT(setCurrentDir(QString)));connect(tftpServer, SIGNAL(startFile(QString,QString)), this, SLOT(onStartFile(QString,QString)));connect(tftpServer, SIGNAL(progress(QString,quint64,quint64)), this, SLOT(onProgress(QString,quint64,quint64)));connect(tftpServer, SIGNAL(stopFile(QString)), this, SLOT(onStopFile(QString)));connect(tftpServer, SIGNAL(bindError()), this, SLOT(onBindError()));tftpServer->start();
}TFtpServerWidget::~TFtpServerWidget()
{saveSettinggs();tftpServer->stop();delete ui;
}void TFtpServerWidget::selectTFtpDir()
{QString filePath = QFileDialog::getExistingDirectory(this,"Select Dir", ui->currentDir->currentText());if(filePath.isEmpty())return;int index = ui->currentDir->findText(filePath);if(index != -1)ui->currentDir->setCurrentIndex(index);else{if(ui->currentDir->count() >= MAX_PATH_SIZE)ui->currentDir->removeItem(0);ui->currentDir->addItem(filePath);ui->currentDir->setCurrentIndex(ui->currentDir->count() - 1);}
}void TFtpServerWidget::setCurrentDir(QString const& path)
{tftpServer->setFilePath(path);
}void TFtpServerWidget::onStartFile(QString const&transferId, QString const& fileName)
{ui->clientTables->addTopLevelItem(new QTreeWidgetItem(QStringList()<< transferId << fileName << QTime::currentTime().toString("hh:mm:ss")));
}void TFtpServerWidget::onProgress(QString const&transferId, quint64 bytes, quint64 total)
{QList<QTreeWidgetItem*> items = ui->clientTables->findItems(transferId, Qt::MatchCaseSensitive);for(int i = 0; i < items.size(); i++){if(total == 0)items[i]->setText(5, QString::number(bytes));else{ items[i]->setText(3, QString("%1%").arg(bytes * 100 / total));items[i]->setText(5, QString::number(total));}items[i]->setText(4, QString::number(bytes));}
}void TFtpServerWidget::onStopFile(QString const&transferId)
{QList<QTreeWidgetItem*> items = ui->clientTables->findItems(transferId, Qt::MatchCaseSensitive);for(int i = 0; i < items.size(); i++){int index = ui->clientTables->indexOfTopLevelItem(items[i]);ui->clientTables->takeTopLevelItem(index);}
}void TFtpServerWidget::saveSettinggs()
{QSettings settings(QCoreApplication::applicationName(), QCoreApplication::applicationVersion());QStringList dirs;for(int i = 0; i < ui->currentDir->count(); i++)dirs << ui->currentDir->itemText(i);settings.setValue("dirs", dirs);settings.setValue("currentDir", ui->currentDir->currentText());
}void TFtpServerWidget::loadSettings()
{QSettings settings(QCoreApplication::applicationName(), QCoreApplication::applicationVersion());QStringList dirs = settings.value("dirs", QStringList()).toStringList();QString currentDir = settings.value("currentDir", QString()).toString();ui->currentDir->addItems(dirs);int index = ui->currentDir->findText(currentDir);if(index != -1){tftpServer->setFilePath(currentDir);ui->currentDir->setCurrentIndex(index);}else{tftpServer->setFilePath(QApplication::applicationDirPath());ui->currentDir->addItem(QApplication::applicationDirPath());}
}void TFtpServerWidget::onBindError()
{QMessageBox::critical(this, "TFtpServer", "Port(69) is already occupied!");ui->btnBrowse->setDisabled(true);ui->currentDir->setDisabled(true);setWindowTitle("TFtpServer is not running");
}
4.工程链接
源码工程链接:https://download.csdn.net/download/weixin_43996145/90300559