目录
模块1
传输中资源的表示
在我们要下载某一个东西时,下载的大多是以文件夹的形式来进行保存的,且一种资源的文件夹中的各个资源名称均不会发生改变,所以从这一方面去考虑的话,表示资源的类中应该有相对路径以及绝对路径的表示,比如入下图
一个普通的资源可以如图所示:其结构如此,我们可以将其以相对路径和文件名称表示文件的个资源,比如此时我们可以以这样的方式表示:
其中里面有三个资源分别以filepath来表示其资源的相对路径,而每一个资源给予其所对应的ID来表示资源中的最小粒度,其每个资源的大小和内容可以用一个类来封装。目录的绝对绝对路径test可以根据个人的意愿进行改变。
ResourceBaseInfo 类
资源表示类
/**
*
* <ol>
* 功能:资源的表示类
* <li>要区分一个资源的名称name以及标识符id</li>
* <li>绝对路径absoluteRoot可以由请求端自行设置</li>
* <li>版本version用来区分资源版本</li>
* <li>rsiList为相对路径的封装</li>
* <li>fsiList为传输中单个资源封装类</li>
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class ResourceBaseInfo {
//默认存储的表示资源文件的位置
private static final String RESOURCE_XML = "F:\\.mecResource\\resource.xml";
//将资源表示写入默认路径中的工具
private XMLEditor editor;
private String name;
private int id;
private String absoluteRoot;
private long version;
private List<ResourceStructInfo> rsiList;
private List<FileSectionInfo> fsiList;
//资源大小
private long totalSize;
public ResourceBaseInfo() {
}
public ResourceBaseInfo(ResourceBaseInfo rbi) {
this.name = rbi.name;
this.id = rbi.id;
this.version = rbi.version;
this.absoluteRoot = rbi.absoluteRoot;
this.fsiList = rbi.fsiList;
this.rsiList = rbi.rsiList;
}
public void saveResource(){
if (editor == null) {
createXmlEditor();
}
List<ResourceStructInfo> tmprsiList = rsiList;
List<FileSectionInfo> tmpsiList = fsiList;
rsiList = null;
fsiList = null;
editor.insert(RESOURCE_XML, this);
rsiList = tmprsiList;
fsiList = tmpsiList;
}
private void createXmlEditor() {
try {
editor = new XMLEditor();
} catch (TransformerFactoryConfigurationError e) {
e.printStackTrace();
}
}
public String getName() {
return name;
}
public ResourceBaseInfo setName(String name) {
this.name = name;
return this;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
public int getId() {
return id;
}
public ResourceBaseInfo setId(int id) {
this.id = id;
return this;
}
public String getAbsoluteRoot() {
return absoluteRoot;
}
public ResourceBaseInfo setAbsoluteRoot(String absoluteRoot) {
this.absoluteRoot = absoluteRoot;
return this;
}
public long getVersion() {
return version;
}
public ResourceBaseInfo setVersion(long version) {
this.version = version;
return this;
}
public List<ResourceStructInfo> getRsiList() {
return rsiList;
}
public ResourceBaseInfo setRsiList(List<ResourceStructInfo> rsiList) {
this.rsiList = rsiList;
return this;
}
public List<FileSectionInfo> getFsiList() {
return fsiList;
}
public void setFsiList(List<FileSectionInfo> fsiList) {
this.fsiList = fsiList;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + (int)(version ^ (version >>> 32));
return result;
}
public void exploreResource() {
exploreResource(null);
}
//扫描资源路径中的资源形成资源基本信息
public void exploreResource(String root) {
root = (root == null ? this.absoluteRoot : root);
File file = new File(root);
//扫描并构成资源结构信息。
rsiList = new ArrayList<ResourceStructInfo>();
scanResourceRoot(rsiList, root, file, 1);
}
private int scanResourceRoot(List<ResourceStructInfo> rsiList, String absolutePath, File file, int fristFilehandle){
if (file.isFile()) {
return createResourceStructInfo(rsiList, absolutePath, file, fristFilehandle);
}
File[] fileList = file.listFiles();
for (File f : fileList) {
if (f.isDirectory()) {
fristFilehandle = scanResourceRoot(rsiList, absolutePath, f, fristFilehandle);
} else {
fristFilehandle = createResourceStructInfo(rsiList, absolutePath, f, fristFilehandle);
}
}
return fristFilehandle;
}
private int createResourceStructInfo(List<ResourceStructInfo> rsiList, String curfileName, File curFile, int fileHandle) {
ResourceStructInfo rsi = new ResourceStructInfo();
rsi.setFileHandle(fileHandle);
rsi.setFilePath(curFile.getAbsolutePath().replace(curfileName, ""));
long size = curFile.length();
rsi.setFsize(size);
rsiList.add(rsi);
if (fsiList == null) {
fsiList = new ArrayList<FileSectionInfo>();
}
FileSectionInfo fileSectionInfo = new FileSectionInfo();
fileSectionInfo.setFileHandle(fileHandle);
fileSectionInfo.setOffset(0);
fileSectionInfo.setSize((int)size);
fsiList.add(fileSectionInfo);
fileHandle++;
return fileHandle;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ResourceBaseInfo other = (ResourceBaseInfo)obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (version != other.version)
return false;
return true;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer();
result.append("AppName=").append(name).append('\n')
.append("id=").append(id).append('\n')
.append("version=").append(version).append('\n')
.append("absoluteRoot=").append(absoluteRoot);
if (rsiList != null) {
result.append("\nStruct-List:");
for (ResourceStructInfo rsi : rsiList) {
result.append("\n\t").append(rsi);
}
}
if (fsiList != null) {
result.append("\nRequest-List:");
for (FileSectionInfo si : fsiList) {
result.append("\n\t").append(si);
}
}
return result.toString();
}
}
FileSection
/**
*
* <ol>
* 功能:文件片段类
* <li>fileSectionInfo单个资源的片段表示</li>
* <li>value:对于的值的表示</li>
* <li>receiveAndSend:对片段接受和发送的工具</li>
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class FileSection {
private FileSectionInfo fileSectionInfo;
private byte[] value;
private IReceiveAndSend receiveAndSend;
public FileSection() {
receiveAndSend = new ReceiveAndSend();
}
public void setReceiveAndSend(IReceiveAndSend receiveAndSend) {
this.receiveAndSend = receiveAndSend;
}
public FileSectionInfo getFileSectionInfo() {
return fileSectionInfo;
}
public void setFileSectionInfo(FileSectionInfo fileSectionInfo) {
this.fileSectionInfo = fileSectionInfo;
}
public byte[] getValue() {
return value;
}
public void setValue(byte[] value) {
this.value = value;
}
/**
* a接收资源时使用16字节来将其封装发送单个资源片段的头和值
* @param dis
* @throws IOException
* @throws WrongHeadLenException
* @throws PeerDownException
*/
public void receiveFileSection(DataInputStream dis) throws IOException, WrongHeadLenException, PeerDownException {
byte[] headSection = receiveAndSend.receive(dis, FileSectionInfo.DEFAULT_HEAD_BYTE_LEN);
this.fileSectionInfo = new FileSectionInfo(headSection);
this.value = receiveAndSend.receive(dis, fileSectionInfo.getSize());
}
/**
* a发送资源时使用16字节来将其封装发送单个资源片段的头和值
* @param dos
* @throws IOException
*/
public void sendFileSection(DataOutputStream dos) throws IOException{
receiveAndSend.send(dos, fileSectionInfo.toBytes());
receiveAndSend.send(dos, value);
}
@Override
public String toString() {
return "FileSection [fileSectionInfo=" + fileSectionInfo + "]";
}
}
ResourceStructInfo
/**
*
* <ol>
* 功能:单个资源的相对路径结构类
* <li>fileHandle 单个资源的标识ID</li>
* <li>filePath 相对路径</li>
* <li>Fsize 单个资源的总长度</li>
* <li>checksum 单个资源的检验和来检测是否是正确的资源</li>
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class ResourceStructInfo {
private int fileHandle;
private String filePath;
private long Fsize;
private int checksum;
public ResourceStructInfo() {
}
public ResourceStructInfo(ResourceStructInfo rsi) {
this.fileHandle = rsi.fileHandle;
this.filePath = rsi.filePath;
this.Fsize = rsi.Fsize;
this.checksum = rsi.checksum;
}
public int getFileHandle() {
return fileHandle;
}
public void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public long getFsize() {
return Fsize;
}
public void setFsize(long fsize) {
Fsize = fsize;
}
public int getChecksum() {
return checksum;
}
public void setChecksum(int checksum) {
this.checksum = checksum;
}
@Override
public String toString() {
return "ResourceStructInfo [fileHandle=" + fileHandle + ", filePath=" + filePath + ", Fsize=" + Fsize
+ ", checksum=" + checksum + "]";
}
}
FileSectionInfo
/**
*
* <ol>
* 功能:用来表示单个资源的类或其片段
* <li>fileHandle 单个资源的标识ID 与 rsiList 中的资源结构对应</li>
* <li>size标识这段单个资源片段的大小</li>
* <li>offset标识从单个资源的其实位置开始</li>
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class FileSectionInfo {
public static final int DEFAULT_HEAD_BYTE_LEN = 16;
private int fileHandle;
private int size;
private long offset;
public FileSectionInfo() {
}
public FileSectionInfo(int fileHandle, long offset, int size) {
setFileHandle(fileHandle);
setOffset(offset);
setSize(size);
}
public FileSectionInfo(FileSectionInfo fileSectionInfo) {
setFileHandle(fileSectionInfo.getFileHandle());
setOffset(fileSectionInfo.getOffset());
setSize(fileSectionInfo.getSize());
}
/**
* a在这里我们规定了其资源传输中的规定,以16字节开始发送分别如下代表
* bFileHandle:代表传输中的哪个文件ID
* bOffset:单个资源的哪个位置开始
* bSize:单个资源的传输长度
* @param byteSectionInfo
* @throws WrongHeadLenException
*/
public FileSectionInfo(byte[] byteSectionInfo) throws WrongHeadLenException {
if (byteSectionInfo.length != DEFAULT_HEAD_BYTE_LEN) {
throw new WrongHeadLenException("错误的头字节数");
}
byte[] bFileHandle = ByteString.getBytesAt(byteSectionInfo, 0, 4);
byte[] bOffset = ByteString.getBytesAt(byteSectionInfo, 4, 8);
byte[] bSize = ByteString.getBytesAt(byteSectionInfo, 12, 4);
setFileHandle(ByteString.bytesToInt(bFileHandle));
setOffset(ByteString.bytesToLong(bOffset));
setSize(ByteString.bytesToInt(bSize));
}
public byte[] toBytes() {
byte[] result = new byte[DEFAULT_HEAD_BYTE_LEN];
byte[] bFileHandle = ByteString.intToBytes(this.fileHandle);
byte[] bOffset = ByteString.longToBytes(this.offset);
byte[] bSize = ByteString.intToBytes(this.size);
ByteString.setBytesAt(result, 0, bFileHandle);
ByteString.setBytesAt(result, 4, bOffset);
ByteString.setBytesAt(result, 12, bSize);
return result;
}
public int getFileHandle() {
return fileHandle;
}
public void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public long getOffset() {
return offset;
}
public void setOffset(long offset) {
this.offset = offset;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + fileHandle;
result = prime * result + (int)(offset ^ (offset >>> 32));
result = prime * result + size;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FileSectionInfo other = (FileSectionInfo)obj;
if (fileHandle != other.fileHandle)
return false;
if (offset != other.offset)
return false;
if (size != other.size)
return false;
return true;
}
@Override
public String toString() {
StringBuffer res = new StringBuffer("fileHandle:");
res.append(this.fileHandle).append(",")
.append("offset:").append(this.offset).append(",")
.append("size:").append(this.size);
return res.toString();
}
}
IReceiveAndSend
/**
*
* <ol>
* 功能:接受和发送的接口类,可以自行选择不同的实现
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public interface IReceiveAndSend {
public static final int DEFAULT_BUFFER_SIZE = 1 << 16;//32K
void send(DataOutputStream dos, byte[] value) throws IOException;
byte[] receive(DataInputStream dis, int size) throws IOException, PeerDownException;
}
ReceiveAndSend
/**
*
* <ol>
* 功能:接受底层实现
* <li></li>
* <li></li>
* <li></li>
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class ReceiveAndSend implements IReceiveAndSend {
private int bufferSize = DEFAULT_BUFFER_SIZE;
public int getBufferSize() {
return bufferSize;
}
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
@Override
public void send(DataOutputStream dos, byte[] value) throws IOException {
dos.write(value);
}
@Override
public byte[] receive(DataInputStream dis, int size) throws IOException, PeerDownException {
byte[] result = new byte[size];
int restLen = size;
int offset = 0;
int readLen = 0;
int len = 0;
while (restLen > 0) {
len = (restLen > bufferSize ? bufferSize : restLen);
readLen = dis.read(result, offset, len);
if (readLen == -1) {
throw new PeerDownException("对端下线");
}
offset += readLen;
restLen -= readLen;
}
return result;
}
}
XMLEditor
/**
*
* <ol>
* 功能:工具类,用来将资源以XML的方式表示到文件中
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class XMLEditor {
private static final Gson gson = new GsonBuilder().create();
private static final String My_ROOT_TAG = "root";
private static volatile DocumentBuilder db;
private static volatile Transformer tf;
public XMLEditor(){
init();
}
private void init(){
try {
if (db == null) {
synchronized (XMLEditor.class) {
if (db == null) {
db= DocumentBuilderFactory.newInstance().newDocumentBuilder();
if (tf == null) {
tf = TransformerFactory.newInstance().newTransformer();
}
}
}
}
}catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerFactoryConfigurationError e) {
e.printStackTrace();
}
}
private void creatNewXml(File xmlFile) {
Document document = db.newDocument();
Element ele = document.createElement(My_ROOT_TAG);
ele.setTextContent("");
document.appendChild(ele);
saveXml(document, xmlFile);
}
private boolean isRightType(Class<?> paraType) {
return paraType.isPrimitive() || paraType.equals(String.class) || paraType.isAssignableFrom(List.class);
}
private <T> Object getFieldValue(Class<?> klass, Field field, Object object) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String fieldName = field.getName();
String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method method = klass.getMethod(methodName, new Class<?>[] {});
return method.invoke(object);
}
private void makeElementByObject(Document document, Element element, Object object) {
Class<?> klass = object.getClass();
Element curelement = document.createElement(klass.getSimpleName());
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
Class<?> paratype = field.getType();
if (!isRightType(paratype)) {
continue;
}
int modifiers = field.getModifiers();
//26 private static final
if (modifiers == 26) {
continue;
}
try {
Object fieldValue = getFieldValue(klass, field, object);
if (fieldValue == null) {
continue;
}
Element ele = document.createElement(field.getName());
if (paratype.isPrimitive() || paratype.equals(String.class)) {
String fieldValueString = fieldValue.toString();
ele.setTextContent(fieldValueString);
} else {
ele.setAttribute("class", fieldValue.getClass().getName());
List<Object> list = (List<Object>) fieldValue;
for (Object obj : list) {
makeElementByObject(document, ele, obj);
}
}
curelement.appendChild(ele);
} catch (Exception e) {
e.printStackTrace();
System.out.println(field.getName() + "没有get方法");
}
}
element.appendChild(curelement);
}
String getAbsolutePathByProjectRoot() {
File currentPath = new File(".");
String absolutePath = currentPath.getAbsolutePath();
int lastDotIndex = absolutePath.lastIndexOf("\\.");
return absolutePath.substring(0, lastDotIndex + 1);
}
public File getAbsolutePathByProjectBin(String path) {
String absolutePath = getAbsolutePathByProjectRoot();
String projectBinPath = absolutePath + "bin\\";
File file = new File(projectBinPath + path);
return file;
}
@SuppressWarnings("unchecked")
private <T> T get(Element element, Class<?> klass) {
StringBuffer result = new StringBuffer();
result.append('{');
Field[] fields = klass.getDeclaredFields();
boolean first = true;
for (Field field : fields) {
String tagName = field.getName();
NodeList eleList = element.getElementsByTagName(tagName);
Element eleProperty = (Element) eleList.item(0);
if (eleProperty == null) {
continue;
}
String propertyValue = eleProperty.getTextContent();
propertyValue = propertyValue.replace("\\", "\\\\");
result.append(first ? "" : ",");
result.append('"').append(tagName).append("\":");
result.append('"').append(propertyValue).append('"');
first = false;
}
result.append('}');
System.out.println(klass);
return (T) gson.fromJson(result.toString(), klass);
}
public <T> T get(String xmlFilePath, Class<?> klass, String propertyName, String value) {
if (propertyName == null || value == null) {
return null;
}
File xmlFile = new File(xmlFilePath);
if (!xmlFile.exists()) {
return null;
}
try {
Document document = db.parse(xmlFile);
String tagName = klass.getSimpleName();
NodeList tagList = document.getElementsByTagName(tagName);
for (int index = 0; index < tagList.getLength(); index++) {
Element clazz = (Element) tagList.item(index);
Element property = (Element) clazz.getElementsByTagName(propertyName).item(0);
String textContent = property.getTextContent();
if (value.equals(textContent)) {
return get(clazz, klass);
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}
public boolean insert(String xmlPath, Object object) {
File xmlFile = new File(xmlPath);
if (!xmlFile.exists()) {
int lastIndex = xmlPath.lastIndexOf("\\");
String xmlFileDirPath = xmlPath.substring(0, lastIndex);
File xmlFileDir = new File(xmlFileDirPath);
xmlFileDir.mkdirs();
creatNewXml(xmlFile);
}
if (object == null) {
return false;
}
try {
Document document = db.parse(xmlPath);
Element root = (Element) document.getElementsByTagName(My_ROOT_TAG).item(0);
if (root == null) {
return false;
}
makeElementByObject(document, root, object);
saveXml(document, xmlFile);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
*将document转化为xml文件
* @param doc
* @param xmlFile
*/
private void saveXml(Document doc, File xmlFile) {
try {
//换行符
tf.setOutputProperty("indent", "yes");
DOMSource domSource = new DOMSource();
domSource.setNode(doc);
StreamResult streamResult = new StreamResult();
streamResult.setOutputStream(new FileOutputStream(xmlFile));
tf.transform(domSource, streamResult);
} catch (Exception e) {
e.printStackTrace();
}
}
}
ByteString
/**
*
* <ol>
* 功能:工具类,用来字节与字符串即数字的转换
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class ByteString {
public static final String HEX_STR = "0123456789ABCDEF";
public ByteString() {
}
public static String byteToString(byte value) {
return "" + HEX_STR.charAt((value >> 4) & 0x0F)
+ HEX_STR.charAt(value & 0x0F);
}
public static String bytesToString(byte[] bytes) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
buffer.append(byteToString(bytes[i]));
}
return buffer.toString();
}
public static String bytesToString(byte[] bytes, int maxColumn) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
if ((i+1) % maxColumn == 0) {
buffer.append("\n");
}
buffer.append(byteToString(bytes[i]));
}
return buffer.toString();
}
public static byte[] intToBytes(int value) {
int len = 4;
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[len - 1 - i] = (byte) ((value >> (i*8)) & 0xFF);
}
return result;
}
public static int bytesToInt(byte[] bytes) {
int result = 0;
int mod = 0xFF;
for (int i = 0; i < 4; i++) {
result |= (((bytes[3 - i]) << (i*8)) & (mod << (i*8)));
}
return result;
}
public static byte[] longToBytes(long value) {
int len = 8;
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[len - 1 - i] = (byte) ((value >> (i*8)) & 0xFF);
}
return result;
}
public static long bytesToLong(byte[] bytes) {
int result = 0;
long mod = 0xFF;
for (int i = 0; i < 4; i++) {
result |= (((bytes[7 - i]) << (i*8)) & (mod << (i*8)));
}
return result;
}
public static void setBytesAt(byte[] target,int offset, byte[] source) {
for (int i = 0; i < source.length; i++) {
target[i + offset] = source[i];
}
}
public static byte[] getBytesAt(byte[] source, int begin, int len) {
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[i] = source[i + begin];
}
return result;
}
@SuppressWarnings("null")
public static byte StringToByte(String str) {
int len = str.length();
if (str == null || len > 2) {
return (Byte) null;
}
str = (len == 1 ? ("0" + str) : str);
byte result = 0;
result += (byte)(HEX_STR.indexOf(str.substring(0, 1)) << 4);
result += (byte)(HEX_STR.indexOf(str.substring(1, 1+1)));
return result;
}
public static byte[] StringToBytes(String str) {
int len = str.length();
if (len % 2 != 0) {
return null;
}
byte[] result = new byte[len/2];
for (int i = 0; i < len/2; i += 2) {
result[i] = StringToByte(str.substring(i, i+2));
}
return result;
}
public static final String calculateCapacity(long size) {
if (size < 1024) {
return String.valueOf((int)(size) + 100).substring(1) + "B";
}
if (size < (1 << 20)) {
return String.valueOf(size >> 10) + "."
+ String.valueOf((int)((size & 0x03FF) / 1024.0 * 100)+ 100).substring(1) + "KB";
}
if (size < (1 << 30)) {
return String.valueOf(size >> 20) + "."
+ String.valueOf((int)((size & 0xFFFFF) / (1024.0*1024.0)* 100) + 100).substring(1) + "MB";
}
if (size < (1L << 40)) {
return String.valueOf(size >> 30) + "."
+ String.valueOf((int)((size & 0x3FFFFFFF) / (1024.0*1024.0*1024.0) * 100) + 100).substring(1) + "GB";
}
return null;
}
}
至此我们说到了资源的表现形式以及传输中资源的接受和发送,接下来我们需要处理资源发送完后若需要断点续传该如何呢?
断点续传的基础
断点续传
断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。
此时我们如何的去判断是否传输完毕呢?
于是乎就有了这样的一个想法,用一个集合去装载资源的抽象,若接受并已经写入磁盘中,则进行组合,直至集合为空,则表示已经接受完毕,否则接受未完成,需要继续地根据此集合中未完成的片段去继续进行请求资源。其表示如下图所示:
假设请求端申请的资源中某文件的信息为片段1,起始地址为0,偏移量为21;
接收端先用片段1初始化UnReceivedFileInfo对象,对象中的List仅包含一个片段;
当接收端接收到片段2后,调用afterReceiveSection()方法,先从列表中找到合适的片段,即片段1,再进行新偏移量和长度的计算,得到两个新的片段,即片段3、4,然后将片段1从List中删除,此时List中仅包含这两个新片段;
当接收端接收到片段5后,与步骤2相同,找到片段4,经过计算得到新片段6、7,然后删除片段4,此时List中包含片段3、6、7;
… …
最后,若该文件全部接收完,则List将为空;
从上面的分析可以得到结论,当接收端接收任务完成后,可以去判断UnReceivedFileInfo对象中的List是否为空,若不为空,则可能出现了某发送端漏发的情况,然后就可以采用断点续传功能再次请求资源;
UNreceiveSectionInfo
/**
*
* <ol>
* 功能:表示单个资源的接受情况
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class UnReceivedFileInfo {
private int fileHandle;
private List<UnReceivedFileSectionInfo> sections;
public UnReceivedFileInfo(int fileHandle, long size) {
this.fileHandle = fileHandle;
sections = new LinkedList<UnReceivedFileSectionInfo>();
sections.add(new UnReceivedFileSectionInfo(fileHandle, 0, size));
}
private int getRightSectionIndex(UnReceivedFileSectionInfo sectionInfo) throws Exception {
int index = 0;
long offset = sectionInfo.getOffset();
long size = sectionInfo.getSize();
for (index = 0; index < sections.size(); index++) {
UnReceivedFileSectionInfo info = sections.get(index);
if (info.isRightSection(offset, size)) {
return index;
}
}
throw new Exception("片段" + sectionInfo + "异常");
}
public int getFileHandle() {
return fileHandle;
}
public void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
public List<UnReceivedFileSectionInfo> getSections() {
return sections;
}
public void setSections(List<UnReceivedFileSectionInfo> sections) {
this.sections = sections;
}
public void afterReceiveSection(UnReceivedFileSectionInfo sectionInfo) {
int index;
try {
index = getRightSectionIndex(sectionInfo);
UnReceivedFileSectionInfo orgSection = sections.get(index);
long orgOffset = orgSection.getOffset();
long orgSize = orgSection.getSize();
long curOffset = sectionInfo.getOffset();
long curSize = sectionInfo.getSize();
long leftOffset = orgOffset;
long leftSize = (curOffset - orgOffset);
long rightOffset = curOffset + curSize;
long rightSize = (orgOffset + orgSize - rightOffset);
sections.remove(index);
if (leftSize > 0) {
sections.add(new UnReceivedFileSectionInfo(fileHandle, leftOffset, leftSize));
}
if (rightSize > 0) {
sections.add(new UnReceivedFileSectionInfo(fileHandle, rightOffset, rightSize));
}
} catch (Exception e) {
}
}
public boolean isOk() {
return sections.size() == 0;
}
@Override
public String toString() {
return "UnReceivedFileInfo [fileHandle=" + fileHandle + ", sections=" + sections.size() + "]";
}
}
UnReceivedFileSectionInfo
/**
*
* <ol>
* 功能:表示一个资源的接受情况
* </ol>
* @author Quan
* @date 2020/03/06
* @version 0.0.1
*/
public class UnReceivedFileInfo {
private int fileHandle;
private List<UnReceivedFileSectionInfo> sections;
public UnReceivedFileInfo(int fileHandle, long size) {
this.fileHandle = fileHandle;
sections = new LinkedList<UnReceivedFileSectionInfo>();
sections.add(new UnReceivedFileSectionInfo(fileHandle, 0, size));
}
private int getRightSectionIndex(UnReceivedFileSectionInfo sectionInfo) throws Exception {
int index = 0;
long offset = sectionInfo.getOffset();
long size = sectionInfo.getSize();
for (index = 0; index < sections.size(); index++) {
UnReceivedFileSectionInfo info = sections.get(index);
if (info.isRightSection(offset, size)) {
return index;
}
}
throw new Exception("片段" + sectionInfo + "异常");
}
public int getFileHandle() {
return fileHandle;
}
public void setFileHandle(int fileHandle) {
this.fileHandle = fileHandle;
}
public List<UnReceivedFileSectionInfo> getSections() {
return sections;
}
public void setSections(List<UnReceivedFileSectionInfo> sections) {
this.sections = sections;
}
public void afterReceiveSection(UnReceivedFileSectionInfo sectionInfo) {
int index;
try {
index = getRightSectionIndex(sectionInfo);
UnReceivedFileSectionInfo orgSection = sections.get(index);
long orgOffset = orgSection.getOffset();
long orgSize = orgSection.getSize();
long curOffset = sectionInfo.getOffset();
long curSize = sectionInfo.getSize();
long leftOffset = orgOffset;
long leftSize = (curOffset - orgOffset);
long rightOffset = curOffset + curSize;
long rightSize = (orgOffset + orgSize - rightOffset);
sections.remove(index);
if (leftSize > 0) {
sections.add(new UnReceivedFileSectionInfo(fileHandle, leftOffset, leftSize));
}
if (rightSize > 0) {
sections.add(new UnReceivedFileSectionInfo(fileHandle, rightOffset, rightSize));
}
// System.out.println("合并");
} catch (Exception e) {
// e.printStackTrace();
}
}
public boolean isOk() {
return sections.size() == 0;
}
@Override
public String toString() {
return "UnReceivedFileInfo [fileHandle=" + fileHandle + ", sections=" + sections.size() + "]";
}
}
Ending:总结图
最后让我们总结一下这些代码,如下图所示