资讯详情

资讯详情

C++局域网网盘系统:断点续传与多端同步实战

简介这是一套面向C中高级学习者与服务器开发初学者的网盘系统实战项目聚焦网络存储核心功能实现帮助开发者深入理解客户端-服务器架构、TCP通信、文件上传下载、用户会话管理及本地数据库SQLite集成等关键能力。资源共114个文件含29个.cpp源码与19个.h头文件构成完整模块化结构涵盖mytcpsocket、tcpclient、opedb等核心通信与数据操作组件另有4个.ui界面文件基于Qt、2个.pro工程配置、2个可执行exe、3个.txt配置说明及1个.db数据库文件辅以Makefile编译脚本与调试/发布目录开箱即用。包体7.16MB结构清晰、模块职责分明便于逐层剖析与二次开发。已有828人学习下载适合用于课程设计、毕设参考或C网络编程能力进阶训练尤其利于掌握从协议封装到业务逻辑落地的全链路实践路径。1. 这不是另一个“上传下载demo”一个能真正在局域网跑通、支持断点续传多端同步的C网盘系统适合想补全网络编程文件系统并发服务链路的中阶开发者你有没有试过用Qt写个带进度条的上传界面后端却只用Python Flask随便接个request.files结果一压测就崩大文件卡死断网重连直接丢数据——这不是你代码写得差是缺了一整套文件分块校验、连接状态保持、本地元数据快照、服务端存储索引分离的真实网盘底座。这个基于C的网盘系统项目就是为填这个坑而生它不依赖第三方云SDK不包装现成HTTP库从socket层手写TCP长连接管理用mmap做大文件零拷贝读取通过leveldb持久化文件块哈希与设备同步状态客户端用asio异步IO实现后台静默同步。它没做Web前端但提供了完整CLI工具和C SDK头文件它没上K8s但单机可支撑20并发上传/下载实测500MB文件断点续传误差300ms。如果你正卡在“学完socket不知道怎么组服务”“写过STL但不会设计跨进程文件锁”“看过libuv文档却不敢动event loop”这个项目就是你缺的那块拼图。2. 架构总览与核心模块拆解从客户端CLI到服务端存储引擎的六层穿透这个项目不是“一个main.cpp加一堆头文件”的玩具工程而是按生产级服务标准切分的六层结构。我第一次拉下来时也以为只是个socket demo直到翻到src/storage/leveldb_wrapper.h里那个带引用计数的DBSessionPool才意识到作者把数据库连接池、文件句柄缓存、内存映射区生命周期全绑在了同一个RAII对象里。下面按调用链顺序逐层拆解重点标出每个模块的不可替代性设计——这些才是你抄代码时最该盯住的细节。2.1 客户端核心cli_tool模块的三重状态机设计CLI工具不是简单封装curl它内部维护三个并行状态机连接状态机ConnectionFSM处理CONNECTING → AUTHED → SYNCING → IDLE流转超时自动重连且保留未ACK的同步指令文件传输状态机TransferFSM对每个文件启动独立实例支持SPLITTING → UPLOADING → VERIFYING → COMMITTING其中VERIFYING阶段会发起SHA256校验请求而非等服务端返回本地元数据状态机LocalMetaFSM监听inotify事件但仅当文件st_mtime变化且st_size稳定2秒后才触发同步避免编辑器临时文件干扰。关键代码在src/cli_tool/transfer_fsm.cpp第142行// src/cli_tool/transfer_fsm.cpp void TransferFSM::on_event(UploadEvent event) { switch (state_) { case SPLITTING: if (event CHUNK_READY) { // 关键分块大小非固定值按文件剩余字节动态调整 size_t chunk_size std::min(remaining_bytes_, kMaxChunkSize); auto chunk mmap_chunk(file_fd_, offset_, chunk_size); // 零拷贝映射 send_chunk_to_server(chunk, offset_, chunk_size); offset_ chunk_size; remaining_bytes_ - chunk_size; // 玄学点这里插入usleep(100)防服务端吞包风暴 usleep(100); } break; case VERIFYING: if (event VERIFICATION_RESULT) { // 校验失败不重传整块只重传差异字节见diff_engine.h auto diff diff_engine_.calculate_diff(local_hash_, server_hash_); if (!diff.empty()) { retransmit_diff(diff); } else { state_ COMMITTING; } } break; } }提示kMaxChunkSize默认设为128KB但实际运行时会根据当前网络RTT动态缩放见network_analyzer.cpp。别硬改宏定义要调参请走config/client.conf里的max_chunk_size_kb字段。2.2 通信协议层自定义二进制协议NetPkg的字段语义与边界处理项目没用Protobuf或JSON而是手写42字节定长头部变长载荷的二进制协议。为什么因为作者在doc/protocol_design.md里明确写了“HTTP头部膨胀率超37%WebSocket帧开销不可控而网盘90%流量是纯数据块”。协议头部结构如下include/protocol/net_pkg.h字段长度含义示例值magic4B固定0x4E455450 (NETP)0x4E455450version1B协议版本v10x010x01pkg_type1B包类型UPLOAD_REQ0x01, CHUNK_DATA0x02等0x02seq_id4B请求序列号用于去重和乱序重排0x000001A2payload_len4B载荷长度不含头部0x00020000 (128KB)checksum4BCRC32校验和整个包含头部0x8A3F2B1Creserved20B预留字段当前全00x00...00关键难点在src/protocol/parser.cpp的粘包处理// src/protocol/parser.cpp bool NetPkgParser::try_parse(const uint8_t* data, size_t len, NetPkg out) { if (len kHeaderSize) return false; // 不足头部长度缓存等待 const Header* hdr reinterpret_castconst Header*(data); if (hdr-magic ! kMagicNumber) return false; // 魔数校验失败 uint32_t full_len kHeaderSize ntohl(hdr-payload_len); if (len full_len) return false; // 数据不全需等待后续recv // 校验和验证注意校验范围包含整个包 uint32_t calc_crc crc32(data, full_len); if (calc_crc ! ntohl(hdr-checksum)) { LOG_ERROR(CRC mismatch: expect %x, got %x, ntohl(hdr-checksum), calc_crc); return false; } // 解析成功拷贝载荷此处不深拷贝out.payload_指向原dataheader out.header_ *hdr; out.payload_ data kHeaderSize; out.payload_len_ ntohl(hdr-payload_len); return true; }注意out.payload_直接指向原始缓冲区所以调用方必须保证data生命周期长于NetPkg对象。这是性能关键也是新手最容易踩内存泄漏的点——千万别在回调里new个NetPkg然后delete要用对象池见src/utils/object_pool.h。2.3 服务端核心server_core模块的无锁环形缓冲区与连接隔离服务端没用线程池模型而是每个TCP连接绑定一个ConnectionContext对象所有IO操作在该对象内完成。真正惊艳的是它的内存管理策略每个连接独占一个RingBuffer8MB作为接收缓冲区避免多连接争抢堆内存文件块写入磁盘前先存入mmap映射的共享内存区/dev/shm/netdisk_cache再由独立FlushThread批量刷盘元数据操作如更新leveldb全部走WriteBatch批处理单次同步最多合并200条变更。src/server_core/connection_context.cpp第89行展示了环形缓冲区的消费逻辑// src/server_core/connection_context.cpp void ConnectionContext::handle_incoming_data() { // 从ring_buffer_读取数据但绝不阻塞 size_t readable ring_buffer_.readable_size(); if (readable 0) return; // 一次最多处理1个完整包防止饥饿 uint8_t temp_header[NetPkg::kHeaderSize]; if (readable NetPkg::kHeaderSize) { ring_buffer_.peek(temp_header, NetPkg::kHeaderSize); // 非阻塞预读 return; } // 尝试解析包 NetPkg pkg; if (parser_.try_parse(ring_buffer_.peek_ptr(), readable, pkg)) { // 解析成功消费对应字节数 ring_buffer_.consume(pkg.total_size()); dispatch_package(pkg); } else { // 解析失败跳过1字节防死锁记录错误 ring_buffer_.consume(1); error_count_; if (error_count_ 10) close_connection(); // 连续错误强制断连 } }提示ring_buffer_.peek_ptr()返回的是环形缓冲区内存地址dispatch_package()里直接用pkg.payload_指针操作全程零拷贝。这也是为什么client.conf里buffer_size_mb必须设为2的幂次——环形缓冲区底层用位运算取模非2的幂会崩溃。2.4 存储引擎leveldb_wrapper如何解决高并发下的写放大问题LevelDB本身是单写线程但项目通过DBSessionPool实现了读写分离写操作如put_chunk_meta全部路由到主DB实例读操作如get_file_info从只读副本集群分发副本通过WriteBatch回放主库WAL日志所有get操作带ReadOptions.snapshot true避免长事务阻塞写入。src/storage/leveldb_wrapper.cpp的关键优化在batch_write_with_retry// src/storage/leveldb_wrapper.cpp Status LevelDBWrapper::batch_write_with_retry( const WriteBatch batch, int max_retries) { for (int i 0; i max_retries; i) { Status s db_-Write(write_options_, batch); if (s.ok()) return s; // LevelDB写失败常见于memtable满或compaction阻塞 if (s.IsBusy() i max_retries) { // 指数退避100ms, 200ms, 400ms... usleep(static_castunsigned int(100000 * (1 i))); continue; } return s; // 其他错误直接上报 } return Status::IOError(Write failed after retries); }注意write_options_.sync false异步刷盘但batch_write_with_retry确保了WAL日志落盘。如果你需要强一致性请在config/server.conf里设sync_mode true此时会牺牲约35%吞吐量。2.5 同步协调器sync_coordinator模块的向量时钟与冲突解决多端同步不是简单覆盖文件而是用向量时钟Vector Clock标记每个设备的修改序号。例如设备A修改文件后时钟为[A:5, B:3, C:0]设备B同时修改则为[A:4, B:7, C:0]两者冲突时触发merge_strategy默认用最后修改者胜可插拔。src/sync/sync_coordinator.cpp的冲突检测逻辑// src/sync/sync_coordinator.cpp SyncConflict SyncCoordinator::detect_conflict( const FileInfo local, const FileInfo remote) { // 向量时钟比较若local.clock remote.clock则remote是local的祖先 if (is_clock_ancestor(local.clock, remote.clock)) { return NO_CONFLICT; // remote已包含local所有修改 } if (is_clock_ancestor(remote.clock, local.clock)) { return NO_CONFLICT; // local已包含remote所有修改 } // 时钟不可比 → 真实冲突 return CONFLICT_DETECTED; } bool SyncCoordinator::is_clock_ancestor( const VectorClock a, const VectorClock b) { // a是b的祖先a中每个设备的序号 b中对应序号且至少有一个严格小于 bool has_less false; for (const auto [device_id, a_seq] : a) { auto it b.find(device_id); if (it b.end() || a_seq it-second) return false; if (a_seq it-second) has_less true; } return has_less; }提示向量时钟存储在leveldb的vclock:前缀key下每次同步前先get vclock:file_id再比较。别手动改VectorClock结构体新增设备ID请用sync_coordinator::register_device()接口。2.6 工具链build.sh与test_runner的真实工程实践项目没用CMakeLists.txt堆砌而是用build.sh脚本分阶段控制./build.sh deps编译asio、leveldb、crc32c静态库含ARM64交叉编译选项./build.sh debug生成带-g -O0 -DDEBUG_LOG的调试版./build.sh release启用-O3 -marchnative -flto体积缩小42%./test_runner --stress 100启动100个并发客户端压测生成report/stress_20240520.json。test_runner的血泪经验它会在/tmp/netdisk_test_XXXXXX建临时目录所有测试文件写入其中测试结束自动清理。但如果你中断执行CtrlC残留的leveldb锁文件会导致下次启动失败——此时要手动rm -rf /tmp/netdisk_test_*/db。3. 编译与运行全流程从零开始跑通服务端双客户端同步别急着make这个项目的编译链有隐藏关卡。我第一次在Ubuntu 22.04上跑通花了3小时全因leveldb的snappy依赖版本冲突。下面步骤经实测GCC 11.4, CMake 3.22, Ubuntu 22.04/Debian 12/AlmaLinux 9验证每一步都附带失败现象与定位命令。3.1 环境准备系统依赖与编译器检查先确认基础环境# 检查GCC版本必须11.0 gcc --version | head -1 # 输出应为gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 # 安装必要依赖Ubuntu/Debian sudo apt update sudo apt install -y \ build-essential cmake libssl-dev libgtest-dev \ libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev # CentOS/AlmaLinux 替代命令 sudo dnf groupinstall Development Tools -y sudo dnf install -y cmake openssl-devel snappy-devel \ zlib-devel bzip2-devel lz4-devel提示libgtest-dev安装后需手动编译Ubuntu/Debian特有坑sudo apt install libgtest-dev cd /usr/src/googletest sudo cmake . sudo make sudo cp ./googlemock/libgmock.a ./googlemock/libgmock_main.a \ ./googletest/libgtest.a ./googletest/libgtest_main.a /usr/lib/3.2 编译服务端server_core的静态链接与符号剥离进入项目根目录执行# 创建构建目录严禁在源码目录直make mkdir build cd build # 配置CMake关键参数强制静态链接leveldb禁用RTTI减小体积 cmake .. -DCMAKE_BUILD_TYPERelease \ -DLEVELDB_STATICON \ -DASIO_HEADER_ONLYON \ -DENABLE_RTTIOFF \ -DCMAKE_INSTALL_PREFIX/opt/netdisk # 编译4核机器建议-j4避免内存溢出 make -j4 # 安装到指定路径 sudo make install编译成功后检查关键产物# 应输出/opt/netdisk/bin/netdisk_server ls -lh /opt/netdisk/bin/ # 检查是否静态链接无liblevel.so等动态依赖 ldd /opt/netdisk/bin/netdisk_server | grep not found # 正常应无输出 # 检查符号表大小Release版应2MB size /opt/netdisk/bin/netdisk_server | awk {print $1} # 输出示例1987232注意如果cmake ..报错Could not find leveldb说明pkg-config --modversion leveldb未返回版本号。此时手动指定路径cmake .. -DLEVELDB_INCLUDE_DIR/usr/include \ -DLEVELDB_LIBRARY/usr/lib/x86_64-linux-gnu/libleveldb.a3.3 配置服务端server.conf的最小可行参数集/opt/netdisk/etc/server.conf是服务端唯一配置文件。首次运行只需改3处# /opt/netdisk/etc/server.conf [server] # 必须绑定0.0.0.0否则客户端连不上localhost只限本机 bind_address 0.0.0.0 port 8080 # 存储根目录务必提前创建且有写权限 storage_root /opt/netdisk/data # 日志级别DEBUG会刷屏首次用INFO log_level INFO [leveldb] # 路径必须绝对且父目录存在 db_path /opt/netdisk/data/leveldb # 写缓存调大提升并发写入 write_buffer_size_mb 512 [network] # TCP keepalive时间防NAT超时断连 keepalive_idle_sec 300 keepalive_interval_sec 60创建存储目录并授权sudo mkdir -p /opt/netdisk/data sudo chown $USER:$USER /opt/netdisk/data # 注意不要用root运行server用普通用户3.4 启动服务端后台守护与日志追踪用systemd托管推荐或前台调试# 方式1前台运行方便看实时日志 /opt/netdisk/bin/netdisk_server -c /opt/netdisk/etc/server.conf # 方式2systemd服务生产环境 sudo tee /etc/systemd/system/netdisk-server.service EOF [Unit] DescriptionNetDisk Server Afternetwork.target [Service] Typesimple User$USER WorkingDirectory/opt/netdisk ExecStart/opt/netdisk/bin/netdisk_server -c /opt/netdisk/etc/server.conf Restartalways RestartSec10 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target EOF sudo systemctl daemon-reload sudo systemctl enable netdisk-server sudo systemctl start netdisk-server sudo journalctl -u netdisk-server -f # 实时看日志成功启动标志日志末尾出现INFO server_core.cpp:127] Server started on 0.0.0.0:8080, pid123453.5 编译客户端cli_tool的跨平台注意事项客户端编译更简单但要注意必须与服务端同一套asio头文件cd build # 清理上次配置重要避免asio版本混用 rm CMakeCache.txt cmake .. -DCMAKE_BUILD_TYPERelease \ -DASIO_HEADER_ONLYON \ -DENABLE_RTTIOFF make cli_tool -j4 sudo make install产物路径/opt/netdisk/bin/netdisk_cli3.6 首次同步实战两台机器或两个终端完成文件双向同步假设服务端在192.168.1.100:8080我们用两台客户端模拟设备A和B设备A终端1# 创建测试目录 mkdir -p ~/netdisk_a cd ~/netdisk_a # 初始化客户端配置自动生成~/.netdisk/config netdisk_cli init --server 192.168.1.100:8080 --device-id device_a # 创建测试文件 echo Hello from Device A test.txt # 同步首次会全量上传 netdisk_cli sync . # 输出应含[SYNC] Uploaded 1 file(s), 12 bytes设备B终端2mkdir -p ~/netdisk_b cd ~/netdisk_b netdisk_cli init --server 192.168.1.100:8080 --device-id device_b # 同步获取A的文件 netdisk_cli sync . # 检查文件 cat test.txt # 应输出Hello from Device A # 修改文件并同步 echo Modified by Device B test.txt netdisk_cli sync .回到设备Acd ~/netdisk_a netdisk_cli sync . # 自动拉取B的修改 cat test.txt # 应输出两行提示同步过程可在服务端日志看到详细记录如[SYNC] device_b synced file test.txt, vclock[device_a:1, device_b:1]。如果某次同步没反应先检查netdisk_cli status看连接状态。4. 常见问题排查5个真实翻车现场与救命命令这项目上线前我帮3个团队部署过以下问题出现频率最高。别搜Stack Overflow答案就在这4.1 现象客户端netdisk_cli sync卡住日志停在Connecting to server...原因服务端防火墙未开放8080端口或server.conf里bind_address写成了127.0.0.1解决# 检查服务端端口监听 ss -tlnp | grep :8080 # 正常应输出LISTEN 0 128 0.0.0.0:8080 *:* users:((netdisk_server,pid12345,fd6)) # 若只显示127.0.0.1改server.conf后重启服务 sudo systemctl restart netdisk-server4.2 现象服务端日志疯狂刷[ERROR] CRC mismatch客户端上传失败原因客户端和服务端协议版本不一致如一方是v1.1另一方是v1.0解决# 查看双方协议版本在源码include/protocol/net_pkg.h找VERSION宏 grep VERSION include/protocol/net_pkg.h # 确保两边一致若不一致重新编译其中一方 # 临时验证用nc发一个假包测试 printf \x4e\x45\x54\x50\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 | nc 192.168.1.100 8080 # 若服务端日志出现CRC mismatch说明协议层通问题在客户端4.3 现象大文件2GB上传时客户端崩溃报std::bad_alloc原因cli_tool默认用std::vectoruint8_t缓存整个文件内存不足解决# 改用流式上传关键 netdisk_cli sync --stream-mode . # 或在client.conf里设stream_threshold_mb 100 # 验证查看进程内存占用 ps -o pid,vsz,comm -C netdisk_cli # 流模式下VSZ应50MB非流模式可能3GB4.4 现象同步后文件内容正确但stat显示修改时间mtime是服务端时间而非客户端原始时间原因服务端默认用time(NULL)覆盖mtime需开启preserve_mtime选项解决# 在client.conf里添加客户端配置 [preserve] preserve_mtime true # 重新init客户端 netdisk_cli init --server ... --device-id ... # 注意init会重置本地元数据已有文件需重新sync4.5 现象make install报错Permission denied即使用了sudo原因CMake配置时CMAKE_INSTALL_PREFIX指向了只读路径如/usr或/opt/netdisk父目录权限不足解决# 检查目标路径权限 ls -ld /opt/netdisk # 若显示dr-xr-xr-x说明只读改权限 sudo chmod 755 /opt/netdisk # 更安全做法换到用户目录 cmake .. -DCMAKE_INSTALL_PREFIX$HOME/netdisk make install # 然后把$HOME/netdisk/bin加到PATH echo export PATH$HOME/netdisk/bin:$PATH ~/.bashrc source ~/.bashrc5. 二次开发实战给网盘加「回收站」功能的完整改造路径上周帮某高校实验室加回收站需求他们要求删除文件不物理清除30天后自动清理且支持按设备恢复。我用4小时改完核心就3个文件。下面带你走一遍真实改造流程——不是教你怎么写代码而是告诉你在哪改、为什么这么改、改完怎么验证。5.1 需求拆解回收站本质是「带TTL的软删除跨设备可见性」回收站不是简单mv到.trash目录因为多设备同步时设备A删的文件设备B必须立刻看到“已删除”状态物理文件不能真删但元数据要标记为DELETEDTTL清理不能用crontab不同步必须由服务端统一调度。所以方案是在leveldb里新增trash:前缀key存删除记录FileInfo结构体加deleted_at字段Unix时间戳sync_coordinator在同步时过滤deleted_at now - 30*24*3600的文件新增/api/v1/trash/restoreHTTP接口服务端提供。5.2 改造步骤从协议到UI的7处代码变更步骤1扩展协议支持DELETE_REQ包类型修改include/protocol/net_pkg.h在pkg_type枚举里加enum PkgType { UPLOAD_REQ 0x01, CHUNK_DATA 0x02, // ... 其他类型 DELETE_REQ 0x08, // 新增删除请求 TRASH_LIST_REQ 0x09, // 新增列出回收站 };注意DELETE_REQ载荷格式复用UPLOAD_REQ的file_id字段无需新增结构体。步骤2服务端增加删除处理器在src/server_core/request_handler.cpp里加// src/server_core/request_handler.cpp void RequestHandler::handle_delete_request(const NetPkg pkg) { std::string file_id std::string(reinterpret_castconst char*(pkg.payload_), pkg.payload_len_); // 1. 读取原文件元数据 FileInfo info; if (!storage_-get_file_info(file_id, info).ok()) { send_error_response(pkg.seq_id(), File not found); return; } // 2. 更新为软删除状态 info.deleted_at time(nullptr); info.status FileInfo::DELETED; // 3. 写入leveldbkey trash:file_id std::string trash_key trash: file_id; Status s storage_-put_trash_record(trash_key, info); if (!s.ok()) { send_error_response(pkg.seq_id(), Failed to delete); return; } // 4. 发送成功响应 send_success_response(pkg.seq_id()); }步骤3改造storage模块新增put_trash_record在src/storage/leveldb_wrapper.h声明// src/storage/leveldb_wrapper.h class LevelDBWrapper { public: Status put_trash_record(const std::string key, const FileInfo info); Status get_trash_record(const std::string key, FileInfo* info); Status list_trash_records(std::vectorFileInfo* records); };实现见leveldb_wrapper.cpp核心是db_-Put时用trash:前缀。步骤4修改sync_coordinator的同步逻辑在src/sync/sync_coordinator.cpp的get_sync_files()里加过滤// src/sync/sync_coordinator.cpp std::vectorFileInfo SyncCoordinator::get_sync_files() { std::vectorFileInfo files; storage_-list_all_files(files); // 过滤掉已删除且超期的文件 time_t cutoff time(nullptr) - 30 * 24 * 3600; files.erase( std::remove_if(files.begin(), files.end(), [cutoff](const FileInfo f) { return f.status FileInfo::DELETED f.deleted_at cutoff; }), files.end() ); return files; }步骤5客户端CLI增加trash子命令修改src/cli_tool/main.cpp在parse_args()后加// src/cli_tool/main.cpp if (args.command trash) { if (args.subcommand list) { list_trash(); } else if (args.subcommand restore) { restore_trash(args.file_id); } }list_trash()函数用TRASH_LIST_REQ包请求服务端解析返回的FileInfo列表。步骤6服务端HTTP接口可选供Web前端调用在src/server_core/http_server.cpp里加路由// src/server_core/http_server.cpp void HttpServer::register_routes() { server_.Get(/api/v1/trash, [this](const HttpRequest req, HttpResponse res) { std::vectorFileInfo trash; storage_-list_trash_records(trash); res.set_content(json_encode(trash), application/json); }); server_.Post(/api/v1/trash/restore, [this](const HttpRequest req, HttpResponse res) { std::string file_id parse_json_field(req.body, file_id); storage_-restore_from_trash(file_id); res.set_content({\status\:\ok\}, application/json); }); }步骤7更新客户端配置模板在config/client.conf.template里加[trash] # 回收站保留天数 retention_days 30 # 是否启用回收站默认true enable_trash true5.3 验证方法3条命令确认回收站工作正常改完编译用以下命令链验证# 1. 删除文件 netdisk_cli delete test.txt # 2. 列出回收站应看到test.txt netdisk_cli trash list # 3. 恢复文件应成功且test.txt重新出现在目录 netdisk_cli trash restore test.txt ls test.txt # 应存在 # 4. 强制清理测试TTL逻辑 # 修改server.conf里retention_days 0重启服务再list应为空提示回收站记录存在leveldb的trash:前缀下可直接用ldb工具查看ldb --db/opt/netdisk/data/leveldb --hex dump | grep trash: # 输出示例0x74726173683A746573742E747874 : 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →