应某C友要求,写了这个局域网同步小工具,说是同步,实际上只是实现了文件的上传和下载,操作都在客户端,服务端只需要配置好同步目录就不用管了。
先看下效果:
工具的实现非常简单,实际上就是基于QTcp互相发送文件,那么主要问题就是,如何在客户端点击下载的时候,通知服务器传送数据给客户端,而这个就需要用到GitHub的一个轻量级开源库JQLibrary,这个库对于轻量级请求完全够用,非常感谢原作者的分享。
服务器配置文件,配置好同步目录即可:
其中nCmdPort为命令端口,nTransPort为传送文件的端口,可以任意指定,保持一致即可。
服务器端的关键代码:
//author:autumoon
//联系QQ:4589968
//日期:2021-09-26switch (nFun){case enCheckState :{//实际只使用了两个目录,这里取qMin是为了防止配置文件错误int nLocalSavePathCount = qMin(cfg_server.lLocalSavePaths.size(), 2);if (nLocalSavePathCount >= 1 ){lKeys.push_back("ip");lKeys.push_back("state");lKeys.push_back("root");lValues.push_back(strIp);lValues.push_back("OK");lValues.push_back(cfg_server.lLocalSavePaths[0]);//进一步判断,目录是否可写,剩余空间for (int j = 0; j < nLocalSavePathCount; ++j){CStdDir::createDirectory(cfg_server.lLocalSavePaths[j]);}}QByteArray cfgData = JsonParser::StringList2ByteArray(lKeys, lValues);//执行完毕session->replyBytes(cfgData);bAlreadyReply = true;break;}case enCheckClientReady:{bool bRes = false;if (g_pDlg){bRes = !g_pDlg->IsClientBusy();}//返回执行结果session->replyJsonObject({{"ready", QString::number(bRes).toUtf8().data()}});bAlreadyReply = true;break;}case enSyncDir:{//返回执行结果,防止再次发送请求QString strReturnInfo = "1";QString strClientSycDir, strTransPort;if (g_pDlg && JsonParser::ParseRootKeyValue(ba, "dir", strClientSycDir) == 0&& JsonParser::ParseRootKeyValue(ba, "port", strTransPort) == 0){emit g_pDlg->start_download(strClientSycDir, strIp, strTransPort.toUInt());//g_pDlg->SendToClient(strClientSycDir, strIp, strTransPort.toUInt());}session->replyJsonObject({{"state", strReturnInfo.toUtf8().data()}});bAlreadyReply = true;break;}default:break;}}
这里就不得不说一下流程,首先客户端必须连接到服务端,同时请求服务端的配置文件内容,获取服务器端共享的目录,方便后续上传和下载。
而上传和下载的过程中自然需要注意服务器是否繁忙,所以在传输之前都需要检查服务器状态。
最后就是数据传输了,用我以前写好的传输类,很快就完成了!
客户端界面:
客户端主要代码:
#include "mainwindow.h"
#include "ui_mainwindow.h"//author:autumoon
//联系QQ:4589968
//日期:2021-09-24
#include "QtDirFile.h"
#include "TcpUploadClient.h"
#include "scanservercfg.h"
#include <QMessageBox>
#include <QNetworkInterface>
#include "Log.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//读取配置文件QString strConfigPath = GetConfigPath();if (QFile::exists(strConfigPath)){//读取ReadConfigFile(strConfigPath, m_cfg);}else{WriteConfigFile(strConfigPath, m_cfg);}ui->le_synDir->setText(m_cfg.strShareRoot);ui->pb_progress->setValue(0);//需要向服务器发送请求,获取服务器的保存路径ScanServerCfg* ssc = new ScanServerCfg(this);connect(ssc, SIGNAL(finished(bool, QString)), this , SLOT(get_server_cfg(bool, QString)));ssc->CheckState(m_cfg.strServerIp, m_cfg.nCmdPort);//需要启动一个下载的server//初始化bLocalServer = false;m_server = nullptr;if (m_server == nullptr){m_server = new TcpUploadServer(this);connect(m_server, SIGNAL(progress(qint64, qint64)), this, SLOT(download_progress(qint64, qint64)));m_server->SetPort(m_cfg.nTransPort);if(m_server->StartServer()){bLocalServer = true;CLOG::Out("本地用于下载的服务启动成功!");}else{bLocalServer = false;CLOG::Out("本地用于下载的服务启动失败!");}}
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::on_btn_upload_clicked()
{QString strIp = m_cfg.strServerIp;QString strSaveDir = m_strSaveDir;QString strSrcDir = ui->le_synDir->text();if(strSrcDir.length() == 0 || strSaveDir.length() == 0 || strIp.length() == 0){return;}if(!ScanServerCfg::CheckIsServerReady(m_cfg.strServerIp, m_cfg.nCmdPort)){QMessageBox::warning(this, "忙碌!", "服务器忙碌,请稍后再试!");return;}TcpUploadClient *tuc = new TcpUploadClient(this);connect(tuc, SIGNAL(progress(qint64, qint64, const QString&)), this, SLOT(show_progress(qint64, qint64, const QString&)));tuc->SetHostAndPort(strIp, m_cfg.nTransPort);tuc->SetLableStatus(ui->lb_state);QStringList lSrcFiles, lDstFiles;int n = QTDirFile::getFiles(strSrcDir, lSrcFiles);for (int i = 0; i < n; ++i){//得到目标路径QString strCurFile = lSrcFiles[i];QString strDstFile = strCurFile;strDstFile.replace(strSrcDir, strSaveDir);lDstFiles.push_back(strDstFile);}tuc->StartUpload(lSrcFiles, lDstFiles);
}void MainWindow::on_btn_browse_clicked()
{QString strDir = QTDirFile::BrowseDir();if (strDir.length()){ui->le_synDir->setText(strDir);}
}void MainWindow::get_server_cfg(bool bRet, QString strServerSyncDir)
{m_strSaveDir = bRet ? strServerSyncDir : "";if(bRet){m_strSaveDir = strServerSyncDir;ui->lb_state->setText("连接服务器成功!");}else{ui->lb_state->setText("连接服务器失败!");}
}void MainWindow::download_progress(qint64 cur, qint64 sum)
{ui->pb_progress->setValue(cur);ui->pb_progress->setMaximum(sum);
}void MainWindow::show_progress(qint64 cur, qint64 sum, const QString & info)
{ui->pb_progress->setValue(cur);ui->pb_progress->setMaximum(sum);ui->pb_progress->setToolTip(info);
}QHostAddress MainWindow::getLocalHostIP()
{QList<QHostAddress> AddressList = QNetworkInterface::allAddresses();QHostAddress result;foreach(QHostAddress address, AddressList){if(address.protocol() == QAbstractSocket::IPv4Protocol &&address != QHostAddress::Null &&address != QHostAddress::LocalHost){if (address.toString().contains("127.0.")){continue;}result = address;break;}}return result;
}void MainWindow::on_btn_download_clicked()
{if(!ScanServerCfg::CheckIsServerReady(m_cfg.strServerIp, m_cfg.nCmdPort)){QMessageBox::warning(this, "忙碌!", "服务器忙碌,请稍后再试!");return;}if (!bLocalServer){QMessageBox::critical(this, "失败!", "本地用于下载的服务启动失败!");return;}if (!ScanServerCfg::StartDownload(m_cfg.strServerIp , MainWindow::getLocalHostIP().toString(), m_cfg.nCmdPort, m_cfg.nTransPort, m_cfg.strShareRoot)){QMessageBox::critical(this, "错误", "下载出错!");return;}
}
总的来说,虽然功能容易实现,但是细节要注意的还是挺多的,最后基本功能实现了,但是要使得小工具好用肯定还得有一些细节需要完善的!
欢迎交流与讨论!