热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

断点续传的Java桌面程序

说明断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行

说明

断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。常见的支持断点续传的上传、下载软件:QQ旋风、迅雷、快车(迷你快车)、web迅雷、影音传送带、快车、BitComet、电驴eMule、哇嘎Vagaa、RF[RaySourse/RayFile]、酷6、土豆、优酷、百度视频、新浪视频、腾讯视频、百度云等都支持断点续传。


实现方式

在下载文件的同时,创建一个info文件保存传输的文件节点,当服务器断开或者暂停下载的时候,通过info文件记录已下载的结点位置,当再次进行下载任务的时候,将info文件进行分析,告诉服务器需要继续传输的文件节点位置进行续传操作。例如如下面要求从 1526118 字节开始:

GET http://ozcte1ybf.bkt.clouddn.com/level5.mp4 HTTP/1.0
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
RANGE: bytes=1526118-
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
这里多了一行 RANGE: bytes=1526118- 

这一行的意思就是告诉服务器level5.mp4这个文件从 1526118 字节开始传,前面的字节不用传了。 服务器收到这个请求以后,返回的信息如下: 

206
Content-Length=15261183
Content-Range=bytes 1526118-15261182/15261183
Date=Wed, 27 Dec 2017 09:17:07 GMT
ETag="A1A1A8B84E5C2F5E7A16E0886721419E"
Content-Type=application/octet-stream
Server=Tengine
Last-Modified=Sun, 22 Jan 2017 15:13:55 GMT

返回的信息增加了一行: Content-Range=bytes 1526118-15261182/15261183

返回的代码也变成 206 ,而不再是 200 了。知道了这些原理,就可以进行断点续传的编程了。

注:206状态码含义:
206 Partial Content


服务器已经成功处理了部分 GET 请求。类似于 FlashGet 或者迅雷这类的 HTTP下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
该请求必须包含 Range 头信息来指示客户端希望得到的内容范围,并且可能包含 If-Range 来作为请求条件。
响应必须包含如下的头部域:
Content-Range 用以指示本次响应中返回的内容的范围;如果是 Content-Type 为 multipart/byteranges 的多段下载,则每一 multipart 段中都应包含 Content-Range 域用以指示本段的内容范围。假如响应中包含 Content-Length,那么它的数值必须匹配它返回的内容范围的真实字节数。
Date
ETag 和/或 Content-Location,假如同样的请求本应该返回200响应。
Expires, Cache-Control,和/或 Vary,假如其值可能与之前相同变量的其他响应对应的值不同的话。
假如本响应请求使用了 If-Range 强缓存验证,那么本次响应不应该包含其他实体头;假如本响应的请求使用了 If-Range 弱缓存验证,那么本次响应禁止包含其他实体头;这避免了缓存的实体内容和更新了的实体头信息之间的不一致。否则,本响应就应当包含所有本应该返回200响应中应当返回的所有实体头部域。
假如 ETag 或 Last-Modified 头部不能精确匹配的话,则客户端缓存应禁止将206响应返回的内容与之前任何缓存过的内容组合在一起。

具体代码

主要用了 6 个类,包括一个测试类:
SiteFileFetch.java         负责整个文件的抓取,控制内部线程 (FileSplitterFetch 类 )。 
FileSplitterFetch.java   负责部分文件的抓取。 
FileAccess.java             负责文件的存储。 
SiteInfoBean.java         要抓取的文件的信息,如文件保存的目录,名字,抓取文件的 URL 等。 
Utility.java                    工具类,放一些简单的方法。

TestMethod.java          测试类。

FileAccess.java

package fileHandling;import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Serializable;/*** * 负责文件的存储* @author yunlingfly* @since 2018-3-11* */
public class FileAccess implements Serializable {private static final long serialVersiOnUID= -6335788938054788024L;RandomAccessFile oSavedFile;long nPos;public FileAccess() throws IOException {this("", 0);}public FileAccess(String sName, long nPos) throws IOException {oSavedFile = new RandomAccessFile(sName, "rw");this.nPos = nPos;oSavedFile.seek(nPos);}public synchronized int write(byte[] b, int nStart, int nLen) {int n = -1;try {oSavedFile.write(b, nStart, nLen);n = nLen;} catch (IOException e) {e.printStackTrace();}return n;}}

FileSplitterFetch.java

package fileHandling;import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;/*** * 负责部分文件的抓取* @author yunlingfly* @since 2018-3-11*/
public class FileSplitterFetch extends Thread {// File URLString sURL;// File Snippet Start Positionlong nStartPos;// File Snippet End Positionlong nEndPos;// Thread's IDint nThreadID;// Downing is overboolean bDownOver = false;// Stop identicalboolean bStop = false;// File Access interfaceFileAccess fileAccessI = null;public FileSplitterFetch(String sURL, String sName, long nStart, long nEnd, int id) throws IOException {this.sURL = sURL;this.nStartPos = nStart;this.nEndPos = nEnd;nThreadID = id;fileAccessI = new FileAccess(sName, nStartPos);}public void run() {while (nStartPos 0 && nStartPos

SiteFileFetch.java

package fileHandling;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;/*** * 负责整个文件的抓取,控制内部线程(FileSplitterFetch类)* * @author yunlingfly* @since 2018-3-11*/
public class SiteFileFetch extends Thread {// 文件信息BeanSiteInfoBean siteInfoBean = null;// 开始位置long[] nStartPos;// 结束位置long[] nEndPos;// 子线程对象FileSplitterFetch[] fileSplitterFetch;// 文件长度long nFileLength;// 是否第一次取文件boolean bFirst = true;// 停止标志boolean bStop = false;// 文件下载的临时信息File tmpFile;// 输出到文件的输出流DataOutputStream output;public SiteFileFetch(SiteInfoBean bean) throws IOException {siteInfoBean = bean;// tmpFile = File.createTempFile ("zhong","1111",new// File(bean.getSFilePath()));tmpFile = new File(bean.getSFilePath() + File.separator + bean.getSFileName() + ".info");if (tmpFile.exists()) {bFirst = false;read_nPos();} else {nStartPos = new long[bean.getNSplitter()];nEndPos = new long[bean.getNSplitter()];}}public void run() {// 获得文件长度// 分割文件// 实例FileSplitterFetch// 启动FileSplitterFetch线程// 等待子线程返回try {if (bFirst) {nFileLength = getFileSize();if (nFileLength == -1) {System.err.println("File Length is not known!");} else if (nFileLength == -2) {System.err.println("File is not access!");} else {for (int i = 0; i = 400) {processErrorCode(responseCode);return -2; // -2 represent access is error}String sHeader;for (int i = 1;; i++) {// DataInputStream in = new// DataInputStream(httpConnection.getInputStream ());// Utility.log(in.readLine());sHeader = httpConnection.getHeaderFieldKey(i);if (sHeader != null) {if (sHeader.equals("Content-Length")) {nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));break;}} elsebreak;}} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}Utility.log(nFileLength);return nFileLength;}// 保存下载信息(文件指针位置)private void write_nPos() {try {output = new DataOutputStream(new FileOutputStream(tmpFile));output.writeInt(nStartPos.length);for (int i = 0; i

SiteInfoBean.java

package fileHandling;/*** * 要抓取的文件的信息,如文件保存的目录,名字,抓取文件的URL等* * @author yunlingfly* @since 2018-3-11*/
public class SiteInfoBean {// Site's URLprivate String sSiteURL;// Saved File's Pathprivate String sFilePath;// Saved File's Nameprivate String sFileName;// Count of Splited Downloading Fileprivate int nSplitter;public SiteInfoBean() {// default value of nSplitter is 5this("", "", "", 5);}public SiteInfoBean(String sURL, String sPath, String sName, int nSpiltter) {sSiteURL = sURL;sFilePath = sPath;sFileName = sName;this.nSplitter = nSpiltter;}public String getSSiteURL() {return sSiteURL;}public void setSSiteURL(String value) {sSiteURL = value;}public String getSFilePath() {return sFilePath;}public void setSFilePath(String value) {sFilePath = value;}public String getSFileName() {return sFileName;}public void setSFileName(String value) {sFileName = value;}public int getNSplitter() {return nSplitter;}public void setNSplitter(int nCount) {nSplitter = nCount;}}

Utility.java

package fileHandling;/*** * 工具类,日志处理,线程等待* @author yunlingfly* @since 2018-3-11*/
public class Utility {public Utility() {}public static void sleep(int nSecond) {try {Thread.sleep(nSecond);} catch (Exception e) {e.printStackTrace();}}public static void log(String sMsg) {System.err.println(sMsg);}public static void log(int sMsg) {System.err.println(sMsg);}}

测试运行类TestJframe.java

package fileHandling;import java.awt.BorderLayout;
import java.awt.EventQueue;import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.GridLayout;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;/*** * 界面展示* @author yunlingfly* @since 2018-3-11* */
public class TestJframe extends JFrame {private static final long serialVersiOnUID= -2866562095512884191L;private JPanel contentPane;private JTextField textField;private SiteFileFetch fileFetch;/*** Launch the application.*/public static void main(String[] args) {EventQueue.invokeLater(new Runnable() {public void run() {try {TestJframe frame = new TestJframe();frame.setVisible(true);} catch (Exception e) {e.printStackTrace();}}});}/*** Create the frame.*/public TestJframe() {// 设置标题setTitle("桌面断点下载程序");// JFrame关闭后退出。setDefaultCloseOperation(EXIT_ON_CLOSE);// JFrame可见。setVisible(true);// 设置JFrame的长和宽。setSize(800, 500);// 设置JFrame是否可以改变大小。setResizable(false);// JFrame打开后居中。setLocationRelativeTo(getOwner());setDefaultCloseOperation(EXIT_ON_CLOSE);// 如果没有这句,界面关闭后程序的内存没有清空。当然也可以用// setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// setBounds(400, 100, 600, 400);cOntentPane= new JPanel();contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));contentPane.setLayout(new BorderLayout(0, 0));setContentPane(contentPane);JPanel panel = new JPanel();contentPane.add(panel, BorderLayout.CENTER);panel.setLayout(new GridLayout(3, 1, 0, 0));JPanel panel_1 = new JPanel();panel.add(panel_1);JLabel label = new JLabel("请输入下载的文件名:");panel_1.add(label);textField = new JTextField();panel_1.add(textField);textField.setColumns(10);JPanel panel_2 = new JPanel();panel.add(panel_2);JButton button = new JButton("下载");// 编写下载按钮事件button.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {System.out.println("开始下载");// 根据需要可以在这里添加选择下载的文件夹和保存名称,比如使用JFileChooser等String fileName = textField.getText();download(fileName);}});panel_2.add(button);JButton button_1 = new JButton("暂停");// 编写暂停按钮事件button_1.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {System.out.println("暂停...");// 停止下载文件fileFetch.siteStop();}});panel_2.add(button_1);JButton button_2 = new JButton("恢复");button_2.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {// 从断点继续下载,由于该性质,根据文件名(下载任务号)使得可以断点下载不同的文件,即多任务可选择性优先下载某个文件String fileName = textField.getText();download(fileName);}});panel_2.add(button_2);JPanel panel_3 = new JPanel();panel.add(panel_3);JLabel lblfly = new JLabel("该测试程序由芸灵fly编写");panel_3.add(lblfly);}public void download(String fileName) {try {// 从服务器取文件,根据需要可以将fileName换成url来更换下载地址前缀(这里默认从笔者的服务器下东西)// 存到D:\\shudian,命名和下载的文件名一致,开启10个线程// SiteInfoBean(下载地址,保存位置,保存文件名,开启线程数)四个参数SiteInfoBean bean = new SiteInfoBean("http://ozcte1ybf.bkt.clouddn.com/" + fileName, "D:\\shudian",fileName, 10);fileFetch = new SiteFileFetch(bean);fileFetch.start();} catch (Exception e) {e.printStackTrace();}}
}

运行结果:


读者可以根据自己的需要更改下载地址、保存位置、文件名和线程数,笔者演示的是下载http://ozcte1ybf.bkt.clouddn.com/level5.mp4文件,输入level5.mp4后点击下载按钮,即可下载,点击暂停或者关闭程序,可以在下载目录找到下载文件和info文件,再次点击恢复,则继续下载直到完成下载


注:

1 没全部下载完成的视频是可以播放已经下载好的部分的(类似迅雷)

2 如果下载文件过大,请增加线程数试试,一个线程最多下多少具体数值不太清楚。。。


推荐阅读
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • pyecharts 介绍
    一、pyecharts介绍ECharts,一个使用JavaScript实现的开源可视化库,可以流畅的运行在PC和移动设备上,兼容当前绝大部 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • 本文介绍了一个免费的asp.net控件,该控件具备数据显示、录入、更新、删除等功能。它比datagrid更易用、更实用,同时具备多种功能,例如属性设置、数据排序、字段类型格式化显示、密码字段支持、图像字段上传和生成缩略图等。此外,它还提供了数据验证、日期选择器、数字选择器等功能,以及防止注入攻击、非本页提交和自动分页技术等安全性和性能优化功能。最后,该控件还支持字段值合计和数据导出功能。总之,该控件功能强大且免费,适用于asp.net开发。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
author-avatar
舅舅家123_204
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有