📁 项目四:文件传输、分片发送、断点续传
项目概述
项目名称:itstack-demo-netty-2-04
核心功能:实现大文件分片传输和断点续传功能
技术要点:RandomAccessFile、分片传输、断点续传、协议设计
为什么需要这个项目?
实际问题:
- 大文件一次性传输容易导致内存溢出
- 网络不稳定时传输中断,需要重新开始
- 无法显示传输进度,用户体验差
- 文件传输失败后浪费时间和流量
解决方案:
- ✅ 分片传输:每次传输100KB,避免内存溢出
- ✅ 断点续传:记录传输位置,中断后继续传输
- ✅ 进度监控:实时显示传输进度
- ✅ RandomAccessFile:支持随机读写,实现断点续传
适用场景:
- 网盘系统(百度网盘、阿里云盘)
- 大文件上传下载(视频、软件安装包)
- 视频流媒体传输
- 分布式文件系统(HDFS、FastDFS)
核心知识点
1. 系统架构设计
传输协议设计:
FileTransferProtocol(文件传输协议)
├── transferType(传输类型)
│ ├── 0 - REQUEST(请求传输文件)
│ ├── 1 - INSTRUCT(文件传输指令)
│ └── 2 - DATA(文件传输数据)
└── transferObj(传输对象)
├── FileDescInfo(文件描述信息)- type=0
├── FileBurstInstruct(文件分片指令)- type=1
└── FileBurstData(文件分片数据)- type=2
2. 核心领域对象
FileDescInfo.java - 文件描述信息
public class FileDescInfo {
private String fileUrl; // 文件路径
private String fileName; // 文件名
private Long fileSize; // 文件大小
}
FileBurstInstruct.java - 文件分片指令
public class FileBurstInstruct {
private Integer status; // 0开始、1中间、2结尾、3完成
private String clientFileUrl; // 客户端文件URL
private Integer readPosition; // 读取位置(断点续传的关键)
}
FileBurstData.java - 文件分片数据
public class FileBurstData {
private String fileUrl; // 客户端文件地址
private String fileName; // 文件名称
private Integer beginPos; // 开始位置
private Integer endPos; // 结束位置
private byte[] bytes; // 文件字节数据(100KB)
private Integer status; // 0开始、1中间、2结尾、3完成
}
3. RandomAccessFile核心技术
什么是RandomAccessFile?
RandomAccessFile是Java提供的支持随机访问文件的类,可以从文件的任意位置读写数据。
对比:
普通文件读取:像看书,只能从第一页顺序往后翻
RandomAccessFile:像电子书,可以直接跳到任意页码
分片读取文件:
public static FileBurstData readFile(String fileUrl, Integer readPosition)
throws IOException {
File file = new File(fileUrl);
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
// 1. 定位到指定位置(断点续传的关键)
randomAccessFile.seek(readPosition);
// 2. 读取100KB数据
byte[] bytes = new byte[1024 * 100]; // 100KB缓冲区
int readSize = randomAccessFile.read(bytes);
// 3. 读取完成
if (readSize <= 0) {
randomAccessFile.close();
return new FileBurstData(Constants.FileStatus.COMPLETE);
}
// 4. 构建数据对象
FileBurstData fileInfo = new FileBurstData();
fileInfo.setBeginPos(readPosition);
fileInfo.setEndPos(readPosition + readSize);
// 5. 处理最后一块数据(去掉空字节)
if (readSize < 1024 * 100) {
byte[] copy = new byte[readSize];
System.arraycopy(bytes, 0, copy, 0, readSize);
fileInfo.setBytes(copy);
fileInfo.setStatus(Constants.FileStatus.END);
} else {
fileInfo.setBytes(bytes);
fileInfo.setStatus(Constants.FileStatus.CENTER);
}
randomAccessFile.close();
return fileInfo;
}
为什么要去掉空字节?
假设文件总大小120KB:
- 第1次读取:0-100KB(完整100KB)✅
- 第2次读取:100-120KB(只有20KB)⚠️
如果不去掉空字节:
byte[100KB] = [20KB实际数据 + 80KB空字节]
写入文件时会多出80KB垃圾数据,导致文件损坏!❌
分片写入文件:
public static FileBurstInstruct writeFile(String baseUrl,
FileBurstData fileBurstData)
throws IOException {
File file = new File(baseUrl + "/" + fileBurstData.getFileName());
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
// 1. 定位到指定位置写入
randomAccessFile.seek(fileBurstData.getBeginPos());
randomAccessFile.write(fileBurstData.getBytes());
randomAccessFile.close();
// 2. 检查是否是最后一块
if (Constants.FileStatus.END == fileBurstData.getStatus()) {
return new FileBurstInstruct(Constants.FileStatus.COMPLETE);
}
// 3. 返回下一次读取指令
FileBurstInstruct fileBurstInstruct = new FileBurstInstruct();
fileBurstInstruct.setStatus(Constants.FileStatus.CENTER);
fileBurstInstruct.setClientFileUrl(fileBurstData.getFileUrl());
fileBurstInstruct.setReadPosition(fileBurstData.getEndPos() + 1);
return fileBurstInstruct;
}
4. 完整传输流程
正常传输流程:
【客户端】 【服务端】
| |
| 1. 发送文件请求 |
| (REQUEST + FileDescInfo) |
|--------------------------------->|
| | 2. 返回传输指令
| 3. 接收指令 | (INSTRUCT + readPosition=0)
|<---------------------------------|
| 4. 读取文件(0-100KB) |
| 5. 发送数据 |
| (DATA + FileBurstData) |
|--------------------------------->|
| | 6. 写入文件
| | 7. 保存断点信息
| | 8. 返回下一次指令
| 9. 接收指令 | (INSTRUCT + readPosition=100KB)
|<---------------------------------|
| ... 循环传输 ... |
断点续传流程:
【客户端】 【服务端】
| |
| 1. 发送文件请求 |
| (REQUEST + FileDescInfo) |
|--------------------------------->|
| | 2. 检查断点信息
| | 发现上次传到200KB
| | 3. 返回断点续传指令
| 4. 接收指令 | (INSTRUCT + readPosition=200KB)
|<---------------------------------|
| 5. 从200KB位置继续读取 |
| ... 继续传输 ... |
5. 断点续传实现
服务端保存断点信息:
// 缓存断点信息(实际应用中应保存到数据库)
public class CacheUtil {
public static Map<String, FileBurstInstruct> burstDataMap =
new ConcurrentHashMap<>();
}
// 服务端Handler
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
FileTransferProtocol protocol = (FileTransferProtocol) msg;
switch (protocol.getTransferType()) {
case 0: // REQUEST - 请求传输文件
FileDescInfo fileDescInfo = (FileDescInfo) protocol.getTransferObj();
// 检查是否有断点续传信息
FileBurstInstruct oldInstruct =
CacheUtil.burstDataMap.get(fileDescInfo.getFileName());
if (oldInstruct != null) {
// 断点续传
ctx.writeAndFlush(MsgUtil.buildTransferInstruct(oldInstruct));
return;
}
// 新传输,从0开始
ctx.writeAndFlush(MsgUtil.buildTransferInstruct(
Constants.FileStatus.BEGIN, fileDescInfo.getFileUrl(), 0
));
break;
case 2: // DATA - 接收文件数据
FileBurstData fileBurstData = (FileBurstData) protocol.getTransferObj();
// 写入文件
FileBurstInstruct instruct = FileUtil.writeFile("E://", fileBurstData);
// 保存断点信息(关键!)
CacheUtil.burstDataMap.put(fileBurstData.getFileName(), instruct);
// 返回下一次指令
ctx.writeAndFlush(MsgUtil.buildTransferInstruct(instruct));
break;
}
}
项目总结
核心技术:
- ✅ RandomAccessFile:支持随机读写
- ✅ 分片传输:每次传输100KB
- ✅ 断点续传:保存传输进度
- ✅ 空字节处理:避免文件损坏
适用场景:
- 网盘系统(百度网盘、阿里云盘)
- 大文件上传下载
- 视频流媒体传输
- 分布式文件系统
2642



被折叠的 条评论
为什么被折叠?



