C++ 课程设计里「实现一个分布式文件系统」这类题目,代码量通常在两千行以上,涉及 socket、多线程、协议设计。分数差别不在功能多少,而在并发安全与资源管理。

分层结构
本地文件系统层
把「目录 / 文件」抽象成统一的接口(open / read / write / seek / stat)。这一层的价值在于把操作系统差异隔离掉,上层不需要关心具体是 POSIX 还是别的实现。
网络传输层
基于 socket 建连,处理请求解析与响应封包。必须解决 TCP 粘包——因为 TCP 是字节流,两次 write 可能被一次 read 读到。标准做法是先发固定长度的长度前缀,再发消息体。
// 发送:先发 4 字节长度,再发消息体
uint32_t len = htonl(payload.size());
send(fd, &len, sizeof(len), 0);
send(fd, payload.data(), payload.size(), 0);
// 接收:先读长度,再按长度读满
uint32_t nlen = 0;
recv_all(fd, &nlen, sizeof(nlen));
size_t n = ntohl(nlen);
std::vector<char> body(n);
recv_all(fd, body.data(), n); // recv_all 需循环直到读满
漏掉长度前缀,或者 recv 没有循环读满,是这类项目最典型的 bug。
服务调度层
多客户端并发处理。两种主流方案:
- 线程池:固定数量的工作线程从队列取任务,适合 CPU 密集型处理。
- 每连接一线程:实现简单,但连接数增长后线程切换开销大。
无论哪种,共享数据结构(文件表、目录树)都必须加锁。评分脚本往往会并发压测,竞态问题一旦出现必然被抓到。
HTTP 接口层
把文件操作包装成 REST 风格接口:GET 读文件、PUT 写文件、DELETE 删除、GET 列目录。要正确处理状态码:200 成功、404 不存在、413 请求体过大、500 内部错误。
数据编码层
二进制文件直接传输即可,但若协议基于文本(如 HTTP 头),需要 Base64 编码。注意 Base64 会让体积膨胀约 33%,且解码时要处理换行与填充。
最容易失分的三类问题
1. 竞态条件
两个线程同时修改目录树而不加锁,表现为「偶尔」出现目录项丢失或程序崩溃。这类 bug 不会稳定复现,但并发测试一定能触发。
2. 文件描述符泄漏
每次请求打开文件却不关闭,长时间运行后 accept 返回 EMFILE。用 lsof -p PID | wc -l 观察 fd 数量是否持续增长即可确认。
3. 协议边界处理
空请求、超长请求、提前断开连接——这些边界情况如果没有处理,服务端会被单个异常客户端拖死。评分脚本常常专门测这些。
交付前自检清单
- 并发压测下无崩溃、无数据丢失。
- fd 数量稳定,不随时间增长。
- 客户端强制断开后服务端不崩溃。
- 编译无警告,Valgrind 无线程类错误。
- Makefile 可一键构建,README 说明启动方式。
