多文件自平衡云传输框架(2)-- 传输中资源的表示(资源的表达形式以及断点续传的基础)

目录

模块1

传输中资源的表示

ResourceBaseInfo 类

FileSection

ResourceStructInfo

FileSectionInfo

IReceiveAndSend

ReceiveAndSend

XMLEditor

ByteString

断点续传的基础

断点续传

UNreceiveSectionInfo

UnReceivedFileSectionInfo

Ending:总结图


模块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:总结图

最后让我们总结一下这些代码,如下图所示

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值