https://blog.csdn.net/soldier_d/article/details/115311866#ifndef MAINWINDOW_H 这个链接是别人写的客户端 因为没人人写服务端 所以我就自己写了一个,跟这个联合起来用也可以,也可以把我这个服务端进行修改改成客户端。做这个TFTP服务端的原因是我想全部编辑在exe里面而不是要挂靠软件在单独去开,非常方便的解决很多网络服务上传下载文件的问题。
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket>
#include <QFile>
#include <QMap>
#include <QTimer>
#include <QCoreApplication>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
// 定义TFTP操作码
enum Opcode {
RRQ = 1, // Read request
WRQ = 2, // Write request
DATA = 3, // Data
ACK = 4, // Acknowledgment
ERROR = 5 // Error
};
// TFTP错误码
enum ErrorCode {
FileNotFound = 1,
AccessViolation = 2,
DiskFull = 3,
IllegalOperation = 4,
UnknownTransferID = 5,
FileAlreadyExists = 6,
NoSuchUser = 7
};
// 服务器配置
const quint16 TftpPort = 69; // TFTP标准端口
const int BlockSize = 512; // 数据块大小
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void start();
private slots:
void processPendingDatagrams();
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QUdpSocket socket;
QMap<quint16, QString> sessions; // 会话映射,用于跟踪每个传输的状态
void handleRRQ(const QHostAddress& clientAddr, quint16 clientPort, const QString& fileName);
void handleWRQ(const QHostAddress& clientAddr, quint16 clientPort, const QString& fileName);
void handleData(const QHostAddress& clientAddr, quint16 clientPort, quint16 blockNumber, const QByteArray& data);
void handleAck(const QHostAddress& clientAddr, quint16 clientPort, quint16 blockNumber);
void handleError(const QHostAddress& clientAddr, quint16 clientPort, ErrorCode errorCode, const QString& errorMessage);
void sendAck(const QHostAddress& clientAddr, quint16 clientPort, quint16 blockNumber);
void sendData(const QHostAddress& clientAddr, quint16 clientPort, quint16 blockNumber, const QByteArray& data);
void sendError(const QHostAddress& clientAddr, quint16 clientPort, ErrorCode errorCode, const QString& errorMessage);
QByteArray createAckPacket(quint16 blockNumber);
QByteArray createDataPacket(quint16 blockNumber, const QByteArray& data);
QByteArray createErrorPacket(ErrorCode errorCode, const QString& errorMessage);
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtEndian>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(&socket, &QUdpSocket::readyRead, this, &MainWindow::processPendingDatagrams);
// 也可以在这里启动服务器,或者根据需要放在其他地方
ui->lineEdit->setPlaceholderText("请输入当前计算机的ip地址");
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::start()
{
QHostAddress address(ui->lineEdit->text());
if (!socket.bind(address,TftpPort)) {
qDebug() << "Could not start TFTP server on port" << TftpPort;
return;
}
qDebug() << "TFTP server started on port" << TftpPort;
}
void MainWindow::processPendingDatagrams()
{
while (socket.hasPendingDatagrams()) {
QByteArray datagram;
QHostAddress sender;
quint16 senderPort;
datagram.resize(socket.pendingDatagramSize());
socket.readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
if (datagram.size() < 4) { // 最小长度检查
qDebug() << "Received datagram is too short.";
continue;
}
quint16 opcode = qFromBigEndian<quint16>((uchar*)datagram.constData());
qDebug() << "opcode" << opcode;
switch (opcode) {
case RRQ: {
// 提取文件名和模式
QStringList parts = QString::fromUtf8(datagram.mid(2)).split(QChar(0), QString::SkipEmptyParts);
if (parts.size() >= 1) {
QString fileName = parts[0];
handleRRQ(sender, senderPort, fileName);
}
break;
}
case WRQ: {
QStringList parts = QString::fromUtf8(datagram.mid(2)).split(QChar(0), QString::SkipEmptyParts);
if (parts.size() >= 1) {
QString fileName = parts[0];
handleWRQ(sender, senderPort, fileName);
}
break;
}
case DATA: {
if (datagram.size() >= 4) {
quint16 blockNumber = qFromBigEndian<quint16>((uchar*)datagram.constData() + 2);
QByteArray data = datagram.mid(4);
handleData(sender, senderPort, blockNumber, data);
}
break;
}
case ACK: {
if (datagram.size() >= 4) {
quint16 blockNumber = qFromBigEndian<quint16>((uchar*)datagram.constData() + 2);
handleAck(sender, senderPort, blockNumber);
}
break;
}
case ERROR: {
if (datagram.size() >= 4) {
quint16 errorCode = qFromBigEndian<quint16>((uchar*)datagram.constData() + 2);
// QString errorMessage = QString::fromUtf8(datagram.mid(4).split(QChar(0))[0]);
QString errorMessage = QString::fromUtf8(datagram.mid(4)).split(QChar(0))[0];
handleError(sender, senderPort, static_cast<ErrorCode>(errorCode), errorMessage);
}
break;
}
default:
qDebug() << "Unknown or unsupported opcode" << opcode;
break;
}
}
}
void MainWindow::handleRRQ(const QHostAddress &clientAddr, quint16 clientPort, const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Failed to open file" << fileName;
sendError(clientAddr, clientPort, FileNotFound, "File not found");
return;
}
quint16 blockNumber = 1;
QByteArray data = file.read(BlockSize);
while (!data.isEmpty()) {
sendData(clientAddr, clientPort, blockNumber++, data);
if (data.size() < BlockSize) {
break; // Last block
}
data = file.read(BlockSize);
}
}
void MainWindow::handleWRQ(const QHostAddress &clientAddr, quint16 clientPort, const QString &fileName)
{
// QFile *file = new QFile(fileName);
// if (!file->open(QIODevice::WriteOnly)) {
// qDebug() << "Failed to create file" << fileName;
// sendError(clientAddr, clientPort, AccessViolation, "Cannot create file");
// delete file;
// return;
// }
// // 保存文件对象以便后续写操作
// sessions.insert(clientPort, fileName);
// // 发送ACK 0来启动传输
// sendAck(clientAddr, clientPort, 0);
// QString filePath = "/usr/data/DataPrograms/" + fileName;
QFile *file = new QFile(fileName);
if (!file->open(QIODevice::WriteOnly)) {
qDebug() << "Failed to create file" << fileName;
sendError(clientAddr, clientPort, AccessViolation, "Cannot create file");
delete file;
return;
}
sessions.insert(clientPort, fileName);
qDebug() << "File" << fileName << "opened for writing.";
sendAck(clientAddr, clientPort, 0);
}
void MainWindow::handleData(const QHostAddress &clientAddr, quint16 clientPort, quint16 blockNumber, const QByteArray &data)
{
// 查找对应的文件和会话
if (!sessions.contains(clientPort)) {
sendError(clientAddr, clientPort, UnknownTransferID, "Unknown transfer ID");
return;
}
QString fileName = sessions.value(clientPort);
QFile file(fileName);
if (!file.open(QIODevice::Append)) {
qDebug() << "Failed to open file" << fileName;
sendError(clientAddr, clientPort, AccessViolation, "Cannot write to file");
return;
}
file.write(data); // 数据包的前4个字节是操作码和块编号 之前这里做了mid(4),为的是删除 之后发现不需要,所以改了。
file.close();
// 发送ACK确认
sendAck(clientAddr, clientPort, blockNumber);
}
void MainWindow::handleAck(const QHostAddress &clientAddr, quint16 clientPort, quint16 blockNumber)
{
// 在这里处理ACK,通常是在发送文件时使用
qDebug() << "ACK received for block" << blockNumber << "from" << clientAddr.toString() << ":" << clientPort;
// 例如,可以在这里继续发送下一个数据块,如果有的话
}
void MainWindow::handleError(const QHostAddress &clientAddr, quint16 clientPort, ErrorCode errorCode, const QString &errorMessage)
{
// 打印或处理错误
qDebug() << "Error from" << clientAddr.toString() << ":" << clientPort << "," << errorCode << "," << errorMessage;
}
void MainWindow::sendAck(const QHostAddress &clientAddr, quint16 clientPort, quint16 blockNumber)
{
QByteArray ackPacket = createAckPacket(blockNumber);
socket.writeDatagram(ackPacket, clientAddr, clientPort);
}
void MainWindow::sendData(const QHostAddress &clientAddr, quint16 clientPort, quint16 blockNumber, const QByteArray &data)
{
QByteArray packet = createDataPacket(blockNumber, data);
socket.writeDatagram(packet, clientAddr, clientPort);
}
void MainWindow::sendError(const QHostAddress &clientAddr, quint16 clientPort, ErrorCode errorCode, const QString &errorMessage)
{
QByteArray errorPacket = createErrorPacket(errorCode, errorMessage);
socket.writeDatagram(errorPacket, clientAddr, clientPort);
}
QByteArray MainWindow::createAckPacket(quint16 blockNumber)
{
QByteArray packet;
QDataStream stream(&packet, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << quint16(ACK) << blockNumber;
return packet;
}
QByteArray MainWindow::createDataPacket(quint16 blockNumber, const QByteArray &data)
{
QByteArray packet;
QDataStream stream(&packet, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
// 操作码为DATA (通常为3)
stream << quint16(DATA1) << blockNumber;
packet.append(data);
return packet;
}
QByteArray MainWindow::createErrorPacket(ErrorCode errorCode, const QString &errorMessage)
{
QByteArray packet;
QDataStream stream(&packet, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << quint16(ERROR) << quint16(errorCode);
packet.append(errorMessage.toUtf8());
packet.append(char(0)); // 以0字节结束
return packet;
}
void MainWindow::on_pushButton_clicked()
{
start();
}