文件断点续传原理与实现

文件断点续传原理与实现

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

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

什么是断点续传?

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

原理:

断点续传的关键是断点,所以在制定传输协议的时候要设计好,如上图,我自定义了一个交互协议,每次下载请求都会带上下载的起始点,这样就可以支持从断点下载了,其实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!

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/464109.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows Forms、MFC、WTL、WxWidgets、Qt、GTK综合比较

图形界面库Windows Forms、 MFC、WTL、 WxWidgets、Qt、GTK 综合比较见下表: 总结: GTK主要用在X Window上,整个设计的架构和许多概念和MFC以及一般 Windows 上的程序开发大异其趣,入门门槛较高,而且最主要的特色是&am…

G3,是塔克和阿德巴约的热火队

这一场,是塔克和阿德巴约的热火队G2的比赛,波斯顿人的数据如下G3的比赛,波斯顿人的数据如下如果说田忌赛马可以用在篮球场上,那这场堪称经典热火和波斯顿人的第三场比赛,火队是客场作战,客场作战的热火在第…

微信小程序--数据存储

对本地缓存数据操作分为同步和异步两种。同步方法有成功回调函数,表示数 据处理成功后的操作。下面是小程序提供本地缓存操作接口: 以Sync结尾都是同步方法。同步方法和异步方法的区别是: 同步方法会堵塞当前任务,直到同步方法处理…

RTMPdump 源代码分析 1: main()函数

http://blog.csdn.net/leixiaohua1020/article/details/12952977 rtmpdump 是一个用来处理 RTMP 流媒体的工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps:// 等。之前在学习RTMP协议的时候,发现没有讲它源代码的,只好自己分…

项目分享| 自制巡线机器人

本次分享机器人项目由myyerrol制作,是他的第一个基于RISC-V的机器人项目。以下文字详细记录了机器人制作的软硬件组成、核心算法等,对机器人感兴趣的朋友可收藏,也可关注他的知乎账号(myyerrol)或GitHub(ht…

CUDA线程、线程块、线程束、流多处理器、流处理器、网格概念的深入理解

一.与CUDA相关的几个概念:thread,block,grid,warp,sp,sm。 sp: 最基本的处理单元,streaming processor 最后具体的指令和任务都是在sp上处理的。GPU进行并行计算,也就是很多个sp同时…

.Net环境下基于Ajax的MVC方案

现在,越来越多人开始尝试基于Ajax进行无刷新的Web开发,不过,在.Net环境下,应用Ajax并不是非常方便,这主要可能是由以下一些原因造成的: •由于Ajax基于javascript的本质,使得开发者必须对javasc…

一道C语言指针的题目

早上看到的题目,但是现在去找没看到了。我当时还把图片放在小号里面了,大家可以看看。大家看看这个代码输出什么?如果想让它输出77要怎么用指针写呢?凭记忆写一下#include "stdio.h"#include "stdint.h" int …

Linux IO多路复用之epoll网络编程,高并发的使用例子 (含源码)

本章节是用基本的Linux基本函数加上epoll调用编写一个完整的服务器和客户端例子,可在Linux上运行,客户端和服务端的功能如下: 客户端从标准输入读入一行,发送到服务端 服务端从网络读取一行,然后输出到客户端 客户端收到服务端…

c#中Excel数据的导入、导出

/**//// <summary>/// 导出到 Excel 文件/// </summary>/// <param name"fileName">含完整路径</param>/// <param name"dataTable">含字段标题名</param>publicvoidExpExcel(stringfileName ,DataTable dataTable){ …

你对Linux下的实时性应该多点了解

作者简介顺刚(网名:沐多)&#xff0c;一线码农&#xff0c;从事工控行业&#xff0c;目前在一家工业自动化公司从事工业实时现场总线开发工作&#xff0c;喜欢钻研Linux内核及xenomai&#xff0c;个人博客 wsg1100&#xff0c;欢迎大家关注&#xff01;本文讲述一些有利于提高x…

DHCP机制

DHCP概念&#xff1a;局域网的网络协议&#xff0c;使用UDP协议工作&#xff0c;在工作过程中&#xff0c;它有两个对象&#xff0c;DHCP客户端和DHCP服务端&#xff0c;DHCP服务运行在&#xff16;&#xff17;端口和&#xff16;&#xff18;端口。 用途&#xff1a;&#xf…

一号团队-团队任务3:每日立会(2018-12-01)

一.基本信息 团队序号&#xff1a;一号 开发的软件名称&#xff1a;Java教学官网 撰写人&#xff1a;张浩洋 学号:2016035107283 职务:项目经理 二.团队汇报 1.汇报文字版 一号团队全体成员与2018年12月01日在第一组项目群中针对2018年11月30日工作任务进行汇报&#xff0c;汇总…

40 岁的中年失业

大家好&#xff0c;我是写代码的篮球球痴&#xff0c;昨天发了一篇文章&#xff0c;有一个朋友转发了&#xff0c;然后看到有人评论&#xff0c;如下&#xff1a;前两天有朋友问我&#xff0c;说腾讯裁员好严重&#xff0c;还有如何如何&#xff0c;我是这样回答的。很多事情我…

Linux/Windows系统内核性能调优

做过Linux平台性能测试的童鞋平时可能会遇到如下问题&#xff1a; 1、 TCP端口号不够用导致并发上不去&#xff08;即与服务器端建立新连接失败&#xff09; 2、 TIME_WAIT状态连接过多导致应用服务器&#xff08;Nginx、Haproxy、Redis、Tomcat等&#xff09;性能下降或假死…

MCU复位和程序启动那些事

大家好&#xff0c;这篇文章转自电源漫谈&#xff0c;文章从专业的角度说明了MCU复位经过的过程&#xff0c;对大家学习很有帮助。MCU通常会在工作之前&#xff0c;先经历复位和启动的一个过程&#xff0c;在用户使用过程中往往在这个阶段的工作不是那么的清楚&#xff0c;这里…

若有所思

今天出来​逛了下&#xff0c;没有准备文章&#xff0c;刚好一个朋友有一段思考&#xff0c;给大家分享下。深圳动物园是一个值得去的地方​-----佚名海边的夏天&#xff0c;太阳公公当空照&#xff0c;照的大地滚烫&#xff0c;海水湛蓝。人们都躲到树荫下&#xff0c;享受舶风…

第三章 阴阳的工作机制(1)

一、道生一&#xff0c;一生二&#xff0c;二生三&#xff0c;三生万物1.易有太极&#xff0c;是生两仪上面&#xff0c;对阴阳已经讨论了很多&#xff0c;对这个问题已经有所了解。现在我们来讨论"阴阳的工作机制"&#xff0c;弄清楚这个以后对理解《伤寒论》的许多…

jquery 操作日期、星期、元素的追加

主要实现日期的显示&#xff0c;获取年月日&#xff0c;时分秒、星期、判断闰年<script language"javascript" >$(document).ready(function(){function show(){var mydatenew Date();var str "" mydate.getFullYear() "年"; …

简单易懂的芯片科普漫画,帮你打开高深的新技术大门

大家好&#xff0c;我是写代码的篮球球痴&#xff0c;今天给大家推荐一本新书&#xff0c;是华为麒麟团队出版的&#xff0c;主要是让大家了解芯片的结构&#xff0c;这对很多人理解计算机是非常有帮助的。希望大家喜欢。喜欢书籍的同学在文章下面评论&#xff0c;我们会选出评…