XxwGit 的个人博客

记录精彩的程序人生

目录
断点续传是怎样实现?
/  

断点续传是怎样实现?

前言:这几天写了一个断点续传的小demo,总结一下。

断点续传是什么?

FTP(文件传输协议)在对文件进行下载或者上传时,如果由于客户端的某些动作导致下载或者上传的动作进行了人为地暂停或者是意外的暂停,断点续传技术就将下载的文件人为的划分为几个部分,每一个部分都会使用一个线程来进行处理,当暂停再次开始时,任务会从上次暂停的位置继续进行下载或者上传的动作。
换个通俗的说法,比如果我们现在在玩一个通关制的RPG游戏,你一般都是一天直接打通关的吗?明显不是啊。一般的情况都是拿到一个新游戏,第一天兴致勃勃大杀四方,如果说这时候已经是晚上凌晨一两点,明显精力不足需要睡觉了,现在才发现刚刚打到第五关,后面还有n多关,这时候不得不睡觉,一般都会有一个存档的功能,这个存档其实就是一个"断点"的概念,下一次继续从这个断点读档继续就行。

断点续传的原理是什么?

想要实现断点续传,我们首先需要客户端记录当前的下载进度,当需要再次进行下载的时候客户端需要提供本次需要下载的片段。
来根据一个具体的场景来看下断点续传:

  1. 现在下载一个1000K的文件,已经下载500K
  2. 请求中断,客户端请求继续下载,那么本次下载将会从500K位置开始继续下载,那么客户端就需要声明一下请求头:Range:bytes=512000- ,通过这个请求头就可以通知服务端从500k的位置开始下载。
  3. 当服务端收到断点续传的请求之后,从500K位置开始传输,并且在HTTP头中增加:Content-Range:bytes 512000-/1024000,并且此时服务端返回的HTTP状态码应该是206,而不是200。

断点续传的具体代码实现

@Controller
@EnableAutoConfiguration
public class DownLoad {
	@RequestMapping("/breakpoint")
	public void HandleBreakPoint(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
			@RequestParam(name = "path") String path) throws InterruptedException {
		// 0.从请求中获取请求头
		String range = httpServletRequest.getHeader("Range");
		File file = new File(path);
		System.out.println(range);// 测试一下range
		// 1.定义文件开始下载和结束下载的位置
		long start = 0;
		long end = file.length() - 1;// 注意下,从0计数必须减一
		// 2.判断range是否符合断点续传条件
		if (range != null && range.contains("bytes=") && range.contains("-")) {
			// 2.1.处理一下range,生成一个数组,记录开始和结束为止
			int index = range.lastIndexOf("=") + 1;
			range = range.substring(index).trim();// 对range进行处理
			String[] nodes = range.split("-");
			// 2.2.处理开始下载和结束下载的位置更改
			try {
				// 情况一:range中只有开始下载位置或者是只有结束位置
				if (nodes.length == 1) {
					// 只存在开始下载位置
					if (range.endsWith("-")) {
						start = Integer.valueOf(nodes[0]).intValue();
					}
					// 只存在结束下载的位置
					else if (range.startsWith("-")) {
						end = Integer.valueOf(nodes[0]).intValue();
					}
				}
				// 情况二:range中同时有开始下载位置和结束位置
				else if (nodes.length == 2) {
					start = Integer.valueOf(nodes[0]).intValue();
					end = Integer.valueOf(nodes[1]).intValue();
				}
			} catch (NumberFormatException e) {
				start = 0;
				end = file.length() - 1;
			}
		}
		// 3.响应头设置
		long length = end - start + 1;// 下载文件的总长度
		String fileType = httpServletRequest.getServletContext().getMimeType(file.getName());// 文件类型
		httpServletResponse.setHeader("Accept-Ranges", "bytes");// 这里设置支持以bytes为单位进行传输
		httpServletResponse.setStatus(httpServletResponse.SC_PARTIAL_CONTENT);// 206响应码
		httpServletResponse.setHeader("Content-Disposition", "Attachment;filename=" + file.getName());// 设置下载后的名称
		httpServletResponse.setContentType(fileType);
		httpServletResponse.setHeader("Content-Type", fileType);// 设置文件格式
		httpServletResponse.setHeader("Content-Length", String.valueOf(length));// 下载文件的长度
		httpServletResponse.setHeader("Content-Range", "bytes" + start + "-" + end + "/" + file.length());

		// 4.开始进行下载处理
		long transmitted = 0;// 已传输数据
		try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
				httpServletResponse.getOutputStream());
				RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");) {
			byte[] bytes = new byte[1024];// 开一个1K的缓冲区
			int byteLength = 0;// 每次下载处理的长度
			randomAccessFile.seek(start);// 设置指针位置到start对应的位置,从此处继续写入

			// 4.1开始处理下载任务,每次写入1024B
			while (transmitted <= length && (byteLength = randomAccessFile.read(bytes)) != -1) {
				bufferedOutputStream.write(bytes, 0, byteLength);
				transmitted = transmitted + byteLength;

				// Thread.sleep(10);//测试用的,减慢一下下载速度
			}

			bufferedOutputStream.flush();
			httpServletResponse.flushBuffer();
			System.out.println(httpServletResponse.getHeader("Content-Range"));
			// System.out.println("下载完毕:"+start+"-"+end);
		} catch (ClientAbortException e) {
			System.out.println("停止下载:" + start + "-" + end + ":" + "本次下载:" + transmitted);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


标题:断点续传是怎样实现?
作者:XxwGit
地址:http://xxwgit.cn/solo/articles/2019/09/06/1567738876561.html

评论