網絡編程之PC版與Android手機版帶斷點續傳的多線程下載


一、多線程下載

        多線程下載就是搶占服務器資源
        原理:服務器CPU 分配給每條線程的時間片相同,服務器帶寬平均分配給每條線程,所以客戶端開啟的線程越多,就能搶占到更多的服務器資源。
        
       1、設置開啟線程數,發送http請求到下載地址,獲取下載文件的總長度
          然后創建一個長度一致的臨時文件,避免下載到一半存儲空間不夠了,並計算每個線程下載多少數據       
       2、計算每個線程下載數據的開始和結束位置
          再次發送請求,用 Range 頭請求開始位置和結束位置的數據
       3、將下載到的數據,存放至臨時文件中
       4、帶斷點續傳的多線程下載
          定義一個int變量,記錄每條線程下載的數據總長度,然后加上該線程的下載開始位置,得到的結果就是下次下載時,該線程的開始位置,把得到的結果存入緩存文件,當文件下載完成,刪除臨時進度文件。
  1 public class MultiDownload {
  2 
  3     static int ThreadCount = 3;
  4     static int finishedThread = 0;
  5     //確定下載地址
  6     static String filename = "EditPlus.exe";
  7     static String path = "http://192.168.3.45:8080/"+filename;
  8     public static void main(String[] args) {
  9         //1、發送get請求,去獲得下載文件的長度
 10         try {
 11             URL url = new URL(path);
 12             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 13             conn.setRequestMethod("GET");
 14             conn.setConnectTimeout(5000);
 15             conn.setReadTimeout(5000);
 16             
 17             if (conn.getResponseCode()==200) {
 18                 //如果請求成功,拿到所請求資源文件的長度
 19                 int length = conn.getContentLength();
 20                 
 21                 //2、生成一個與原文件同樣的大小的臨時文件,以免下載一半存儲空間不夠了
 22                 File file = new File(filename);//演示,所以將保存的文件目錄放在工程的同目錄
 23                 //使用RandomAccessFile 生成臨時文件,可以用指針定位文件的任意位置,
 24                 //而且能夠實時寫到硬件底層設備,略過緩存,這對下載文件是突然斷電等意外是有好處的
 25                 RandomAccessFile  raf = new RandomAccessFile(file, "rwd");//rwd, 實時寫到底層設備
 26                 //設置臨時文件的大小
 27                 raf.setLength(length);
 28                 raf.close();
 29                 
 30                 //3、計算出每個線程應該下載多少個字節
 31                 int size = length/ThreadCount;//如果有余數,負責最后一部分的線程負責下砸
 32                 
 33                 //開啟多線程
 34                 for (int threadId = 0; threadId < ThreadCount; threadId++) {
 35                     //計算每個線程下載的開始位置和結束位置
 36                     int startIndex = threadId*size;  // 開始 = 線程id * size
 37                     int endIndex = (threadId+1)*size - 1; //結束 = (線程id + 1)*size - 1
 38                     //如果是最后一個線程,那么結束位置寫死為文件結束位置
 39                     if (threadId == ThreadCount - 1) {
 40                         endIndex = length - 1;
 41                     }
 42                     
 43                     //System.out.println("線程"+threadId+"的下載區間是: "+startIndex+"----"+endIndex);
 44                     new DownloadThread(startIndex,endIndex,threadId).start();
 45                 }
 46             }
 47         } catch (MalformedURLException e) {
 48             e.printStackTrace();
 49         } catch (IOException e) {
 50             e.printStackTrace();
 51         }
 52     }
 53 }
 54 
 55 class DownloadThread extends Thread{
 56     
 57     private int startIndex;
 58     private int endIndex;
 59     private int threadId;
 60 
 61     public DownloadThread(int startIndex, int endIndex, int threadId) {
 62         super();
 63         this.startIndex = startIndex;
 64         this.endIndex = endIndex;
 65         this.threadId = threadId;
 66     }
 67 
 68     @Override
 69     public void run() {
 70          //每個線程再次發送http請求,下載自己對應的那部分數據
 71         try {
 72             File progressFile = new File(threadId+".txt");
 73             //判斷進度文件是否存在,如果存在,則接着斷點繼續下載,如果不存在,則從頭下載
 74             if (progressFile.exists()) {
 75                 FileInputStream fis = new FileInputStream(progressFile);
 76                 BufferedReader br = new BufferedReader(new InputStreamReader(fis));
 77                 //從進度文件中度取出上一次下載的總進度,然后與原本的開始進度相加,得到新的開始進度
 78                 startIndex += Integer.parseInt(br.readLine());
 79                 fis.close();
 80             }
 81             System.out.println("線程"+threadId+"的下載區間是:"+startIndex+"----"+endIndex);
 82             
 83             //4、每個線程發送http請求自己的數據
 84             URL url = new URL(MultiDownload.path);
 85             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 86             conn.setRequestMethod("GET");
 87             conn.setConnectTimeout(5000);
 88             conn.setReadTimeout(5000);
 89             //設置本次http請求所請求的數據的區間
 90             conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
 91             
 92             //請求部分數據,響應碼是206
 93             if (conn.getResponseCode()==206) {
 94                 //此時,流里只有ThreadCount分之一的原文件數據
 95                 InputStream is = conn.getInputStream();
 96                 byte[] b = new byte[1024];
 97                 int len = 0;
 98                 int total = 0;//total 用於保存斷點續傳的斷點
 99                 //拿到臨時文件的輸出流
100                 File file = new File(MultiDownload.filename);
101                 RandomAccessFile raf = new RandomAccessFile(file, "rwd");
102                 //把文件的寫入位置移動至 startIndex
103                 raf.seek(startIndex);
104                 
105                 while ((len = is.read(b))!=-1) {
106                     //每次讀取流里數據之后,同步把數據寫入臨時文件
107                     raf.write(b, 0, len);
108                     total += len;
109                     
110                     //System.out.println("線程" + threadId + "下載了" + total);
111                     //生成一個一個專門用來記錄下載進度的臨時文件
112                     RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
113                     progressRaf.write((total+"").getBytes());
114                     progressRaf.close();
115                 }
116                 System.out.println("線程"+threadId+"下載完了---------------------");
117                 raf.close();
118                 
119                 //當所有的線程下載完之后,將進度文件刪除
120                 MultiDownload.finishedThread++;
121                 synchronized (MultiDownload.path) {//所有線程使用同一個鎖
122                     if (MultiDownload.finishedThread==MultiDownload.ThreadCount) {
123                         for (int i = 0; i < MultiDownload.ThreadCount; i++) {
124                             File f = new File(i+".txt");
125                             f.delete();
126                         }
127                         MultiDownload.finishedThread=0;
128                     }
129                 }
130             }    
131         } catch (Exception e) {
132             e.printStackTrace();
133         }
134         
135     }
136 }

 

二、Android手機版帶斷點續傳的多線程下載
      Android手機版的帶斷點續傳的多線程下載邏輯與PC版的幾乎一樣,只不過在Android手機中耗時操作不能放在主線程,網絡下載屬於耗時操作,所以多線程下載要在Android中開啟一個子線程執行。 並使用消息隊列機制刷新文本進度條。
public class MainActivity extends Activity {
    
    static int ThreadCount = 3;
    static int FinishedThread = 0;
    int currentProgess;
    static String Filename = "QQPlayer.exe";
    static String Path = "http://192.168.3.45:8080/"+Filename;
    
    static MainActivity ma;
    static ProgressBar pb;
    static TextView tv;
    static Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg){
            tv.setText((long)pb.getProgress()*100 /pb.getMax()  +"%");
        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ma = this;
        pb = (ProgressBar) findViewById(R.id.pb);
        tv = (TextView) findViewById(R.id.tv);
    }
    
    public void download(View v){
        
        Thread t = new Thread(){
            
            @Override
            public void run() {
                //發送http請求獲取文件的長度,創建臨時文件
                try {
                    URL url= new URL(Path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5000);
                    conn.setReadTimeout(5000);
                    
                    if (conn.getResponseCode()==200) {
                        int length = conn.getContentLength();
                        
                        //設置進度條的最大值就是原文件的總長度
                        pb.setMax(length);
                        
                        //生成一個與原文件相同大小的臨時文件
                        File file = new File(Environment.getExternalStorageDirectory(),Filename);
                        RandomAccessFile raf = new RandomAccessFile(file, "rwd");
                        raf.setLength(length);
                        raf.close();
                        
                        //計算每個線程需要下載的數據大小
                        int size = length/ThreadCount;
                        //開啟多線程
                        for (int threadId = 0; threadId < ThreadCount; threadId++) {
                            int startIndex = threadId*size;
                            int endIndex = (threadId + 1)*size - 1;
                            if (threadId==ThreadCount - 1) {
                                endIndex = length - 1;
                            }
                            
                            new DownloadThread(startIndex, endIndex, threadId).start();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
    }
    
    class DownloadThread extends Thread{
        private int startIndex;
        private int endIndex;
        private int threadId;
        
        public DownloadThread(int startIndex, int endIndex, int threadId) {
            super();
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.threadId = threadId;
        }
        
        @Override
        public void run() {
            // 每個線程發送http請求自己的數據
            try{
                //先判斷是不是斷點續傳
                File progessFile = new File(Environment.getExternalStorageDirectory(),threadId+".txt");
                if (progessFile.exists()) {
                    FileReader fr = new FileReader(progessFile);
                    BufferedReader br = new BufferedReader(fr);
                    int lastProgess = Integer.parseInt(br.readLine());
                    startIndex += lastProgess;
                    
                    //把上次下載的進度顯示至進度條
                    currentProgess +=lastProgess;
                    pb.setProgress(currentProgess);
                    //發消息,讓主線程刷新文本進度
                    handler.sendEmptyMessage(1);
                    
                    br.close();
                    fr.close();
                }
                
                URL url = new URL(Path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(5000);
                conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
                
                if (conn.getResponseCode()==206) {
                    InputStream is = conn.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len = 0;
                    int total = 0;
                    
                    File file = new File(Environment.getExternalStorageDirectory(),Filename);
                    RandomAccessFile raf = new RandomAccessFile(file, "rwd");
                    raf.seek(startIndex);
                    
                    while ((len = is.read(buffer))!= -1) {
                        raf.write(buffer, 0, len);
                        total += len;
                        
                        //每次讀取流里數據之后,把本次讀取的數據的長度顯示至進度條
                        currentProgess += len;
                        pb.setProgress(currentProgess);
                        //發消息,讓主線程刷新文本進度
                        handler.sendEmptyMessage(1);
                        //生成臨時文件保存下載進度,用於斷點續傳,在所有線程現在完畢后刪除臨時文件
                        RandomAccessFile progressRaf = new RandomAccessFile(progessFile, "rwd");
                        progressRaf.write((total+"").getBytes());
                        progressRaf.close();
                    }
                    raf.close();
                    System.out.println("線程"+threadId+"下載完了");
                    
                    //當所有線程都下在完了之后,刪除臨時進度文件
                    FinishedThread++;
                    synchronized (Path) {
                        if (FinishedThread==ThreadCount) {
                            for (int i = 0; i < ThreadCount; i++) {
                                File f = new File(Environment.getExternalStorageDirectory(),i+".txt");
                                f.delete();
                            }
                            FinishedThread=0;
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM