斷點續傳和下載原理分析
阿新 • • 發佈:2018-11-07
斷點續傳和下載原理分析
斷點續傳和斷點下載都是用的RandomAccessFile, 它具有移動指定的檔案大小的位置的功能seek 。
斷點續傳是由伺服器給客戶端一個已經上傳的位置標記position,然後客戶端再將檔案指標移動到相應的position,通過輸入流將檔案剩餘部分讀出來傳輸給伺服器
斷點下載 是由客戶端告訴伺服器已經下載的大小,然後伺服器會將指標移動到相應的position,繼續讀出,把檔案返回給客戶端。 當然為了下載的更快一下,也可以多執行緒下載,那麼基本實現就是給每個執行緒分配固定的位元組的檔案,分別去讀
首先是檔案上傳,這個要用到伺服器
關鍵程式碼:
FileServer.java
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.PushbackInputStream;
- import java.io.RandomAccessFile;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Properties;
- import java.util.Set;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import util.FileLogInfo;
- import util.StreamTool;
- public class FileServer {
- private ExecutorService executorService;//執行緒池
- private int port;//監聽埠
- private boolean quit = false;//退出
- private ServerSocket server;
- private Map<Long, FileLogInfo> datas = new HashMap<Long, FileLogInfo>();//存放斷點資料,以後改為資料庫存放
- public FileServer(int port)
- {
- this.port = port;
- //建立執行緒池,池中具有(cpu個數*50)條執行緒
- executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50);
- }
- /**
- * 退出
- */
- public void quit()
- {
- this.quit = true;
- try
- {
- server.close();
- }catch (IOException e)
- {
- e.printStackTrace();
- }
- }
- /**
- * 啟動服務
- * @throws Exception
- */
- public void start() throws Exception
- {
- server = new ServerSocket(port);//實現埠監聽
- while(!quit)
- {
- try
- {
- Socket socket = server.accept();
- executorService.execute(new SocketTask(socket));//為支援多使用者併發訪問,採用執行緒池管理每一個使用者的連線請求
- }catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- }
- private final class SocketTask implements Runnable
- {
- private Socket socket = null;
- public SocketTask(Socket socket)
- {
- this.socket = socket;
- }
- @Override
- public void run()
- {
- try
- {
- System.out.println("FileServer accepted connection "+ socket.getInetAddress()+ ":"+ socket.getPort());
- //得到客戶端發來的第一行協議資料:Content-Length=143253434;filename=xxx.3gp;sourceid=
- //如果使用者初次上傳檔案,sourceid的值為空。
- InputStream inStream = socket.getInputStream();
- String head = StreamTool.readLine(inStream);
- System.out.println("FileServer head:"+head);
- if(head!=null)
- {
- //下面從協議資料中提取各項引數值
- String[] items = head.split(";");
- String filelength = items[0].substring(items[0].indexOf("=")+1);
- String filename = items[1].substring(items[1].indexOf("=")+1);
- String sourceid = items[2].substring(items[2].indexOf("=")+1);
- //生成資源id,如果需要唯一性,可以採用UUID
- long id = System.currentTimeMillis();
- FileLogInfo log = null;
- if(sourceid!=null && !"".equals(sourceid))
- {
- id = Long.valueOf(sourceid);
- //查詢上傳的檔案是否存在上傳記錄
- log = find(id);
- }
- File file = null;
- int position = 0;
- //如果上傳的檔案不存在上傳記錄,為檔案新增跟蹤記錄
- if(log==null)
- {
- //設定存放的位置與當前應用的位置有關
- File dir = new File("c:/temp/");
- if(!dir.exists()) dir.mkdirs();
- file = new File(dir, filename);
- //如果上傳的檔案發生重名,然後進行改名
- if(file.exists())
- {
- filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf("."));
- file = new File(dir, filename);
- }
- save(id, file);
- }
- // 如果上傳的檔案存在上傳記錄,讀取上次的斷點位置
- else
- {
- System.out.println("FileServer have exits log not null");
- //從上傳記錄中得到檔案的路徑
- file = new File(log.getPath());
- if(file.exists())
- {
- File logFile = new File(file.getParentFile(), file.getName()+".log");
- if(logFile.exists())
- {
- Properties properties = new Properties();
- properties.load(new FileInputStream(logFile));
- //讀取斷點位置
- position = Integer.valueOf(properties.getProperty("length"));
- }
- }
- }
- //***************************上面是對協議頭的處理,下面正式接收資料***************************************
- //向客戶端請求傳輸資料
- OutputStream outStream = socket.getOutputStream();
- String response = "sourceid="+ id+ ";position="+ position+ "%";
- //伺服器收到客戶端的請求資訊後,給客戶端返回響應資訊:sourceid=1274773833264;position=position
- //sourceid由服務生成,唯一標識上傳的檔案,position指示客戶端從檔案的什麼位置開始上傳
- outStream.write(response.getBytes());
- RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");
- //設定檔案長度
- if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));
- //移動檔案指定的位置開始寫入資料
- fileOutStream.seek(position);
- byte[] buffer = new byte[1024];
- int len = -1;
- int length = position;
- //從輸入流中讀取資料寫入到檔案中,並將已經傳入的檔案長度寫入配置檔案,實時記錄檔案的最後儲存位置
- while( (len=inStream.read(buffer)) != -1)
- {
- fileOutStream.write(buffer, 0, len);
- length += len;
- Properties properties = new Properties();
- properties.put("length", String.valueOf(length));
- FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));
- //實時記錄檔案的最後儲存位置
- properties.store(logFile, null);
- logFile.close();
- }
- //如果長傳長度等於實際長度則表示長傳成功
- if(length==fileOutStream.length()){
- delete(id);
- }
- fileOutStream.close();
- inStream.close();
- outStream.close();
- file = null;
- }
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- finally{
- try
- {
- if(socket!=null && !socket.isClosed()) socket.close();
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- }
- }
- }
- /**
- * 查詢在記錄中是否有sourceid的檔案
- * @param sourceid
- * @return
- */
- public FileLogInfo find(Long sourceid)
- {
- return datas.get(sourceid);
- }
- /**
- * 儲存上傳記錄,日後可以改成通過資料庫存放
- * @param id
- * @param saveFile
- */
- public void save(Long id, File saveFile)
- {
- System.out.println("save logfile "+id);
- datas.put(id, new FileLogInfo(id, saveFile.getAbsolutePath()));
- }
- /**
- * 當檔案上傳完畢,刪除記錄
- * @param sourceid
- */
- public void delete(long sourceid)
- {
- System.out.println("delete logfile "+sourceid);
- if(datas.containsKey(sourceid)) datas.remove(sourceid);
- }
- }
由於在上面的流程圖中已經進行了詳細的分析,我在這兒就不講了,只是在儲存資料的時候伺服器沒有用資料庫去儲存,這兒只是為了方便,所以要想測試斷點上傳,伺服器是不能停的,否則資料就沒有了,在以後改進的時候應該用資料庫去儲存資料。
檔案上傳客戶端:
關鍵程式碼:
UploadActivity.java
- package com.hao;
- import java.io.File;
- import java.util.List;
- import com.hao.upload.UploadThread;
- import com.hao.upload.UploadThread.UploadProgressListener;
- import com.hao.util.ConstantValues;
- import com.hao.util.FileBrowserActivity;
- import android.app.Activity;
- import android.app.Dialog;
- import android.app.ProgressDialog;
- import android.content.DialogInterface;
- import android.content.Intent;
- import android.content.res.Resources;
- import android.net.Uri;
- import android.os.Bundle;
- import android.os.Environment;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.TextView;
- import android.widget.Toast;
- /**
- *
- * @author Administrator
- *
- */
- public class UploadActivity extends Activity implements OnClickListener{
- private static final String TAG = "SiteFileFetchActivity";
- private Button download, upload, select_file;
- private TextView info;
- private static final int PROGRESS_DIALOG = 0;
- private ProgressDialog progressDialog;
- private UploadThread uploadThread;
- private String uploadFilePath = null;
- private String fileName;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.upload);
- initView();
- }
- private void initView(){
- download = (Button) findViewById(R.id.download);
- download.setOnClickListener(this);
- upload = (Button) findViewById(R.id.upload);
- upload.setOnClickListener(this);
- info = (TextView) findViewById(R.id.info);
- select_file = (Button) findViewById(R.id.select_file);
- select_file.setOnClickListener(this);
- }
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- // TODO Auto-generated method stub
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == RESULT_OK) {
- if (requestCode == 1) {
- Uri uri = data.getData(); // 接收使用者所選檔案的路徑
- info.setText("select: " + uri); // 在介面上顯示路徑
- uploadFilePath = uri.getPath();
- int last = uploadFilePath.lastIndexOf("/");
- uploadFilePath = uri.getPath().substring(0, last+1);
- fileName = uri.getLastPathSegment();
- }
- }
- }
- protected Dialog onCreateDialog(int id) {
- switch(id) {
- case PROGRESS_DIALOG:
- progressDialog = new ProgressDialog(UploadActivity.this);
- progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- progressDialog.setButton("暫停", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // TODO Auto-generated method stub
- uploadThread.closeLink();
- dialog.dismiss();
- }
- });
- progressDialog.setMessage("正在上傳...");
- progressDialog.setMax(100);
- return progressDialog;
- default:
- return null;
- }
- }
- /**
- * 使用Handler給建立他的執行緒傳送訊息,
- * 匿名內部類
- */
- private Handler handler = new Handler()
- {
- @Override
- public void handleMessage(Message msg)
- {
- //獲得上傳長度的進度
- int length = msg.getData().getInt("size");
- progressDialog.setProgress(length);
- if(progressDialog.getProgress()==progressDialog.getMax())//上傳成功
- {
- progressDialog.dismiss();
- Toast.makeText(UploadActivity.this, getResources().getString(R.string.upload_over), 1).show();
- }
- }
- };
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- Resources r = getResources();
- switch(v.getId()){
- case R.id.select_file:
- Intent intent = new Intent();
- //設定起始目錄和查詢的型別
- intent.setDataAndType(Uri.fromFile(new File("/sdcard")), "*/*");//"*/*"表示所有型別,設定起始資料夾和檔案型別
- intent.setClass(UploadActivity.this, FileBrowserActivity.class);
- startActivityForResult(intent, 1);
- break;
- case R.id.download:
- startActivity(new Intent(UploadActivity.this, SmartDownloadActivity.class));
- break;
- case R.id.upload:
- if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))//判斷SDCard是否存在
- {
- if(uploadFilePath == null){
- Toast.makeText(UploadActivity.this, "還沒設定上傳檔案", 1).show();
- }
- System.out.println("uploadFilePath:"+uploadFilePath+" "+fileName);
- //取得SDCard的目錄
- File uploadFile = new File(new File(uploadFilePath), fileName);
- Log.i(TAG, "filePath:"+uploadFile.toString());
- if(uploadFile.exists())
- {
- showDialog(PROGRESS_DIALOG);
- info.setText(uploadFile+" "+ConstantValues.HOST+":"+ConstantValues.PORT);
- progressDialog.setMax((int) uploadFile.length());//設定長傳檔案的最大刻度
- uploadThread = new UploadThread(UploadActivity.this, uploadFile, ConstantValues.HOST, ConstantValues.PORT);
- uploadThread.setListener(new UploadProgressListener() {
- @Override
- public void onUploadSize(int size) {
- // TODO Auto-generated method stub
- Message msg = new Message();
- msg.getData().putInt("size", size);
- handler.sendMessage(msg);
- }
- });
- uploadThread.start();
- }
- else
- {
- Toast.makeText(UploadActivity.this, "檔案不存在", 1).show();
- }
- }
- else
- {
- Toast.makeText(UploadActivity.this, "SDCard不存在!", 1).show();
- }
- break;
- }
- }
- }
UploadThread.java
- package com.hao.upload;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.RandomAccessFile;
- import java.net.Socket;
- import android.content.Context;
- import android.util.Log;
- import com.hao.db.UploadLogService;
- import com.hao.util.StreamTool;
- public class UploadThread extends Thread {
- private static final String TAG = "UploadThread";
- /*需要上傳檔案的路徑*/
- private File uploadFile;
- /*上傳檔案伺服器的IP地址*/
- private String dstName;
- /*上傳伺服器埠號*/
- private int dstPort;
- /*上傳socket連結*/
- private Socket socket;
- /*儲存上傳的資料庫*/
- private UploadLogService logService;
- private UploadProgressListener listener;
- public UploadThread(Context context, File uploadFile, final String dstName,final int dstPort){
- this.uploadFile = uploadFile;
- this.dstName = dstName;
- this.dstPort = dstPort;
- logService = new UploadLogService(context);
- }
- public void setListener(UploadProgressListener listener) {
- this.listener = listener;
- }
- /**
- * 模擬斷開連線
- */
- public void closeLink(){
- try{
- if(socket != null) socket.close();
- }catch(IOException e){
- e.printStackTrace();
- Log.e(TAG, "close socket fail");
- }
- }
- @Override
- public void run() {
- // TODO Auto-generated method stub
- try {
- // 判斷檔案是否已有上傳記錄
- String souceid = logService.getBindId(uploadFile);
- // 構造拼接協議
- String head = "Content-Length=" + uploadFile.length()
- + ";filename=" + uploadFile.getName() + ";sourceid="
- + (souceid == null ? "" : souceid) + "%";
- // 通過Socket取得輸出流
- socket = new Socket(dstName, dstPort);
- OutputStream outStream = socket.getOutputStream();
- outStream.write(head.getBytes());
- Log.i(TAG, "write to outStream");
- InputStream inStream = socket.getInputStream();
- // 獲取到字元流的id與位置
- String response = StreamTool.readLine(inStream);
- Log.i(TAG, "response:" + response);
- String[] items = response.split(";");
- String responseid = items[0].substring(items[0].indexOf("=") + 1);
- String position = items[1].substring(items[1].indexOf("=") + 1);
- // 代表原來沒有上傳過此檔案,往資料庫新增一條繫結記錄
- if (souceid == null) {
- logService.save(responseid, uploadFile);
- }
- RandomAccessFile fileOutStream = new RandomAccessFile(uploadFile, "r");
- // 查詢上次傳送的最終位置,並從這開始傳送
- fileOutStream.seek(Integer.valueOf(position));
- byte[] buffer = new byte[1024];
- int len = -1;
- // 初始化上傳的資料長度
- int length = Integer.valueOf(position);
- while ((len = fileOutStream.read(buffer)) != -1) {
- outStream.write(buffer, 0, len);
- // 設定長傳資料長度
- length += len;
- listener.onUploadSize(length);
- }
- fileOutStream.close();
- outStream.close();
- inStream.close();
- socket.close();
- // 判斷上傳完則刪除資料
- if (length == uploadFile.length())
- logService.delete(uploadFile);
- &