Netty文件上传断点续传的演示
一、理论和协议规范和工具类等
1、实现原理:
netty文件上传采用自定义的协议方式实现,断点续传主要是依据RandomAccessFile类的随机读写能力,主要流程是客户端发起请求,将需要上传文件名称、路径、读取文件的数据、以及读取文件的起始位置等等信息,并且缓存在服务端中(以文件路径为key,自定义协议对象为value),服务端拿到客户端发送的上述数据,就会写文件,并且写完文件,也会记录写过数据位置等信息,再次发送信息给客户端下一次需要读取的数据。
假如这个过程中,客户端断开了链接,此时由于服务端缓存了已经写过文件的位置,那么只会从写过文件的位置进行读文件,再传文件给服务端写等循环操作,从而达到了断点续茶传的效果。
2、RandomAccessFile的基本使用
* RandomAccessFile的基本api
* .getFilePointer : 获取当前操作的位置
* .seek(index): 将操作位置设置到index
* .read(byte):读文件到byte数组中,返回读取文件的长度
*
* 构造函数
* rf = new RandomAccessFile(new File(filePath),"r"); // 参数2是模式
*
* 模式详解:
* “r” 以只读方式来打开指定文件夹。如果试图对该RandomAccessFile执行写入方法,都将抛出IOException异常。
* “rw” 以读,写方式打开指定文件。如果该文件尚不存在,则试图创建该文件。
* “rws” 以读,写方式打开指定文件。相对于”rw” 模式,还要求对文件内容或元数据的每个更新都同步写入到底层设备。
* “rwd” 以读,写方式打开指定文件。相对于”rw” 模式,还要求对文件内容每个更新都同步写入到底层设备。
* 测试1:下面演示基本的读文件操作。
*/
@Test
public void test1() throws IOException {
String filePath = "src/d00_file/a.txt";
RandomAccessFile rf = null;
try {
rf = new RandomAccessFile(new File(filePath), "r");
System.out.println("输入内容:" + rf.getFilePointer());
// 从10位置操作
rf.seek(10);
byte[] b = new byte[1024];
int len = 0;
// 循环读写 (len是读取的长度)
while ((len = rf.read(b)) > 0) {
System.out.print(new String(b, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
rf.close();
}
}
/**
* 测试2: 向文件追加内容
*/
@Test
public void test2() throws IOException{
String filePath="src/d00_file/a.txt";
RandomAccessFile rf=null;
try {
rf = new RandomAccessFile(new File(filePath), "rw");
// 将操作指针移动到文件的最后
rf.seek(rf.length());
// 向文件写出数据
rf.write("这是追加的内容。。".getBytes());
}catch (IOException e){
e.printStackTrace();
}finally {
rf.close();
}
}
/**
* 测试3:修改文件内容
*/
@Test
public void test3() {
RandomAccessFile rf = null;
String oldStr = "www.www.www";
String newStr = "hahahaha";
try {
rf = new RandomAccessFile("src/d00_file/a.txt", "rw");
String line = null;
// 记录上次操作点
long lastpoint = 0;
while ((line = rf.readLine()) != null) {
long ponit = rf.getFilePointer();
// 如果包含替换字符串
if (line.contains(oldStr)) {
// 替换字符串
String str = line.replace(oldStr, newStr);
// 恢复到上次读取位置 (readLine前的位置)
rf.seek(lastpoint);
// 重写行数据
if (oldStr.length() != newStr.length()) {
byte[] newb = Arrays.copyOf(str.getBytes(), line.getBytes().length);
rf.write(newb);
// lastpoint = lastpoint + newb.length;
lastpoint = rf.getFilePointer();
continue;
} else {
rf.write(str.getBytes());
}
}
lastpoint = ponit;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
rf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、传输对象和协议对象设计
传输对象分为三种,一种是客户端发送给服务端的起始传输信息类、一种是分片文件数据类、一种是服务端返回给客户端分片文件指令类。
协议对象就是包含二个字段,一个是类型(上面三个对象的类型),一个是obejct字段,表示传输对象。
常量:
/**
* 文件传输常量
*/
public class Constants {
/**
* 文件传输状态的标示
*/
public static class FileStatus{
public static int BEGIN = 0; //开始
public static int CENTER = 1; //中间
public static int END = 2; //结尾
public static int COMPLETE = 3; //完成
}
/**
* 协议对象的传输对象类型
*/
public static class TransferType{
public static int REQUEST = 0; //文件信息类型
public static int INSTRUCT = 1; //文件指令类型
public static int DATA = 2; //文件分片类型
}
}
协议对象:
public class FileTransferProtocol {
private Integer transferType; // 类型
private Object transferObj; // 数据对象;(0)FileDescInfo、(1)FileBurstInstruct、(2)FileBurstData
public Integer getTransferType() {
return transferType;
}
public void setTransferType(Integer transferType) {
this.transferType = transferType;
}
public Object getTransferObj() {
return transferObj;
}
public void setTransferObj(Object transferObj) {
this.transferObj = transferObj;
}
}
文件传输相关对象:
/