Java实现的断点续传功能的示例代码

时间:2022-10-23 22:58:12

代码中已经加入了注释,需要的朋友可以直接参考代码中的注释。下面直接上功能实现的主要代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/*
 * Encode:UTF-8
 *
 * Author:zhiming.xu
 *
 * 多线程的断点下载程序,根据输入的url和指定线程数,来完成断点续传功能。
 *
 * 每个线程支负责某一小段的数据下载;再通过RandomAccessFile完成数据的整合。
 */
public class MultiTheradDownLoad {
 
  private String filepath = null;
  private String filename = null;
  private String tmpfilename = null;
 
  private int threadNum = 0;
 
  private CountDownLatch latch = null;//设置一个计数器,代码内主要用来完成对缓存文件的删除
 
  private long fileLength = 0l;
  private long threadLength = 0l;
  private long[] startPos;//保留每个线程下载数据的起始位置。
  private long[] endPos;//保留每个线程下载数据的截止位置。
 
  private boolean bool = false;
 
  private URL url = null;
 
  //有参构造函数,先构造需要的数据
  public MultiTheradDownLoad(String filepath, int threadNum) {
    this.filepath = filepath;
    this.threadNum = threadNum;
    startPos = new long[this.threadNum];
    endPos = new long[this.threadNum];
    latch = new CountDownLatch(this.threadNum);
  }
 
  /*
   * 组织断点续传功能的方法
   */
  public void downloadPart() {
 
    File file = null;
    File tmpfile = null;
    HttpURLConnection httpcon = null;
 
    //在请求url内获取文件资源的名称;此处没考虑文件名为空的情况,此种情况可能需使用UUID来生成一个唯一数来代表文件名。
    filename = filepath.substring(filepath.lastIndexOf('/') + 1, filepath
        .contains("?") ? filepath.lastIndexOf('?') : filepath.length());
    tmpfilename = filename + "_tmp";
 
    try {
      url = new URL(filepath);
      httpcon = (HttpURLConnection) url.openConnection();
 
      setHeader(httpcon);
      fileLength = httpcon.getContentLengthLong();//获取请求资源的总长度。
 
      file = new File(filename);
      tmpfile = new File(tmpfilename);
 
      threadLength = fileLength / threadNum;//每个线程需下载的资源大小。
      System.out.println("fileName: " + filename + " ," + "fileLength= "
          + fileLength + " the threadLength= " + threadLength);
 
      if (file.exists() && file.length() == fileLength) {
        System.out
            .println("the file you want to download has exited!!");
        return;
      } else {
        setBreakPoint(startPos, endPos, tmpfile);
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < threadNum; i++) {
          exec.execute(new DownLoadThread(startPos[i], endPos[i],
              this, i, tmpfile, latch));
        }
        latch.await();//当你的计数器减为0之前,会在此处一直阻塞。
        exec.shutdown();
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
 
    if (file.length() == fileLength) {
      if (tmpfile.exists()) {
        System.out.println("delect the temp file!!");
        tmpfile.delete();
      }
    }
  }
 
  /*
   * 断点设置方法,当有临时文件时,直接在临时文件中读取上次下载中断时的断点位置。没有临时文件,即第一次下载时,重新设置断点。
   *
   * rantmpfile.seek()跳转到一个位置的目的是为了让各个断点存储的位置尽量分开。
   *
   * 这是实现断点续传的重要基础。
   */
  private void setBreakPoint(long[] startPos, long[] endPos, File tmpfile) {
    RandomAccessFile rantmpfile = null;
    try {
      if (tmpfile.exists()) {
        System.out.println("the download has continued!!");
        rantmpfile = new RandomAccessFile(tmpfile, "rw");
        for (int i = 0; i < threadNum; i++) {
          rantmpfile.seek(8 * i + 8);
          startPos[i] = rantmpfile.readLong();
 
          rantmpfile.seek(8 * (i + 1000) + 16);
          endPos[i] = rantmpfile.readLong();
 
          System.out.println("the Array content in the exit file: ");
          System.out.println("thre thread" + (i + 1) + " startPos:"
              + startPos[i] + ", endPos: " + endPos[i]);
        }
      } else {
        System.out.println("the tmpfile is not available!!");
        rantmpfile = new RandomAccessFile(tmpfile, "rw");
        
        //最后一个线程的截止位置大小为请求资源的大小
        for (int i = 0; i < threadNum; i++) {
          startPos[i] = threadLength * i;
          if (i == threadNum - 1) {
            endPos[i] = fileLength;
          } else {
            endPos[i] = threadLength * (i + 1) - 1;
          }
 
          rantmpfile.seek(8 * i + 8);
          rantmpfile.writeLong(startPos[i]);
 
          rantmpfile.seek(8 * (i + 1000) + 16);
          rantmpfile.writeLong(endPos[i]);
 
          System.out.println("the Array content: ");
          System.out.println("thre thread" + (i + 1) + " startPos:"
              + startPos[i] + ", endPos: " + endPos[i]);
        }
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (rantmpfile != null) {
          rantmpfile.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
  
  /*
   * 实现下载功能的内部类,通过读取断点来设置向服务器请求的数据区间。
   */
  class DownLoadThread implements Runnable {
 
    private long startPos;
    private long endPos;
    private MultiTheradDownLoad task = null;
    private RandomAccessFile downloadfile = null;
    private int id;
    private File tmpfile = null;
    private RandomAccessFile rantmpfile = null;
    private CountDownLatch latch = null;
 
    public DownLoadThread(long startPos, long endPos,
        MultiTheradDownLoad task, int id, File tmpfile,
        CountDownLatch latch) {
      this.startPos = startPos;
      this.endPos = endPos;
      this.task = task;
      this.tmpfile = tmpfile;
      try {
        this.downloadfile = new RandomAccessFile(this.task.filename,
            "rw");
        this.rantmpfile = new RandomAccessFile(this.tmpfile, "rw");
      } catch (FileNotFoundException e) {
        e.printStackTrace();
      }
      this.id = id;
      this.latch = latch;
    }
 
    @Override
    public void run() {
 
      HttpURLConnection httpcon = null;
      InputStream is = null;
      int length = 0;
 
      System.out.println("the thread " + id + " has started!!");
 
      while (true) {
        try {
          httpcon = (HttpURLConnection) task.url.openConnection();
          setHeader(httpcon);
          
          //防止网络阻塞,设置指定的超时时间;单位都是ms。超过指定时间,就会抛出异常
          httpcon.setReadTimeout(20000);//读取数据的超时设置
          httpcon.setConnectTimeout(20000);//连接的超时设置
 
          if (startPos < endPos) {
            
            //向服务器请求指定区间段的数据,这是实现断点续传的根本。
            httpcon.setRequestProperty("Range", "bytes=" + startPos
                + "-" + endPos);
 
            System.out
                .println("Thread " + id
                    + " the total size:---- "
                    + (endPos - startPos));
 
            downloadfile.seek(startPos);
 
            if (httpcon.getResponseCode() != HttpURLConnection.HTTP_OK
                && httpcon.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
              this.task.bool = true;
              httpcon.disconnect();
              downloadfile.close();
              System.out.println("the thread ---" + id
                  + " has done!!");
              latch.countDown();//计数器自减
              break;
            }
 
            is = httpcon.getInputStream();//获取服务器返回的资源流
            long count = 0l;
            byte[] buf = new byte[1024];
 
            while (!this.task.bool && (length = is.read(buf)) != -1) {
              count += length;
              downloadfile.write(buf, 0, length);
              
              //不断更新每个线程下载资源的起始位置,并写入临时文件;为断点续传做准备
              startPos += length;
              rantmpfile.seek(8 * id + 8);
              rantmpfile.writeLong(startPos);
            }
            System.out.println("the thread " + id
                + " total load count: " + count);
            
            //关闭流
            is.close();
            httpcon.disconnect();
            downloadfile.close();
            rantmpfile.close();
          }
          latch.countDown();//计数器自减
          System.out.println("the thread " + id + " has done!!");
          break;
        } catch (IOException e) {
          e.printStackTrace();
        } finally {
          try {
            if (is != null) {
              is.close();
            }
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }
 
  /*
   * 为一个HttpURLConnection模拟请求头,伪装成一个浏览器发出的请求
   */
  private void setHeader(HttpURLConnection con) {
    con.setRequestProperty(
        "User-Agent",
        "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");
    con.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");
    con.setRequestProperty("Accept-Encoding", "aa");
    con.setRequestProperty("Accept-Charset",
        "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
    con.setRequestProperty("Keep-Alive", "300");
    con.setRequestProperty("Connection", "keep-alive");
    con.setRequestProperty("If-Modified-Since",
        "Fri, 02 Jan 2009 17:00:05 GMT");
    con.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");
    con.setRequestProperty("Cache-Control", "max-age=0");
    con.setRequestProperty("Referer",
        "http://www.skycn.com/soft/14857.html");
  }
}

下面是测试代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
public class DownLoadTest {
 
  /**
   * @param args
   */
  public static void main(String[] args) {
    
    String filepath = "http://127.0.0.1:8080/file/loadfile.mkv";
    MultiTheradDownLoad load = new MultiTheradDownLoad(filepath ,4); 
    load.downloadPart(); 
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://www.cnblogs.com/stsinghua/p/6418786.html