本站业务范围:1、PC端软件开发、网站开发 2、移动端APP、网站、微信接口、微商城开发 3、视频教程、课程设计和辅导 4、单片机开发 5、串口通讯调试
 当前位置:文章中心 >> Java_Jsp_Jdk_eclipse_tomcat
立即购买视频教程 java实现断点续传功能
夜鹰教程网 来源:www.yyjcw.com 日期:2018-6-30 15:52:48
java实现断点续传功能

这篇文章不能解决你的问题?我们还有相关视频教程云课堂 全套前端开发工程师培训课程

微信号:yyjcw10000 QQ:1416759661  远程协助需要加QQ!

业务范围:视频教程|程序开发|在线解答|Demo制作|远程调试| 点击查看相关的视频教程

技术范围:全端开发/前端开发/webapp/web服务/接口开发/单片机/C#/java/node/sql server/mysql/mongodb/android/。 



在网络状况不好的情况下,对于文件的传输,我们希望能够支持可以每次传部分数据。首先从文件传输协议FTP和TFTP开始分析,

FTP是基于TCP的,一般情况下建立两个连接,一个负责指令,一个负责数据;而TFTP是基于UDP的,由于UDP传输是不可靠的,虽然传输速度很快,但对于普通的文件像PDF这种,少了一个字节都不行。本次以IM中的文件下载场景为例,解析基于TCP的文件断点续传的原理,并用代码实现。

什么是断点续传?

断点续传其实正如字面意思,就是在下载的断开点继续开始传输,不用再从头开始。所以理解断点续传的核心后,发现其实和很简单,关键就在于对传输中断点的把握,我就自己的理解画了一个简单的示意图:

20141206215834127.png

原理:

断点续传的关键是断点,所以在制定传输协议的时候要设计好,如上图,我自定义了一个交互协议,每次下载请求都会带上下载的起始点,这样就可以支持从断点下载了,其实HTTP里的断点续传也是这个原理,在HTTP的头里有个可选的字段RANGE,表示下载的范围,下面是我用Java语言实现的下载断点续传示例。

提供下载的服务端代码:


[java] view plaincopy

  1. import java.io.File;  

  2. import java.io.IOException;  

  3. import java.io.InputStream;  

  4. import java.io.OutputStream;  

  5. import java.io.RandomAccessFile;  

  6. import java.io.StringWriter;  

  7. import java.net.ServerSocket;  

  8. import java.net.Socket;  

  9.   

  10. // 断点续传服务端  

  11. public class FTPServer {  

  12.   

  13.     // 文件发送线程  

  14.     class Sender extends Thread{  

  15.         // 网络输入流  

  16.         private InputStream in;  

  17.         // 网络输出流  

  18.         private OutputStream out;  

  19.         // 下载文件名  

  20.         private String filename;  

  21.   

  22.         public Sender(String filename, Socket socket){  

  23.             try {  

  24.                 this.out = socket.getOutputStream();  

  25.                 this.in = socket.getInputStream();  

  26.                 this.filename = filename;  

  27.             } catch (IOException e) {  

  28.                 e.printStackTrace();  

  29.             }  

  30.         }  

  31.           

  32.         @Override  

  33.         public void run() {  

  34.             try {  

  35.                 System.out.println("start to download file!");  

  36.                 int temp = 0;  

  37.                 StringWriter sw = new StringWriter();  

  38.                 while((temp = in.read()) != 0){  

  39.                     sw.write(temp);  

  40.                     //sw.flush();  

  41.                 }  

  42.                 // 获取命令  

  43.                 String cmds = sw.toString();  

  44.                 System.out.println("cmd : " + cmds);  

  45.                 if("get".equals(cmds)){  

  46.                     // 初始化文件  

  47.                     File file = new File(this.filename);  

  48.                     RandomAccessFile access = new RandomAccessFile(file,"r");  

  49.                     //  

  50.                     StringWriter sw1 = new StringWriter();  

  51.                     while((temp = in.read()) != 0){  

  52.                         sw1.write(temp);  

  53.                         sw1.flush();  

  54.                     }  

  55.                     System.out.println(sw1.toString());  

  56.                     // 获取断点位置  

  57.                     int startIndex = 0;  

  58.                     if(!sw1.toString().isEmpty()){  

  59.                         startIndex = Integer.parseInt(sw1.toString());  

  60.                     }  

  61.                     long length = file.length();  

  62.                     byte[] filelength = String.valueOf(length).getBytes();  

  63.                     out.write(filelength);  

  64.                     out.write(0);  

  65.                     out.flush();  

  66.                     // 计划要读的文件长度  

  67.                     //int length = (int) file.length();//Integer.parseInt(sw2.toString());  

  68.                     System.out.println("file length : " + length);  

  69.                     // 缓冲区10KB  

  70.                     byte[] buffer = new byte[1024*10];  

  71.                     // 剩余要读取的长度  

  72.                     int tatol = (int) length;  

  73.                     System.out.println("startIndex : " + startIndex);  

  74.                     access.skipBytes(startIndex);  

  75.                     while (true) {  

  76.                         // 如果剩余长度为0则结束  

  77.                         if(tatol == 0){  

  78.                             break;  

  79.                         }  

  80.                         // 本次要读取的长度假设为剩余长度  

  81.                         int len = tatol - startIndex;  

  82.                         // 如果本次要读取的长度大于缓冲区的容量  

  83.                         if(len > buffer.length){  

  84.                             // 修改本次要读取的长度为缓冲区的容量  

  85.                             len = buffer.length;  

  86.                         }  

  87.                         // 读取文件,返回真正读取的长度  

  88.                         int rlength = access.read(buffer,0,len);  

  89.                         // 将剩余要读取的长度减去本次已经读取的  

  90.                         tatol -= rlength;  

  91.                         // 如果本次读取个数不为0则写入输出流,否则结束  

  92.                         if(rlength > 0){  

  93.                             // 将本次读取的写入输出流中  

  94.                             out.write(buffer,0,rlength);  

  95.                             out.flush();  

  96.                         } else {  

  97.                             break;  

  98.                         }  

  99.                         // 输出读取进度  

  100.                         //System.out.println("finish : " + ((float)(length -tatol) / length) *100 + " %");  

  101.                     }  

  102.                     //System.out.println("receive file finished!");  

  103.                     // 关闭流  

  104.                     out.close();  

  105.                     in.close();  

  106.                     access.close();  

  107.                 }  

  108.             } catch (IOException e) {  

  109.                 e.printStackTrace();  

  110.             }  

  111.             super.run();  

  112.         }  

  113.     }  

  114.       

  115.     public void run(String filename, Socket socket){  

  116.         // 启动接收文件线程   

  117.         new Sender(filename,socket).start();  

  118.     }  

  119.       

  120.     public static void main(String[] args) throws Exception {  

  121.         // 创建服务器监听  

  122.         ServerSocket server = new ServerSocket(8888);  

  123.         // 接收文件的保存路径  

  124.         String filename = "E:\\ceshi\\mm.pdf";  

  125.         for(;;){  

  126.             Socket socket = server.accept();  

  127.             new FTPServer().run(filename, socket);  

  128.         }  

  129.     }  

  130.   

  131. }  


下载的客户端代码:



[java] view plaincopy

  1. import java.io.File;  

  2. import java.io.InputStream;  

  3. import java.io.OutputStream;  

  4. import java.io.RandomAccessFile;  

  5. import java.io.StringWriter;  

  6. import java.net.InetSocketAddress;  

  7. import java.net.Socket;  

  8.   

  9. // 断点续传客户端  

  10. public class FTPClient {  

  11.   

  12.     /** 

  13.      *  request:get0startIndex0 

  14.      *  response:fileLength0fileBinaryStream 

  15.      *   

  16.      * @param filepath 

  17.      * @throws Exception 

  18.      */  

  19.     public void Get(String filepath) throws Exception {  

  20.         Socket socket = new Socket();  

  21.         // 建立连接  

  22.         socket.connect(new InetSocketAddress("127.0.0.1"8888));  

  23.         // 获取网络流  

  24.         OutputStream out = socket.getOutputStream();  

  25.         InputStream in = socket.getInputStream();  

  26.         // 文件传输协定命令  

  27.         byte[] cmd = "get".getBytes();  

  28.         out.write(cmd);  

  29.         out.write(0);// 分隔符  

  30.         int startIndex = 0;  

  31.         // 要发送的文件  

  32.         File file = new File(filepath);  

  33.         if(file.exists()){  

  34.             startIndex = (int) file.length();  

  35.         }  

  36.         System.out.println("Client startIndex : " + startIndex);  

  37.         // 文件写出流  

  38.         RandomAccessFile access = new RandomAccessFile(file,"rw");  

  39.         // 断点  

  40.         out.write(String.valueOf(startIndex).getBytes());  

  41.         out.write(0);  

  42.         out.flush();  

  43.         // 文件长度  

  44.         int temp = 0;  

  45.         StringWriter sw = new StringWriter();  

  46.         while((temp = in.read()) != 0){  

  47.             sw.write(temp);  

  48.             sw.flush();  

  49.         }  

  50.         int length = Integer.parseInt(sw.toString());  

  51.         System.out.println("Client fileLength : " + length);  

  52.         // 二进制文件缓冲区  

  53.         byte[] buffer = new byte[1024*10];  

  54.         // 剩余要读取的长度  

  55.         int tatol = length - startIndex;  

  56.         //  

  57.         access.skipBytes(startIndex);  

  58.         while (true) {  

  59.             // 如果剩余长度为0则结束  

  60.             if (tatol == 0) {  

  61.                 break;  

  62.             }  

  63.             // 本次要读取的长度假设为剩余长度  

  64.             int len = tatol;  

  65.             // 如果本次要读取的长度大于缓冲区的容量  

  66.             if (len > buffer.length) {  

  67.                 // 修改本次要读取的长度为缓冲区的容量  

  68.                 len = buffer.length;  

  69.             }  

  70.             // 读取文件,返回真正读取的长度  

  71.             int rlength = in.read(buffer, 0, len);  

  72.             // 将剩余要读取的长度减去本次已经读取的  

  73.             tatol -= rlength;  

  74.             // 如果本次读取个数不为0则写入输出流,否则结束  

  75.             if (rlength > 0) {  

  76.                 // 将本次读取的写入输出流中  

  77.                 access.write(buffer, 0, rlength);  

  78.             } else {  

  79.                 break;  

  80.             }  

  81.             System.out.println("finish : " + ((float)(length -tatol) / length) *100 + " %");  

  82.         }  

  83.         System.out.println("finished!");  

  84.         // 关闭流  

  85.         access.close();  

  86.         out.close();  

  87.         in.close();  

  88.     }  

  89.   

  90.     public static void main(String[] args) {  

  91.         FTPClient client = new FTPClient();  

  92.         try {  

  93.             client.Get("E:\\ceshi\\test\\mm.pdf");  

  94.         } catch (Exception e) {  

  95.             e.printStackTrace();  

  96.         }  

  97.     }  

  98. }  

测试
原文件、下载中途断开的文件和从断点下载后的文件分别从左至右如下:



断点前的传输进度如下(中途省略):

Client fileLength : 51086228
finish : 0.020044541 %
finish : 0.040089082 %
finish : 0.060133625 %
finish : 0.07430574 %
finish : 0.080178164 %
...
finish : 60.41171 %
finish : 60.421593 %
finish : 60.428936 %
finish : 60.448982 %
finish : 60.454338 %

断开的点计算:30883840 / 51086228 = 0.604543361471119 * 100% = 60.45433614%

从断点后开始传的进度(中途省略):
Client startIndex : 30883840
Client fileLength : 51086228
finish : 60.474377 %
finish : 60.494423 %
finish : 60.51447 %
finish : 60.53451 %
finish : 60.554558 %
...
finish : 99.922035 %
finish : 99.942085 %
finish : 99.95677 %
finish : 99.96213 %
finish : 99.98217 %
finish : 100.0 %
finished!

断点处前后的百分比计算如下:

20141206222814964.jpg

============================下面是从断点开始的进度==============================


20141206222835727.jpg

本方案是基于TCP,在本方案设计之初,我还探索了一下介于TCP与UDP之间的一个协议:UDT(基于UDP的可靠传输协议)。

我基于Netty写了相关的测试代码,用Wireshark拆包发现的确是UDP的包,而且是要建立连接的,与UDP不同的是需要建立连接,所说UDT的传输性能比TCP好,传输的可靠性比UDP好,属于两者的一个平衡的选择,感兴的可以深入研究一下。


复制链接 网友评论 收藏本文 关闭此页
上一条: php实现大文件断点续传功能  下一条: Windows Server 2016新功能
夜鹰教程网成立于2008年,目前已经运营了将近 13 年,发布了大量关于 html5/css3/C#/asp.net/java/python/nodejs/mongodb/sql server/android/javascript/mysql/mvc/easyui/vue/echarts原创教程。 我们一直都在坚持的是:认证负责、一丝不苟、以工匠的精神来打磨每一套教程,让读者感受到作者的用心。我们默默投入的时间,确保每一套教程都是一件作品,而不是呆板的文字和视频! 目前我们推出在线辅导班试运营,模式为一对一辅导,教学工具为QQ。我们的辅导学科包括 java 、android原生开发、webapp开发、商城开发、C#和asp.net开发,winform和物联网开发、web前端开发,但不仅限于此。 普通班针对的是国内学员,例如想打好基础的大学生、想转行的有志青年、想深入学习的程序员、想开发软件的初学者或者业余爱好者等。 就业办针对即将毕业上岗的大四学生,或者打算转行的初级开发工程师。 留学生班针对的是在欧美、加拿大、澳洲、日本、韩国、新加坡等地留学的中国学子,目的是让大家熟练地掌握编程技能,按时完成老师布置的作业,并能顺利地通过考试。 详细咨询QQ:1416759661   夜鹰教程网  基于角色的权限管理系统(c-s/b-s)。
  夜鹰教程网  基于nodejs的聊天室开发视频教程
  夜鹰教程网  Git分布式版本管理视频教程
  夜鹰教程网  MVC+EasyUI视频教程
  夜鹰教程网  在线考试系统视频教程
  夜鹰教程网  MongoDB视频教程。
  夜鹰教程网 Canvas视频教程
  夜鹰教程网 报表开发视频教程
  推荐教程/优惠活动

  热门服务/教程目录

  夜鹰教程网  新手必看,详细又全面。
  夜鹰教程网  购买教程  夜鹰教程网  在线支付-方便
  夜鹰教程网  担保交易-快捷安全   夜鹰教程网  闪电发货
  夜鹰教程网  电话和QQ随时可以联系我们。
  夜鹰教程网 不会的功能都可以找我们,按工作量收费。

客服电话:153 9760 0032

购买教程QQ:1416759661  
  热点推荐
初学教程:Java Socket网络编程浅…
Java随机数总结
java读取文本文件内容并获取文件大…
字节流和字符流Java
java常用图片读写程序
怎样成为一个优秀的Java程序员
C++程序员转Java容易吗?难点在哪…
思科培训与华为培训在教材和内容方…
Java注释的使用和定义
使用 Java 实现 Comet 风格的 Web…
利用Java实现zip压缩/解压缩
Java Web三层架构的配置详解
Java Socket编程之我见
使用 Java 实现 Comet 风格的 Web…
两种J2ME网络编程的方法之一
  尊贵服务
夜鹰教程网 承接业务:软件开发 网站开发 网页设计 .Net+C#+VS2008+MSsql+Jquery+ExtJs全套高清完整版视频教程
  最近更新
short、int、long、float、double…
二进制(原码、反码、补码)
python时间模块详解
java 解决split分割空值不能得到的…
如何打开eclipse安卓开发代码提示…
php实现大文件断点续传功能
java实现断点续传功能
Windows Server 2016新功能
MyEclipse 8.5 汉化方法
jquery实现无刷新分页
什么是JSON?
什么是回调函数
jQuery 1.7下载
jquery加载XML文档
什么是MVC(三层架构)
  工具下载  需要远程协助? 

sql2008视频教程 c#视频教程

VIP服务:如果您的某个功能不会做,可以加我们QQ,给你做DEMO!

JQUERY  Asp.net教程

MVC视频教程  vs2012
.NET+sql开发
手机:15397600032 C#视频教程下载
微信小程序 vue.js高级实例视频教程

教程咨询QQ:1416759661


这篇文章不能解决你的问题?我们还有相关视频教程云课堂 全套前端开发工程师培训课程

微信号:yyjcw10000 QQ:1416759661  远程协助需要加QQ!

业务范围:视频教程|程序开发|在线解答|Demo制作|远程调试| 点击查看相关的视频教程

技术范围:全端开发/前端开发/webapp/web服务/接口开发/单片机/C#/java/node/sql server/mysql/mongodb/android/。 



关于我们 | 购买教程 | 网站建设 | 技术辅导 | 常见问题 | 联系我们 | 友情链接

夜鹰教程网 版权所有 www.yyjcw.com All rights reserved 备案号:蜀ICP备08011740号3