1. 程式人生 > >斷點續傳和下載原理分析

斷點續傳和下載原理分析

斷點續傳和下載原理分析

斷點續傳和斷點下載都是用的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;  
  26. import android.widget.TextView;  
  27. import android.widget.Toast;  
  28. /** 
  29.  *  
  30.  * @author Administrator 
  31.  * 
  32.  */  
  33. public class UploadActivity extends Activity implements OnClickListener{  
  34.     private static final String TAG = "SiteFileFetchActivity";  
  35.     private Button download, upload, select_file;  
  36.     private TextView info;  
  37.     private static final int PROGRESS_DIALOG = 0;  
  38.     private ProgressDialog progressDialog;  
  39.     private UploadThread uploadThread;  
  40.     private String uploadFilePath = null;  
  41.     private String fileName;  
  42.     /** Called when the activity is first created. */  
  43.     @Override  
  44.     public void onCreate(Bundle savedInstanceState) {  
  45.         super.onCreate(savedInstanceState);  
  46.         setContentView(R.layout.upload);  
  47.         initView();  
  48.     }  
  49.       
  50.     private void initView(){  
  51.         download = (Button) findViewById(R.id.download);  
  52.         download.setOnClickListener(this);  
  53.         upload = (Button) findViewById(R.id.upload);  
  54.         upload.setOnClickListener(this);  
  55.         info = (TextView) findViewById(R.id.info);  
  56.         select_file = (Button) findViewById(R.id.select_file);  
  57.         select_file.setOnClickListener(this);  
  58.     }  
  59.       
  60.     @Override  
  61.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  62.         // TODO Auto-generated method stub  
  63.         super.onActivityResult(requestCode, resultCode, data);  
  64.         if (resultCode == RESULT_OK) {  
  65.                   if (requestCode == 1) {  
  66.                            Uri uri = data.getData();    // 接收使用者所選檔案的路徑  
  67.                            info.setText("select: " + uri); // 在介面上顯示路徑  
  68.                            uploadFilePath = uri.getPath();  
  69.                            int last = uploadFilePath.lastIndexOf("/");  
  70.                            uploadFilePath = uri.getPath().substring(0, last+1);  
  71.                            fileName = uri.getLastPathSegment();  
  72.                   }  
  73.         }  
  74.     }  
  75.       
  76.     protected Dialog onCreateDialog(int id) {  
  77.         switch(id) {  
  78.         case PROGRESS_DIALOG:  
  79.             progressDialog = new ProgressDialog(UploadActivity.this);  
  80.             progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);  
  81.             progressDialog.setButton("暫停", new DialogInterface.OnClickListener() {  
  82.                 @Override  
  83.                 public void onClick(DialogInterface dialog, int which) {  
  84.                     // TODO Auto-generated method stub  
  85.                     uploadThread.closeLink();  
  86.                     dialog.dismiss();  
  87.                 }  
  88.             });  
  89.             progressDialog.setMessage("正在上傳...");  
  90.             progressDialog.setMax(100);  
  91.             return progressDialog;  
  92.         default:  
  93.             return null;  
  94.         }  
  95.     }  
  96.       
  97.     /**  
  98.      * 使用Handler給建立他的執行緒傳送訊息,  
  99.      * 匿名內部類  
  100.      */    
  101.     private Handler handler = new Handler()    
  102.     {    
  103.         @Override  
  104.         public void handleMessage(Message msg)     
  105.         {    
  106.             //獲得上傳長度的進度    
  107.             int length = msg.getData().getInt("size");    
  108.             progressDialog.setProgress(length);    
  109.             if(progressDialog.getProgress()==progressDialog.getMax())//上傳成功    
  110.             {    
  111.                 progressDialog.dismiss();  
  112.                 Toast.makeText(UploadActivity.this, getResources().getString(R.string.upload_over), 1).show();    
  113.             }    
  114.         }    
  115.     };     
  116.   
  117.     @Override  
  118.     public void onClick(View v) {  
  119.         // TODO Auto-generated method stub  
  120.         Resources r = getResources();  
  121.         switch(v.getId()){  
  122.             case R.id.select_file:  
  123.                 Intent intent = new Intent();  
  124.                 //設定起始目錄和查詢的型別  
  125.                 intent.setDataAndType(Uri.fromFile(new File("/sdcard")), "*/*");//"*/*"表示所有型別,設定起始資料夾和檔案型別  
  126.                 intent.setClass(UploadActivity.this, FileBrowserActivity.class);  
  127.                 startActivityForResult(intent, 1);  
  128.                 break;  
  129.             case R.id.download:  
  130.                 startActivity(new Intent(UploadActivity.this, SmartDownloadActivity.class));  
  131.                 break;  
  132.             case R.id.upload:  
  133.                 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))//判斷SDCard是否存在    
  134.                 {    
  135.                     if(uploadFilePath == null){  
  136.                         Toast.makeText(UploadActivity.this, "還沒設定上傳檔案", 1).show();  
  137.                     }  
  138.                     System.out.println("uploadFilePath:"+uploadFilePath+" "+fileName);  
  139.                     //取得SDCard的目錄    
  140.                     File uploadFile = new File(new File(uploadFilePath), fileName);    
  141.                     Log.i(TAG, "filePath:"+uploadFile.toString());  
  142.                     if(uploadFile.exists())    
  143.                     {    
  144.                         showDialog(PROGRESS_DIALOG);  
  145.                         info.setText(uploadFile+" "+ConstantValues.HOST+":"+ConstantValues.PORT);  
  146.                         progressDialog.setMax((int) uploadFile.length());//設定長傳檔案的最大刻度  
  147.                         uploadThread = new UploadThread(UploadActivity.this, uploadFile, ConstantValues.HOST, ConstantValues.PORT);  
  148.                         uploadThread.setListener(new UploadProgressListener() {  
  149.                               
  150.                             @Override  
  151.                             public void onUploadSize(int size) {  
  152.                                 // TODO Auto-generated method stub  
  153.                                 Message msg = new Message();  
  154.                                 msg.getData().putInt("size", size);  
  155.                                 handler.sendMessage(msg);  
  156.                             }  
  157.                         });  
  158.                         uploadThread.start();  
  159.                     }    
  160.                     else    
  161.                     {    
  162.                         Toast.makeText(UploadActivity.this, "檔案不存在", 1).show();    
  163.                     }    
  164.                 }    
  165.                 else    
  166.                 {    
  167.                     Toast.makeText(UploadActivity.this, "SDCard不存在!", 1).show();    
  168.                 }    
  169.                 break;  
  170.         }  
  171.               
  172.     }  
  173.       
  174.       
  175. }  

 UploadThread.java

 

Java程式碼  收藏程式碼

  1. package com.hao.upload;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.io.OutputStream;  
  7. import java.io.RandomAccessFile;  
  8. import java.net.Socket;  
  9.   
  10. import android.content.Context;  
  11. import android.util.Log;  
  12.   
  13. import com.hao.db.UploadLogService;  
  14. import com.hao.util.StreamTool;  
  15.   
  16. public class UploadThread extends Thread {  
  17.   
  18.     private static final String TAG = "UploadThread";  
  19.     /*需要上傳檔案的路徑*/  
  20.     private File uploadFile;  
  21.     /*上傳檔案伺服器的IP地址*/  
  22.     private String dstName;  
  23.     /*上傳伺服器埠號*/  
  24.     private int dstPort;  
  25.     /*上傳socket連結*/  
  26.     private Socket socket;  
  27.     /*儲存上傳的資料庫*/  
  28.     private UploadLogService logService;   
  29.     private UploadProgressListener listener;  
  30.     public UploadThread(Context context, File uploadFile, final String dstName,final int dstPort){  
  31.         this.uploadFile = uploadFile;  
  32.         this.dstName = dstName;  
  33.         this.dstPort = dstPort;  
  34.         logService = new UploadLogService(context);  
  35.     }  
  36.       
  37.     public void setListener(UploadProgressListener listener) {  
  38.         this.listener = listener;  
  39.     }  
  40.   
  41.     /** 
  42.      * 模擬斷開連線 
  43.      */  
  44.     public void closeLink(){  
  45.         try{  
  46.             if(socket != null) socket.close();  
  47.         }catch(IOException e){  
  48.             e.printStackTrace();  
  49.             Log.e(TAG, "close socket fail");  
  50.         }  
  51.     }  
  52.   
  53.     @Override  
  54.     public void run() {  
  55.         // TODO Auto-generated method stub  
  56.         try {  
  57.             // 判斷檔案是否已有上傳記錄  
  58.             String souceid = logService.getBindId(uploadFile);  
  59.             // 構造拼接協議  
  60.             String head = "Content-Length=" + uploadFile.length()  
  61.                     + ";filename=" + uploadFile.getName() + ";sourceid="  
  62.                     + (souceid == null ? "" : souceid) + "%";  
  63.             // 通過Socket取得輸出流  
  64.             socket = new Socket(dstName, dstPort);  
  65.             OutputStream outStream = socket.getOutputStream();  
  66.             outStream.write(head.getBytes());  
  67.             Log.i(TAG, "write to outStream");  
  68.   
  69.             InputStream inStream = socket.getInputStream();  
  70.             // 獲取到字元流的id與位置  
  71.             String response = StreamTool.readLine(inStream);  
  72.             Log.i(TAG, "response:" + response);  
  73.             String[] items = response.split(";");  
  74.             String responseid = items[0].substring(items[0].indexOf("=") + 1);  
  75.             String position = items[1].substring(items[1].indexOf("=") + 1);  
  76.             // 代表原來沒有上傳過此檔案,往資料庫新增一條繫結記錄  
  77.             if (souceid == null) {  
  78.                 logService.save(responseid, uploadFile);  
  79.             }  
  80.             RandomAccessFile fileOutStream = new RandomAccessFile(uploadFile, "r");  
  81.             // 查詢上次傳送的最終位置,並從這開始傳送  
  82.             fileOutStream.seek(Integer.valueOf(position));  
  83.             byte[] buffer = new byte[1024];  
  84.             int len = -1;  
  85.             // 初始化上傳的資料長度  
  86.             int length = Integer.valueOf(position);  
  87.             while ((len = fileOutStream.read(buffer)) != -1) {  
  88.                 outStream.write(buffer, 0, len);  
  89.                 // 設定長傳資料長度  
  90.                 length += len;  
  91.                 listener.onUploadSize(length);  
  92.             }  
  93.             fileOutStream.close();  
  94.             outStream.close();  
  95.             inStream.close();  
  96.             socket.close();  
  97.             // 判斷上傳完則刪除資料  
  98.             if (length == uploadFile.length())  
  99.                 logService.delete(uploadFile);  
  100.       &