断点续传和下载原理分析

Posted LOVE SHARE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了断点续传和下载原理分析相关的知识,希望对你有一定的参考价值。

断点续传和断点下载都是用的RandomAccessFile, 它具有移动指定的文件大小的位置的功能seek 。

断点续传是由服务器给客户端一个已经上传的位置标记position,然后客户端再将文件指针移动到相应的position,通过输入流将文件剩余部分读出来传输给服务器

断点下载 是由客户端告诉服务器已经下载的大小,然后服务器会将指针移动到相应的position,继续读出,把文件返回给客户端。 当然为了下载的更快一下,也可以多线程下载,那么基本实现就是给每个线程分配固定的字节的文件,分别去读

 

首先是文件上传,这个要用到服务器


技术分享

关键代码:


 FileServer.java

 

Java代码  技术分享
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileOutputStream;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.io.OutputStream;  
  7. import java.io.PushbackInputStream;  
  8. import java.io.RandomAccessFile;  
  9. import java.net.ServerSocket;  
  10. import java.net.Socket;  
  11. import java.text.SimpleDateFormat;  
  12. import java.util.Date;  
  13. import java.util.HashMap;  
  14. import java.util.Map;  
  15. import java.util.Properties;  
  16. import java.util.Set;  
  17. import java.util.concurrent.ExecutorService;  
  18. import java.util.concurrent.Executors;  
  19.   
  20. import util.FileLogInfo;  
  21. import util.StreamTool;  
  22.   
  23.   
  24.   
  25. public class FileServer {  
  26.      private ExecutorService executorService;//线程池  
  27.      private int port;//监听端口  
  28.      private boolean quit = false;//退出  
  29.      private ServerSocket server;  
  30.      private Map<Long, FileLogInfo> datas = new HashMap<Long, FileLogInfo>();//存放断点数据,以后改为数据库存放  
  31.      public FileServer(int port)  
  32.      {  
  33.          this.port = port;  
  34.          //创建线程池,池中具有(cpu个数*50)条线程  
  35.          executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50);  
  36.      }  
  37.        
  38.     /** 
  39.       * 退出 
  40.       */  
  41.      public void quit()  
  42.      {  
  43.         this.quit = true;  
  44.         try   
  45.         {  
  46.             server.close();  
  47.         }catch (IOException e)   
  48.         {  
  49.             e.printStackTrace();  
  50.         }  
  51.      }  
  52.        
  53.      /** 
  54.       * 启动服务 
  55.       * @throws Exception 
  56.       */  
  57.      public void start() throws Exception  
  58.      {  
  59.          server = new ServerSocket(port);//实现端口监听  
  60.          while(!quit)  
  61.          {  
  62.              try   
  63.              {  
  64.                Socket socket = server.accept();  
  65.                executorService.execute(new SocketTask(socket));//为支持多用户并发访问,采用线程池管理每一个用户的连接请求  
  66.              }catch (Exception e)   
  67.              {  
  68.                  e.printStackTrace();  
  69.              }  
  70.          }  
  71.      }  
  72.        
  73.      private final class SocketTask implements Runnable  
  74.      {  
  75.         private Socket socket = null;  
  76.         public SocketTask(Socket socket)   
  77.         {  
  78.             this.socket = socket;  
  79.         }  
  80.         @Override  
  81.         public void run()   
  82.         {  
  83.             try   
  84.             {  
  85.                 System.out.println("FileServer accepted connection "+ socket.getInetAddress()+ ":"+ socket.getPort());  
  86.                 //得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid=  
  87.                 //如果用户初次上传文件,sourceid的值为空。  
  88.                 InputStream inStream = socket.getInputStream();  
  89.                 String head = StreamTool.readLine(inStream);  
  90.                 System.out.println("FileServer head:"+head);  
  91.                 if(head!=null)  
  92.                 {  
  93.                     //下面从协议数据中提取各项参数值  
  94.                     String[] items = head.split(";");  
  95.                     String filelength = items[0].substring(items[0].indexOf("=")+1);  
  96.                     String filename = items[1].substring(items[1].indexOf("=")+1);  
  97.                     String sourceid = items[2].substring(items[2].indexOf("=")+1);        
  98.                     //生成资源id,如果需要唯一性,可以采用UUID  
  99.                     long id = System.currentTimeMillis();  
  100.                     FileLogInfo log = null;  
  101.                     if(sourceid!=null && !"".equals(sourceid))  
  102.                     {  
  103.                         id = Long.valueOf(sourceid);  
  104.                         //查找上传的文件是否存在上传记录  
  105.                         log = find(id);  
  106.                     }  
  107.                     File file = null;  
  108.                     int position = 0;  
  109.                     //如果上传的文件不存在上传记录,为文件添加跟踪记录  
  110.                     if(log==null)  
  111.                     {  
  112.                         //设置存放的位置与当前应用的位置有关  
  113.                         File dir = new File("c:/temp/");  
  114.                         if(!dir.exists()) dir.mkdirs();  
  115.                         file = new File(dir, filename);  
  116.                         //如果上传的文件发生重名,然后进行改名  
  117.                         if(file.exists())  
  118.                         {  
  119.                             filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf("."));  
  120.                             file = new File(dir, filename);  
  121.                         }  
  122.                         save(id, file);  
  123.                     }  
  124.                     // 如果上传的文件存在上传记录,读取上次的断点位置  
  125.                     else  
  126.                     {  
  127.                         System.out.println("FileServer have exits log not null");  
  128.                         //从上传记录中得到文件的路径  
  129.                         file = new File(log.getPath());  
  130.                         if(file.exists())  
  131.                         {  
  132.                             File logFile = new File(file.getParentFile(), file.getName()+".log");  
  133.                             if(logFile.exists())  
  134.                             {  
  135.                                 Properties properties = new Properties();  
  136.                                 properties.load(new FileInputStream(logFile));  
  137.                                 //读取断点位置  
  138.                                 position = Integer.valueOf(properties.getProperty("length"));  
  139.                             }  
  140.                         }  
  141.                     }  
  142.                     //***************************上面是对协议头的处理,下面正式接收数据***************************************  
  143.                     //向客户端请求传输数据  
  144.                     OutputStream outStream = socket.getOutputStream();  
  145.                     String response = "sourceid="+ id+ ";position="+ position+ "%";  
  146.                     //服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=position  
  147.                     //sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传  
  148.                     outStream.write(response.getBytes());  
  149.                     RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");  
  150.                     //设置文件长度  
  151.                     if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));  
  152.                     //移动文件指定的位置开始写入数据  
  153.                     fileOutStream.seek(position);  
  154.                     byte[] buffer = new byte[1024];  
  155.                     int len = -1;  
  156.                     int length = position;  
  157.                     //从输入流中读取数据写入到文件中,并将已经传入的文件长度写入配置文件,实时记录文件的最后保存位置  
  158.                     while( (len=inStream.read(buffer)) != -1)  
  159.                     {  
  160.                         fileOutStream.write(buffer, 0, len);  
  161.                         length += len;  
  162.                         Properties properties = new Properties();  
  163.                         properties.put("length", String.valueOf(length));  
  164.                         FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));  
  165.                         //实时记录文件的最后保存位置  
  166.                         properties.store(logFile, null);  
  167.                         logFile.close();  
  168.                     }  
  169.                     //如果长传长度等于实际长度则表示长传成功  
  170.                     if(length==fileOutStream.length()){  
  171.                         delete(id);  
  172.                     }  
  173.                     fileOutStream.close();                    
  174.                     inStream.close();  
  175.                     outStream.close();  
  176.                     file = null;  
  177.                 }  
  178.             }  
  179.             catch (Exception e)   
  180.             {  
  181.                 e.printStackTrace();  
  182.             }  
  183.             finally{  
  184.                 try  
  185.                 {  
  186.                     if(socket!=null && !socket.isClosed()) socket.close();  
  187.                 }   
  188.                 catch (IOException e)  
  189.                 {  
  190.                     e.printStackTrace();  
  191.                 }  
  192.             }  
  193.         }  
  194.      }  
  195.        
  196.      /**  
  197.       * 查找在记录中是否有sourceid的文件  
  198.       * @param sourceid  
  199.       * @return  
  200.       */  
  201.      public FileLogInfo find(Long sourceid)  
  202.      {  
  203.          return datas.get(sourceid);  
  204.      }  
  205.        
  206.      /** 
  207.       * 保存上传记录,日后可以改成通过数据库存放 
  208.       * @param id 
  209.       * @param saveFile 
  210.       */  
  211.      public void save(Long id, File saveFile)  
  212.      {  
  213.          System.out.println("save logfile "+id);  
  214.          datas.put(id, new FileLogInfo(id, saveFile.getAbsolutePath()));  
  215.      }  
  216.        
  217.      /** 
  218.       * 当文件上传完毕,删除记录 
  219.       * @param sourceid 
  220.       */  
  221.      public void delete(long sourceid)  
  222.      {  
  223.          System.out.println("delete logfile "+sourceid);  
  224.          if(datas.containsKey(sourceid)) datas.remove(sourceid);  
  225.      }  
  226.        
  227. }  

 由于在上面的流程图中已经进行了详细的分析,我在这儿就不讲了,只是在存储数据的时候服务器没有用数据库去存储,这儿只是为了方便,所以要想测试断点上传,服务器是不能停的,否则数据就没有了,在以后改进的时候应该用数据库去存储数据。

文件上传客户端:


技术分享

关键代码:

UploadActivity.java

 

Java代码  技术分享
  1. package com.hao;  
  2.   
  3. import java.io.File;  
  4. import java.util.List;  
  5.   
  6. import com.hao.upload.UploadThread;  
  7. import com.hao.upload.UploadThread.UploadProgressListener;  
  8. import com.hao.util.ConstantValues;  
  9. import com.hao.util.FileBrowserActivity;  
  10.   
  11. import android.app.Activity;  
  12. import android.app.Dialog;  
  13. import android.app.ProgressDialog;  
  14. import android.content.DialogInterface;  
  15. import android.content.Intent;  
  16. import android.content.res.Resources;  
  17. import android.net.Uri;  
  18. import android.os.Bundle;  
  19. import android.os.Environment;  
  20. import android.os.Handler;  
  21. import android.os.Message;  
  22. import android.util.Log;  
  23. import android.view.View;  
  24. import android.view.View.OnClickListener;  
  25. import android.widget.Button; &nb

以上是关于断点续传和下载原理分析的主要内容,如果未能解决你的问题,请参考以下文章

基于http的断点续传和多线程下载

http文件的断点续传和下载

Unity 使用UnityWebRequest下载超大资源,实现断点续传和分段下载。

Java如何实现大文件分片上传,断点续传和秒传

断点续传和分块上传

用C实现断点续传的功能,详细点的实现原理是啥嘞