================================================================================
文件传输系统 - 服务器端代码
================================================================================
[文件: WinServ.pro]
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main_server.cpp \
serverwidget.cpp
HEADERS += \
serverwidget.h
FORMS += \
serverwidget.ui
RESOURCES += \
resources.qrc
RC_FILE = app.rc
# Build directory
DESTDIR = bin
OBJECTS_DIR = temp/obj
MOC_DIR = temp/moc
RCC_DIR = temp/rcc
UI_DIR = temp/ui
[文件: main_server.cpp]
#include "serverwidget.h"
#include <QApplication>
#include <QTextCodec>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
#endif
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
QApplication a(argc, argv);
ServerWidget w;
w.setFixedSize(562, 400);
w.show();
return a.exec();
}
[文件: serverwidget.h]
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QFile>
#include <QDir>
#include <QMap>
#include <QAtomicInteger>
#include <QMenu>
#include <QSystemTrayIcon>
#include <QThreadPool>
#include <QRunnable>
#include <QLineEdit>
#include <QIntValidator>
namespace Ui {
class ServerWidget;
}
struct FileInfo {
QString relativePath;
qint64 size;
QByteArray hash;
QString absolutePath;
};
struct ClientInfo {
QTcpSocket* socket;
QMap<QString, FileInfo> requestedFiles;
int concurrentTransfers;
QAtomicInteger<int> activeTransfers;
bool isReceivingFileList;
QByteArray fileListBuffer;
};
class FileTransferTask : public QRunnable {
public:
FileTransferTask(QTcpSocket* socket, const FileInfo& fileInfo, QObject* parent = nullptr);
void run() override;
private:
QTcpSocket* m_socket;
FileInfo m_fileInfo;
QObject* m_parent;
};
class ServerWidget : public QWidget
{
Q_OBJECT
public:
explicit ServerWidget(QWidget *parent = nullptr);
~ServerWidget();
protected:
void closeEvent(QCloseEvent *event) override;
void changeEvent(QEvent *event) override;
private slots:
void on_starButton_clicked();
void on_stopButton_clicked();
void DirButton_clicked();
void on_radioButton_clicked();
void onConcurrentTextChanged(const QString &text);
void newConnection();
void clientDisconnected();
void readClientData();
void onBytesWritten(qint64 bytes);
void onTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
void onRestoreAction();
void onQuitAction();
private:
Ui::ServerWidget *ui;
QTcpServer *tcpServer;
QMap<QTcpSocket*, ClientInfo*> clients;
QString serverDir;
QFile *logFile;
QMenu *contextMenu;
QSystemTrayIcon *trayIcon;
QThreadPool *threadPool;
QMap<QString, FileInfo> fileList;
int maxConcurrentTransfers;
QIntValidator *concurrentValidator;
void showLog(const QString &message);
void initContextMenu();
void showContextMenu(const QPoint &pos);
void clearLog();
void copyText();
void initSystemTray();
QString getLocalIP();
void initLogFile();
void closeLogFile();
void loadPortSetting();
void savePortSetting();
void scanDirectory();
QByteArray calculateFileHash(const QString &filePath);
void sendFileList(QTcpSocket* socket);
void cleanupClient(ClientInfo* clientInfo);
void stopServer();
void closeServer();
void sendErrorResponse(QTcpSocket* socket, const QString& errorMessage);
void handleFileRequest(QTcpSocket* socket, const QString& filePath);
};
#endif
[文件: serverwidget.cpp]
#include "serverwidget.h"
#include "ui_serverwidget.h"
#include <QNetworkInterface>
#include <QFileDialog>
#include <QMessageBox>
#include <QCryptographicHash>
#include <QDateTime>
#include <QHostAddress>
#include <QTextCursor>
#include <QCloseEvent>
#include <QTimer>
#include <QSettings>
#include <QClipboard>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QThreadPool>
#include <QStack>
#include <QLineEdit>
#include <QIntValidator>
FileTransferTask::FileTransferTask(QTcpSocket* socket, const FileInfo& fileInfo, QObject* parent)
: m_socket(socket), m_fileInfo(fileInfo), m_parent(parent)
{
setAutoDelete(true);
}
void FileTransferTask::run()
{
if (!m_socket || m_socket->state() != QAbstractSocket::ConnectedState) {
return;
}
QFile file(m_fileInfo.absolutePath);
if (!file.open(QIODevice::ReadOnly)) {
return;
}
QByteArray header = "FILE:" + m_fileInfo.relativePath.toUtf8() + "|" +
QByteArray::number(m_fileInfo.size) + "|" +
m_fileInfo.hash.toHex() + "\n";
m_socket->write(header);
if (!m_socket->waitForBytesWritten(5000)) {
file.close();
return;
}
const qint64 chunkSize = 64 * 1024;
qint64 bytesSent = 0;
while (!file.atEnd() && bytesSent < m_fileInfo.size) {
QByteArray data = file.read(chunkSize);
qint64 bytesWritten = m_socket->write(data);
if (bytesWritten == -1) {
break;
}
if (!m_socket->waitForBytesWritten(30000)) {
break;
}
bytesSent += bytesWritten;
}
file.close();
m_socket->write("FILE_END\n");
m_socket->waitForBytesWritten(5000);
}
ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget),
tcpServer(nullptr),
logFile(nullptr),
trayIcon(nullptr),
threadPool(new QThreadPool(this)),
maxConcurrentTransfers(3),
concurrentValidator(new QIntValidator(1, 512, this))
{
setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
ui->setupUi(this);
threadPool->setMaxThreadCount(maxConcurrentTransfers);
ui->lineEditConcurrent->setValidator(concurrentValidator);
ui->lineEditConcurrent->setText(QString::number(maxConcurrentTransfers));
connect(ui->lineEditConcurrent, &QLineEdit::textChanged,
this, &ServerWidget::onConcurrentTextChanged);
ui->line_IP->setText(getLocalIP());
ui->line_IP->setEnabled(false);
ui->line_Port->setEnabled(false);
QString downPath = QDir::currentPath() + "/FileDownload";
QDir dir;
if (!dir.exists(downPath)) {
dir.mkdir(downPath);
}
ui->line_Dir->setText(downPath);
serverDir = downPath;
loadPortSetting();
connect(ui->DirButton, &QPushButton::clicked, this, &ServerWidget::DirButton_clicked);
ui->textBrowser_Show->setFont(QFont("Consolas", 9));
initLogFile();
initContextMenu();
initSystemTray();
showLog("服务器初始化完成");
}
ServerWidget::~ServerWidget()
{
stopServer();
closeLogFile();
if (trayIcon) {
trayIcon->hide();
delete trayIcon;
}
threadPool->waitForDone(3000);
delete ui;
}
void ServerWidget::onConcurrentTextChanged(const QString &text)
{
bool ok;
int value = text.toInt(&ok);
if (ok && value >= 1 && value <= 512) {
maxConcurrentTransfers = value;
threadPool->setMaxThreadCount(value);
showLog("并发传输数已设置为: " + QString::number(value));
} else if (!text.isEmpty()) {
ui->lineEditConcurrent->setText(QString::number(maxConcurrentTransfers));
}
}
void ServerWidget::scanDirectory()
{
fileList.clear();
QDir dir(serverDir);
if (!dir.exists()) {
showLog("错误: 目录不存在: " + serverDir);
return;
}
QStack<QDir> dirStack;
dirStack.push(dir);
int fileCount = 0;
qint64 totalSize = 0;
while (!dirStack.isEmpty()) {
QDir currentDir = dirStack.pop();
QFileInfoList entries = currentDir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
for (const QFileInfo &entry : entries) {
if (entry.isDir()) {
dirStack.push(QDir(entry.absoluteFilePath()));
} else {
FileInfo fileInfo;
fileInfo.relativePath = dir.relativeFilePath(entry.absoluteFilePath());
fileInfo.absolutePath = entry.absoluteFilePath();
fileInfo.size = entry.size();
fileInfo.hash = calculateFileHash(entry.absoluteFilePath());
if (!fileInfo.hash.isEmpty()) {
fileList.insert(fileInfo.relativePath, fileInfo);
fileCount++;
totalSize += entry.size();
}
}
}
}
double totalSizeMB = totalSize / (1024.0 * 1024.0);
showLog(QString("目录扫描完成,共发现 %1 个文件,总大小: %2 MB").arg(fileCount).arg(totalSizeMB, 0, 'f', 2));
}
void ServerWidget::sendFileList(QTcpSocket* socket)
{
if (!socket || socket->state() != QAbstractSocket::ConnectedState) {
return;
}
if (fileList.isEmpty()) {
sendErrorResponse(socket, "服务器文件列表为空");
return;
}
showLog("向客户端发送文件列表...");
QByteArray header = "FILE_LIST_START:" + QByteArray::number(fileList.size()) + "\n";
qint64 written = socket->write(header);
if (written == -1 || !socket->waitForBytesWritten(5000)) {
showLog("发送文件列表头失败");
return;
}
int filesSent = 0;
for (auto it = fileList.constBegin(); it != fileList.constEnd(); ++it) {
const FileInfo &fileInfo = it.value();
QByteArray fileData = fileInfo.relativePath.toUtf8() + "|" +
QByteArray::number(fileInfo.size) + "|" +
fileInfo.hash.toHex() + "\n";
written = socket->write(fileData);
if (written == -1) {
showLog("发送文件信息失败: " + fileInfo.relativePath);
continue;
}
if (!socket->waitForBytesWritten(1000)) {
showLog("发送文件信息超时: " + fileInfo.relativePath);
break;
}
filesSent++;
}
socket->write("FILE_LIST_END\n");
socket->waitForBytesWritten(5000);
showLog(QString("文件列表发送完成,成功发送 %1/%2 个文件信息").arg(filesSent).arg(fileList.size()));
}
void ServerWidget::newConnection()
{
QTcpSocket *clientSocket = tcpServer->nextPendingConnection();
if (!clientSocket) return;
clientSocket->setReadBufferSize(256 * 1024);
clientSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
ClientInfo *clientInfo = new ClientInfo;
clientInfo->socket = clientSocket;
clientInfo->concurrentTransfers = maxConcurrentTransfers;
clientInfo->activeTransfers.storeRelaxed(0);
clientInfo->isReceivingFileList = false;
clientInfo->fileListBuffer.clear();
clients.insert(clientSocket, clientInfo);
connect(clientSocket, &QTcpSocket::disconnected, this, &ServerWidget::clientDisconnected);
connect(clientSocket, &QTcpSocket::readyRead, this, &ServerWidget::readClientData);
connect(clientSocket, &QTcpSocket::bytesWritten, this, &ServerWidget::onBytesWritten);
QString clientIP = clientSocket->peerAddress().toString();
if (clientIP.startsWith("::ffff:")) clientIP = clientIP.mid(7);
quint16 clientPort = clientSocket->peerPort();
showLog("客户端连接: " + clientIP + ":" + QString::number(clientPort));
scanDirectory();
sendFileList(clientSocket);
}
void ServerWidget::readClientData()
{
QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
if (!clientSocket || !clients.contains(clientSocket)) return;
ClientInfo *clientInfo = clients.value(clientSocket);
QByteArray data = clientSocket->readAll();
QList<QByteArray> lines = data.split('\n');
for (const QByteArray &line : lines) {
if (line.isEmpty()) continue;
QString request = QString::fromUtf8(line).trimmed();
if (request.startsWith("REQUEST_FILE:")) {
QString filePath = request.mid(13);
handleFileRequest(clientSocket, filePath);
}
else if (request == "TRANSFER_COMPLETE") {
clientInfo->activeTransfers.deref();
}
else if (request == "ALL_FILES_COMPLETE") {
QString clientIP = clientSocket->peerAddress().toString();
if (clientIP.startsWith("::ffff:")) clientIP = clientIP.mid(7);
showLog("客户端 " + clientIP + " 所有文件传输完成");
}
else if (request == "GET_FILE_LIST") {
showLog("客户端请求更新文件列表");
scanDirectory();
sendFileList(clientSocket);
}
else if (!request.isEmpty()) {
showLog("未知命令: " + request);
}
}
}
void ServerWidget::handleFileRequest(QTcpSocket* socket, const QString& filePath)
{
if (!socket || !clients.contains(socket)) return;
ClientInfo *clientInfo = clients.value(socket);
if (fileList.contains(filePath)) {
FileInfo fileInfo = fileList.value(filePath);
if (clientInfo->activeTransfers.loadRelaxed() >= maxConcurrentTransfers) {
sendErrorResponse(socket, "并发传输数已达上限: " + QString::number(maxConcurrentTransfers));
return;
}
QFileInfo diskFileInfo(fileInfo.absolutePath);
if (!diskFileInfo.exists() || !diskFileInfo.isFile()) {
sendErrorResponse(socket, "文件不存在: " + filePath);
showLog("文件不存在: " + fileInfo.absolutePath);
return;
}
clientInfo->activeTransfers.ref();
clientInfo->requestedFiles.insert(filePath, fileInfo);
FileTransferTask *task = new FileTransferTask(socket, fileInfo, this);
threadPool->start(task);
showLog("开始传输文件: " + filePath + " (" +
QString::number(fileInfo.size / (1024.0 * 1024.0), 'f', 2) + " MB)");
} else {
sendErrorResponse(socket, "文件不存在: " + filePath);
showLog("客户端请求不存在的文件: " + filePath);
}
}
void ServerWidget::clientDisconnected()
{
QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
if (!clientSocket) return;
QString clientIP = clientSocket->peerAddress().toString();
if (clientIP.startsWith("::ffff:")) clientIP = clientIP.mid(7);
if (clients.contains(clientSocket)) {
ClientInfo *clientInfo = clients.value(clientSocket);
if (clientInfo->activeTransfers.loadRelaxed() > 0) {
showLog("客户端断开,中断 " + QString::number(clientInfo->activeTransfers.loadRelaxed()) + " 个传输任务");
}
cleanupClient(clientInfo);
clients.remove(clientSocket);
delete clientInfo;
}
showLog("客户端断开连接: " + clientIP);
clientSocket->deleteLater();
}
void ServerWidget::onBytesWritten(qint64 bytes)
{
Q_UNUSED(bytes);
}
QString ServerWidget::getLocalIP()
{
QString localIP;
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
for (const QHostAddress &address : ipAddressesList) {
if (address.protocol() == QAbstractSocket::IPv4Protocol &&
address != QHostAddress::LocalHost) {
localIP = address.toString();
break;
}
}
if (localIP.isEmpty()) {
localIP = QHostAddress(QHostAddress::LocalHost).toString();
}
return localIP;
}
void ServerWidget::loadPortSetting()
{
QString configPath = QDir::currentPath() + "/LocConfig";
QString filePath = configPath + "/Connset.con";
QDir dir;
if (!dir.exists(configPath)) {
dir.mkpath(configPath);
return;
}
QSettings settings(filePath, QSettings::IniFormat);
quint16 port = settings.value("Network/ServerPort", 15210).toUInt();
ui->line_Port->setText(QString::number(port));
}
void ServerWidget::savePortSetting()
{
bool portOk;
quint16 port = ui->line_Port->text().toUShort(&portOk);
if (portOk && port > 0 && port < 65535) {
QString configPath = QDir::currentPath() + "/LocConfig";
QString filePath = configPath + "/Connset.con";
QDir dir;
if (!dir.exists(configPath)) {
dir.mkpath(configPath);
}
QSettings settings(filePath, QSettings::IniFormat);
settings.setValue("Network/ServerPort", port);
settings.sync();
}
}
void ServerWidget::initLogFile()
{
QDir logDir(QDir::currentPath() + "/log");
if (!logDir.exists()) logDir.mkpath(".");
QString logFileName = QDateTime::currentDateTime().toString("yyyy.MM.dd") + ".log";
QString logFilePath = logDir.absoluteFilePath(logFileName);
logFile = new QFile(logFilePath);
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
{
delete logFile;
logFile = nullptr;
showLog("无法打开日志文件:" + logFilePath);
return;
}
QTextStream stream(logFile);
QString titleText = "文件服务器日志";
stream << "\n";
stream << "**************************************************************************\n";
stream << " " + titleText << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "\n";
stream << "**************************************************************************\n";
stream.flush();
}
void ServerWidget::closeLogFile()
{
if (logFile) {
if (logFile->isOpen()) {
logFile->close();
}
delete logFile;
logFile = nullptr;
}
}
void ServerWidget::showLog(const QString &message)
{
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
QString logMessage = "[" + timestamp + "] " + message;
if (ui->textBrowser_Show->document()->blockCount() > 1000) {
QTextCursor cursor(ui->textBrowser_Show->document());
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, 100);
cursor.removeSelectedText();
}
ui->textBrowser_Show->append(logMessage);
QTextCursor cursor = ui->textBrowser_Show->textCursor();
cursor.movePosition(QTextCursor::End);
ui->textBrowser_Show->setTextCursor(cursor);
if (ui->radioButton_rj->isChecked() && logFile && logFile->isOpen()) {
QTextStream stream(logFile);
stream << logMessage << "\n";
stream.flush();
}
}
void ServerWidget::DirButton_clicked()
{
QString dirNew = QFileDialog::getExistingDirectory(this, "选择服务器目录",
QDir::currentPath(), QFileDialog::ShowDirsOnly);
if (dirNew.isEmpty() || !QDir(dirNew).exists()) {
QMessageBox::warning(this, "警告", "选择目录为空或无效,请重新选择目录");
return;
}
serverDir = QDir(dirNew).absolutePath();
ui->line_Dir->setText(serverDir);
showLog("服务器目录已更改为: " + serverDir);
}
void ServerWidget::on_radioButton_clicked()
{
bool enable = ui->radioButton->isChecked();
ui->line_IP->setEnabled(enable);
ui->line_Port->setEnabled(enable);
}
void ServerWidget::on_starButton_clicked()
{
if (tcpServer && tcpServer->isListening()) {
closeServer();
ui->starButton->setText("启动");
showLog("服务器已停止");
ui->radioButton->setEnabled(true);
ui->line_Dir->setEnabled(true);
ui->DirButton->setEnabled(true);
} else {
bool portOk;
quint16 port = ui->line_Port->text().toUShort(&portOk);
if (!portOk || port <= 0 || port >= 65535) {
QMessageBox::warning(this, "端口错误", "请输入有效的端口号 (1-65534)");
return;
}
savePortSetting();
serverDir = ui->line_Dir->text();
if (!QDir(serverDir).exists()) {
QMessageBox::warning(this, "目录错误", "服务器目录不存在");
return;
}
showLog("正在扫描服务器目录...");
scanDirectory();
if (!tcpServer) {
tcpServer = new QTcpServer(this);
}
if (!tcpServer->listen(QHostAddress::Any, port)) {
showLog("服务器启动失败: " + tcpServer->errorString());
return;
}
connect(tcpServer, &QTcpServer::newConnection, this, &ServerWidget::newConnection);
showLog("服务器已启动,监听端口: " + QString::number(port));
showLog("服务器目录: " + serverDir);
showLog("等待客户端连接...");
ui->starButton->setText("停止");
ui->radioButton->setEnabled(false);
ui->line_IP->setEnabled(false);
ui->line_Port->setEnabled(false);
ui->line_Dir->setEnabled(false);
ui->DirButton->setEnabled(false);
}
}
void ServerWidget::on_stopButton_clicked()
{
QApplication::quit();
}
void ServerWidget::closeServer()
{
if (tcpServer && tcpServer->isListening()) {
QList<QTcpSocket*> clientSockets = clients.keys();
for (QTcpSocket* socket : clientSockets) {
if (clients.contains(socket)) {
ClientInfo* clientInfo = clients.value(socket);
socket->disconnectFromHost();
if (socket->state() == QAbstractSocket::ConnectedState) {
socket->waitForDisconnected(1000);
}
cleanupClient(clientInfo);
clients.remove(socket);
delete clientInfo;
}
}
tcpServer->close();
}
}
void ServerWidget::stopServer()
{
closeServer();
if (tcpServer) {
delete tcpServer;
tcpServer = nullptr;
}
}
void ServerWidget::closeEvent(QCloseEvent *event)
{
stopServer();
event->accept();
}
void ServerWidget::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
if (event->type() == QEvent::WindowStateChange) {
if (isMinimized()) {
hide();
if (trayIcon && trayIcon->isVisible()) {
trayIcon->showMessage("文件服务器", "程序已最小化到系统托盘",
QSystemTrayIcon::Information, 2000);
}
}
}
}
QByteArray ServerWidget::calculateFileHash(const QString &filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
return QByteArray();
}
QCryptographicHash hash(QCryptographicHash::Md5);
const qint64 bufferSize = 64 * 1024;
char buffer[bufferSize];
qint64 bytesRead;
while ((bytesRead = file.read(buffer, bufferSize)) > 0) {
hash.addData(buffer, bytesRead);
}
file.close();
return hash.result();
}
void ServerWidget::cleanupClient(ClientInfo* clientInfo)
{
if (!clientInfo) return;
clientInfo->requestedFiles.clear();
clientInfo->activeTransfers.storeRelaxed(0);
clientInfo->isReceivingFileList = false;
clientInfo->fileListBuffer.clear();
}
void ServerWidget::sendErrorResponse(QTcpSocket* socket, const QString& errorMessage)
{
if (!socket || socket->state() != QAbstractSocket::ConnectedState) {
return;
}
QByteArray errorData = "ERROR:" + errorMessage.toUtf8() + "\n";
socket->write(errorData);
socket->waitForBytesWritten(1000);
}
void ServerWidget::initContextMenu()
{
contextMenu = new QMenu(this);
QAction *copyAction = new QAction("复制", this);
copyAction->setShortcut(QKeySequence::Copy);
connect(copyAction, &QAction::triggered, this, &ServerWidget::copyText);
contextMenu->addAction(copyAction);
contextMenu->addSeparator();
QAction *clearAction = new QAction("清除日志", this);
clearAction->setShortcut(QKeySequence("Ctrl+Del"));
connect(clearAction, &QAction::triggered, this, &ServerWidget::clearLog);
contextMenu->addAction(clearAction);
ui->textBrowser_Show->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->textBrowser_Show, &QTextBrowser::customContextMenuRequested,
this, &ServerWidget::showContextMenu);
}
void ServerWidget::showContextMenu(const QPoint &pos)
{
contextMenu->exec(ui->textBrowser_Show->mapToGlobal(pos));
}
void ServerWidget::copyText()
{
QTextCursor cursor = ui->textBrowser_Show->textCursor();
if (cursor.hasSelection()) {
QString selectedText = cursor.selectedText();
QApplication::clipboard()->setText(selectedText);
}
}
void ServerWidget::clearLog()
{
ui->textBrowser_Show->clear();
}
void ServerWidget::initSystemTray()
{
trayIcon = new QSystemTrayIcon(this);
QIcon icon = QIcon(":/icon/Server-Network.ico");
if (icon.isNull()) {
QPixmap pixmap(16, 16);
pixmap.fill(Qt::blue);
icon = QIcon(pixmap);
}
trayIcon->setIcon(icon);
trayIcon->setToolTip("文件服务器");
QMenu *trayMenu = new QMenu(this);
QAction *restoreAction = new QAction("打开", this);
connect(restoreAction, &QAction::triggered, this, &ServerWidget::onRestoreAction);
trayMenu->addAction(restoreAction);
trayMenu->addSeparator();
QAction *quitAction = new QAction("退出", this);
connect(quitAction, &QAction::triggered, this, &ServerWidget::onQuitAction);
trayMenu->addAction(quitAction);
trayIcon->setContextMenu(trayMenu);
connect(trayIcon, &QSystemTrayIcon::activated, this, &ServerWidget::onTrayIconActivated);
trayIcon->show();
}
void ServerWidget::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason) {
case QSystemTrayIcon::DoubleClick:
onRestoreAction();
break;
default:
break;
}
}
void ServerWidget::onRestoreAction()
{
setWindowState(windowState() & ~Qt::WindowMinimized);
show();
raise();
activateWindow();
}
void ServerWidget::onQuitAction()
{
stopServer();
closeLogFile();
QApplication::quit();
}
[文件: serverwidget.ui]
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ServerWidget</class>
<widget class="QWidget" name="ServerWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>562</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>文件服务器</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>292</x>
<y>6</y>
<width>61</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>端口:</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>6</x>
<y>6</y>
<width>81</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>本机IP:</string>
</property>
</widget>
<widget class="QPushButton" name="stopButton">
<property name="geometry">
<rect>
<x>480</x>
<y>290</y>
<width>70</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>退出</string>
</property>
</widget>
<widget class="QLineEdit" name="line_Dir">
<property name="geometry">
<rect>
<x>130</x>
<y>60</y>
<width>381</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>10</pointsize>
</font>
</property>
</widget>
<widget class="QRadioButton" name="radioButton">
<property name="geometry">
<rect>
<x>452</x>
<y>6</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>修改端口</string>
</property>
</widget>
<widget class="QLineEdit" name="line_Port">
<property name="geometry">
<rect>
<x>356</x>
<y>6</y>
<width>61</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>15210</string>
</property>
<property name="maxLength">
<number>5</number>
</property>
</widget>
<widget class="QPushButton" name="starButton">
<property name="geometry">
<rect>
<x>480</x>
<y>220</y>
<width>70</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>启动</string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>6</x>
<y>60</y>
<width>131</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>服务器文件路径:</string>
</property>
</widget>
<widget class="QLineEdit" name="line_IP">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>86</x>
<y>6</y>
<width>181</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>15</pointsize>
</font>
</property>
<property name="maxLength">
<number>15</number>
</property>
</widget>
<widget class="QTextBrowser" name="textBrowser_Show">
<property name="geometry">
<rect>
<x>6</x>
<y>114</y>
<width>471</width>
<height>231</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
</widget>
<widget class="QPushButton" name="DirButton">
<property name="geometry">
<rect>
<x>510</x>
<y>60</y>
<width>41</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
<widget class="QRadioButton" name="radioButton_rj">
<property name="geometry">
<rect>
<x>490</x>
<y>140</y>
<width>71</width>
<height>51</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>记录日志</string>
</property>
</widget>
<widget class="QLabel" name="label_concurrent">
<property name="geometry">
<rect>
<x>490</x>
<y>100</y>
<width>71</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>并发传输数:</string>
</property>
</widget>
<widget class="QLineEdit" name="lineEditConcurrent">
<property name="geometry">
<rect>
<x>490</x>
<y>120</y>
<width>61</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>3</string>
</property>
<property name="maxLength">
<number>3</number>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
[文件: resources.qrc]
<RCC>
<qresource prefix="/icon">
<file>icons/Server-Network.ico</file>
</qresource>
</RCC>
================================================================================
文件传输系统 - 客户端代码
================================================================================
[文件: WinClient.pro]
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
clientwidget.cpp \
main_client.cpp
HEADERS += \
clientwidget.h
FORMS += \
clientwidget.ui
# Build directory
DESTDIR = bin
OBJECTS_DIR = temp/obj
MOC_DIR = temp/moc
RCC_DIR = temp/rcc
UI_DIR = temp/ui
[文件: main_client.cpp]
#include "clientwidget.h"
#include <QApplication>
#include <QTextCodec>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
#endif
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
QApplication a(argc, argv);
ClientWidget w;
w.setFixedSize(575, 400);
w.show();
return a.exec();
}
[文件: clientwidget.h]
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include <QFile>
#include <QDir>
#include <QMap>
#include <QSettings>
#include <QThreadPool>
#include <QRunnable>
#include <QLineEdit>
#include <QIntValidator>
namespace Ui {
class ClientWidget;
}
struct FileInfo {
QString relativePath;
qint64 size;
QByteArray hash;
};
class FileDownloadTask : public QRunnable {
public:
FileDownloadTask(const QString& serverIP, quint16 port, const FileInfo& fileInfo,
const QString& baseDir, QObject* parent = nullptr);
void run() override;
signals:
void downloadFinished(const QString& filePath, bool success);
private:
QString m_serverIP;
quint16 m_port;
FileInfo m_fileInfo;
QString m_baseDir;
QObject* m_parent;
};
class ClientWidget : public QWidget
{
Q_OBJECT
public:
explicit ClientWidget(QWidget *parent = nullptr);
~ClientWidget();
private slots:
void on_radioButton_clicked();
void on_pushButton_clicked();
void onDownloadFinished(const QString& filePath, bool success);
void onConcurrentTextChanged(const QString &text);
void onSocketConnected();
void onSocketDisconnected();
void onSocketReadyRead();
void onSocketError(QAbstractSocket::SocketError error);
private:
Ui::ClientWidget *ui;
QTcpSocket *tcpSocket;
QMap<QString, FileInfo> fileList;
QThreadPool *threadPool;
int maxConcurrentDownloads;
int completedDownloads;
int totalFiles;
bool isDownloading;
QByteArray receiveBuffer;
bool isReceivingFileList;
QIntValidator *concurrentValidator;
void showLog(const QString &message);
void connectToServer();
void getFileListFromServer();
void parseFileList(const QByteArray& data);
void startDownloads();
void resetTransferState();
void processFileListData(const QByteArray& data);
bool verifyFileHash(const QString& filePath, const QByteArray& expectedHash);
void saveConnectionSettings();
void loadConnectionSettings();
QString getParentDir();
};
#endif
[文件: clientwidget.cpp]
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QTcpSocket>
#include <QFileDialog>
#include <QMessageBox>
#include <QCryptographicHash>
#include <QDateTime>
#include <QHostAddress>
#include <QTextCursor>
#include <QThreadPool>
#include <QDir>
#include <QFileInfo>
#include <QIntValidator>
QString Encrypt(const QString& value, const QString& key)
{
QByteArray encryptedData;
QByteArray dataToEncrypt = value.toUtf8();
QByteArray hash = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha256);
for (int i = 0; i < dataToEncrypt.size(); ++i) {
encryptedData.append(dataToEncrypt.at(i) ^ hash.at(i % hash.size()));
}
return encryptedData.toBase64();
}
QString Decrypt(const QString& encryptedValue, const QString& key)
{
QByteArray decryptedData;
QByteArray dataToDecrypt = QByteArray::fromBase64(encryptedValue.toUtf8());
QByteArray hash = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha256);
for (int i = 0; i < dataToDecrypt.size(); ++i) {
decryptedData.append(dataToDecrypt.at(i) ^ hash.at(i % hash.size()));
}
return QString::fromUtf8(decryptedData);
}
FileDownloadTask::FileDownloadTask(const QString& serverIP, quint16 port,
const FileInfo& fileInfo, const QString& baseDir, QObject* parent)
: m_serverIP(serverIP), m_port(port), m_fileInfo(fileInfo), m_baseDir(baseDir), m_parent(parent)
{
setAutoDelete(true);
}
void FileDownloadTask::run()
{
QTcpSocket socket;
socket.connectToHost(m_serverIP, m_port);
if (!socket.waitForConnected(5000)) {
emit static_cast<ClientWidget*>(m_parent)->downloadFinished(m_fileInfo.relativePath, false);
return;
}
QByteArray request = "REQUEST_FILE:" + m_fileInfo.relativePath.toUtf8() + "\n";
socket.write(request);
if (!socket.waitForBytesWritten(5000)) {
emit static_cast<ClientWidget*>(m_parent)->downloadFinished(m_fileInfo.relativePath, false);
return;
}
QString filePath = QDir(m_baseDir).filePath(m_fileInfo.relativePath);
QFileInfo fileInfo(filePath);
QDir().mkpath(fileInfo.absolutePath());
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly)) {
emit static_cast<ClientWidget*>(m_parent)->downloadFinished(m_fileInfo.relativePath, false);
return;
}
qint64 totalBytesReceived = 0;
bool success = false;
socket.waitForReadyRead(1000);
while (socket.bytesAvailable() > 0 || socket.waitForReadyRead(30000)) {
QByteArray data = socket.readAll();
file.write(data);
totalBytesReceived += data.size();
if (totalBytesReceived >= m_fileInfo.size) {
break;
}
}
file.close();
if (totalBytesReceived == m_fileInfo.size) {
success = static_cast<ClientWidget*>(m_parent)->verifyFileHash(filePath, m_fileInfo.hash);
}
socket.disconnectFromHost();
emit static_cast<ClientWidget*>(m_parent)->downloadFinished(m_fileInfo.relativePath, success);
}
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget),
tcpSocket(new QTcpSocket(this)),
threadPool(new QThreadPool(this)),
maxConcurrentDownloads(3),
completedDownloads(0),
totalFiles(0),
isDownloading(false),
isReceivingFileList(false),
concurrentValidator(new QIntValidator(1, 512, this))
{
setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
ui->setupUi(this);
threadPool->setMaxThreadCount(maxConcurrentDownloads);
ui->lineEditConcurrent->setValidator(concurrentValidator);
ui->lineEditConcurrent->setText(QString::number(maxConcurrentDownloads));
connect(ui->lineEditConcurrent, &QLineEdit::textChanged,
this, &ClientWidget::onConcurrentTextChanged);
loadConnectionSettings();
QString downloadDir = getParentDir();
ui->lineEdit_Dir_Clic->setText(downloadDir);
connect(tcpSocket, &QTcpSocket::connected, this, &ClientWidget::onSocketConnected);
connect(tcpSocket, &QTcpSocket::disconnected, this, &ClientWidget::onSocketDisconnected);
connect(tcpSocket, &QTcpSocket::readyRead, this, &ClientWidget::onSocketReadyRead);
connect(tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
this, &ClientWidget::onSocketError);
showLog("客户端初始化完成");
}
ClientWidget::~ClientWidget()
{
if (tcpSocket && tcpSocket->state() == QAbstractSocket::ConnectedState) {
tcpSocket->disconnectFromHost();
tcpSocket->waitForDisconnected(1000);
}
delete tcpSocket;
delete ui;
}
void ClientWidget::onConcurrentTextChanged(const QString &text)
{
bool ok;
int value = text.toInt(&ok);
if (ok && value >= 1 && value <= 512) {
maxConcurrentDownloads = value;
threadPool->setMaxThreadCount(value);
showLog("并发下载数已设置为: " + QString::number(value));
} else if (!text.isEmpty()) {
ui->lineEditConcurrent->setText(QString::number(maxConcurrentDownloads));
}
}
QString ClientWidget::getParentDir()
{
QDir currentDir = QDir::current();
if (currentDir.cdUp()) {
return currentDir.absolutePath();
} else {
return currentDir.absolutePath();
}
}
void ClientWidget::showLog(const QString &message)
{
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
ui->textBrowser->append("[" + timestamp + "] " + message);
QTextCursor cursor = ui->textBrowser->textCursor();
cursor.movePosition(QTextCursor::End);
ui->textBrowser->setTextCursor(cursor);
}
void ClientWidget::on_radioButton_clicked()
{
bool enable = ui->radioButton->isChecked();
ui->lineEdit_IP->setEnabled(enable);
ui->lineEdit_Port->setEnabled(enable);
}
void ClientWidget::on_pushButton_clicked()
{
if (isDownloading) {
showLog("停止下载...");
resetTransferState();
ui->pushButton->setText("下载");
ui->pushButton->setEnabled(true);
ui->radioButton->setEnabled(true);
isDownloading = false;
return;
}
if(ui->pushButton->text() == "下载") {
QString serverIP = ui->lineEdit_IP->text();
if (serverIP.isEmpty()) {
QMessageBox::warning(this, "警告", "请输入服务器IP地址");
return;
}
bool portOk;
quint16 port = ui->lineEdit_Port->text().toUShort(&portOk);
if (!portOk || port == 0) {
QMessageBox::warning(this, "警告", "请输入有效的端口号");
return;
}
saveConnectionSettings();
resetTransferState();
isDownloading = true;
ui->pushButton->setText("停止");
ui->lineEdit_IP->setEnabled(false);
ui->lineEdit_Port->setEnabled(false);
ui->radioButton->setChecked(false);
ui->radioButton->setEnabled(false);
showLog("正在连接服务器: " + serverIP + ":" + QString::number(port));
connectToServer();
}
}
void ClientWidget::connectToServer()
{
QString serverIP = ui->lineEdit_IP->text();
quint16 port = ui->lineEdit_Port->text().toUShort();
tcpSocket->connectToHost(serverIP, port);
if (!tcpSocket->waitForConnected(5000)) {
showLog("连接超时: " + tcpSocket->errorString());
resetTransferState();
ui->pushButton->setText("下载");
ui->pushButton->setEnabled(true);
ui->radioButton->setEnabled(true);
isDownloading = false;
return;
}
}
void ClientWidget::onSocketConnected()
{
showLog("已连接到服务器 " + ui->lineEdit_IP->text() + ":" + ui->lineEdit_Port->text());
isReceivingFileList = true;
receiveBuffer.clear();
showLog("正在获取文件列表...");
}
void ClientWidget::onSocketDisconnected()
{
showLog("与服务器断开连接");
if (isDownloading && completedDownloads < totalFiles) {
showLog("下载被中断");
}
resetTransferState();
ui->pushButton->setText("下载");
ui->pushButton->setEnabled(true);
ui->radioButton->setEnabled(true);
isDownloading = false;
}
void ClientWidget::onSocketReadyRead()
{
QByteArray data = tcpSocket->readAll();
if (isReceivingFileList) {
receiveBuffer.append(data);
if (receiveBuffer.contains("FILE_LIST_END")) {
processFileListData(receiveBuffer);
isReceivingFileList = false;
receiveBuffer.clear();
}
}
}
void ClientWidget::onSocketError(QAbstractSocket::SocketError error)
{
Q_UNUSED(error);
showLog("网络错误: " + tcpSocket->errorString());
resetTransferState();
ui->pushButton->setText("下载");
ui->pushButton->setEnabled(true);
ui->radioButton->setEnabled(true);
isDownloading = false;
}
void ClientWidget::processFileListData(const QByteArray& data)
{
fileList.clear();
QStringList lines = QString::fromUtf8(data).split('\n', Qt::SkipEmptyParts);
bool parsingFiles = false;
int fileCount = 0;
for (const QString& line : lines) {
if (line.startsWith("FILE_LIST_START:")) {
parsingFiles = true;
QString countStr = line.mid(16).split(':')[0];
totalFiles = countStr.toInt();
continue;
}
if (line == "FILE_LIST_END") {
break;
}
if (parsingFiles && line.contains('|')) {
QStringList parts = line.split('|');
if (parts.size() >= 3) {
FileInfo fileInfo;
fileInfo.relativePath = parts[0];
fileInfo.size = parts[1].toLongLong();
fileInfo.hash = QByteArray::fromHex(parts[2].toLatin1());
fileList.insert(fileInfo.relativePath, fileInfo);
fileCount++;
}
}
}
showLog("获取到文件列表,共 " + QString::number(fileCount) + " 个文件");
if (fileCount > 0) {
startDownloads();
} else {
showLog("错误: 文件列表为空");
tcpSocket->disconnectFromHost();
}
}
void ClientWidget::startDownloads()
{
if (fileList.isEmpty()) {
showLog("错误: 文件列表为空");
return;
}
completedDownloads = 0;
totalFiles = fileList.size();
showLog("开始下载 " + QString::number(totalFiles) + " 个文件,并发数: " + QString::number(maxConcurrentDownloads));
for (auto it = fileList.constBegin(); it != fileList.constEnd(); ++it) {
const FileInfo& fileInfo = it.value();
FileDownloadTask* task = new FileDownloadTask(
ui->lineEdit_IP->text(),
ui->lineEdit_Port->text().toUShort(),
fileInfo,
ui->lineEdit_Dir_Clic->text(),
this
);
connect(task, &FileDownloadTask::downloadFinished,
this, &ClientWidget::onDownloadFinished, Qt::QueuedConnection);
threadPool->start(task);
}
}
void ClientWidget::onDownloadFinished(const QString& filePath, bool success)
{
completedDownloads++;
if (success) {
showLog("下载完成: " + filePath + " (" +
QString::number(completedDownloads) + "/" + QString::number(totalFiles) + ")");
} else {
showLog("下载失败: " + filePath);
}
if (completedDownloads >= totalFiles) {
showLog("所有文件下载完成");
if (tcpSocket && tcpSocket->state() == QAbstractSocket::ConnectedState) {
tcpSocket->write("ALL_FILES_COMPLETE\n");
tcpSocket->disconnectFromHost();
}
ui->pushButton->setText("下载");
ui->pushButton->setEnabled(true);
ui->radioButton->setEnabled(true);
isDownloading = false;
}
}
bool ClientWidget::verifyFileHash(const QString& filePath, const QByteArray& expectedHash)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
QCryptographicHash hash(QCryptographicHash::Md5);
const qint64 bufferSize = 8192;
char buffer[bufferSize];
qint64 bytesRead;
while ((bytesRead = file.read(buffer, bufferSize)) > 0) {
hash.addData(buffer, bytesRead);
}
file.close();
QByteArray actualHash = hash.result();
return (actualHash == expectedHash);
}
void ClientWidget::resetTransferState()
{
fileList.clear();
receiveBuffer.clear();
completedDownloads = 0;
totalFiles = 0;
isReceivingFileList = false;
threadPool->clear();
threadPool->waitForDone(1000);
}
void ClientWidget::saveConnectionSettings()
{
QString encryptionKey = "Connect.set";
QString configPath = QDir::currentPath() + "/LocConfig";
QDir dir;
if (!dir.exists(configPath)) {
dir.mkpath(configPath);
}
QString filePath = configPath + "/Connset.con";
QSettings settings(filePath, QSettings::IniFormat);
QString serverIP = ui->lineEdit_IP->text();
quint16 port = ui->lineEdit_Port->text().toUShort();
if (!serverIP.isEmpty() && port > 0) {
QString encryptedIP = Encrypt(serverIP, encryptionKey);
settings.setValue("Network/IPAddress", encryptedIP);
settings.setValue("Network/Port", port);
}
}
void ClientWidget::loadConnectionSettings()
{
QString encryptionKey = "Connect.set";
QString configPath = QDir::currentPath() + "/LocConfig";
QString filePath = configPath + "/Connset.con";
QFile file(filePath);
if (file.exists()) {
QSettings settings(filePath, QSettings::IniFormat);
QString encryptedIP = settings.value("Network/IPAddress").toString();
quint16 port = settings.value("Network/Port", 15210).toUInt();
if (!encryptedIP.isEmpty()) {
QString serverIP = Decrypt(encryptedIP, encryptionKey);
ui->lineEdit_IP->setText(serverIP);
}
ui->lineEdit_Port->setText(QString::number(port));
}
ui->radioButton->setEnabled(true);
if (!ui->lineEdit_Port->text().isEmpty()) {
ui->lineEdit_Port->setEnabled(false);
}
}
[文件: clientwidget.ui]
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ClientWidget</class>
<widget class="QWidget" name="ClientWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>575</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>文件客户端</string>
</property>
<widget class="QLineEdit" name="lineEdit_Port">
<property name="geometry">
<rect>
<x>370</x>
<y>10</y>
<width>61</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>15210</string>
</property>
<property name="maxLength">
<number>5</number>
</property>
</widget>
<widget class="QLineEdit" name="lineEdit_IP">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>110</x>
<y>10</y>
<width>181</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>15</pointsize>
</font>
</property>
<property name="maxLength">
<number>15</number>
</property>
</widget>
<widget class="QTextBrowser" name="textBrowser">
<property name="geometry">
<rect>
<x>5</x>
<y>100</y>
<width>470</width>
<height>241</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>服务器IP:</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>480</x>
<y>240</y>
<width>91</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string>下载</string>
</property>
</widget>
<widget class="QRadioButton" name="radioButton">
<property name="geometry">
<rect>
<x>450</x>
<y>10</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>修改端口</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
<widget class="QLineEdit" name="lineEdit_Dir_Clic">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>140</x>
<y>60</y>
<width>421</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>10</pointsize>
</font>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>314</x>
<y>10</y>
<width>52</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>端口:</string>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>131</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>新宋体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>客户端文件路径:</string>
</property>
</widget>
<widget class="QLabel" name="label_concurrent">
<property name="geometry">
<rect>
<x>480</x>
<y>100</y>
<width>71</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>并发下载数:</string>
</property>
</widget>
<widget class="QLineEdit" name="lineEditConcurrent">
<property name="geometry">
<rect>
<x>480</x>
<y>120</y>
<width>61</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>3</string>
</property>
<property name="maxLength">
<number>3</number>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
================================================================================
README.md - 使用说明文档
================================================================================
[文件: README.md]
# Qt文件传输系统
## 功能特点
- 无XML依赖,直接目录同步
- 支持1-512个并发传输
- 文件MD5完整性校验
- 保持服务器目录结构
- 系统托盘支持
- 详细的传输日志
## 编译说明
### 服务器端
1. 使用Qt Creator打开WinServ.pro
2. 选择构建套件
3. 点击构建
### 客户端
1. 使用Qt Creator打开WinClient.pro
2. 选择构建套件
3. 点击构建
## 使用说明
### 服务器端
1. 设置服务器文件目录
2. 设置监听端口(默认15210)
3. 设置并发传输数(1-512)
4. 点击"启动"开始服务
### 客户端
1. 输入服务器IP和端口
2. 设置本地保存目录
3. 设置并发下载数(1-512)
4. 点击"下载"开始传输
## 协议格式
### 文件列表
最新发布