JAVA实现HTTP断点续传
- 格式:doc
- 大小:89.00 KB
- 文档页数:14
0.使用多线程下载会提升文件下载的速度,那么多线程下载文件的过程是:(1)首先获得下载文件的长度,然后设置本地文件的长度HttpURLConnection.getContentLength();RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");file.setLength(filesize);//设置本地文件的长度(2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示。
例如10M大小,使用3个线程来下载,线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4M,第三个线程下载长度为2M下载开始位置:线程id*每条线程下载的数据长度 = ?下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?(3)使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止代码如下:HttpURLConnection.setRequestProperty("Range","bytes=2097152-4194303");(4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");threadfile.seek(2097152);//从文件的什么位置开始写入数据1.多线程下载的核心代码示例Java代码1.public class MulThreadDownload2.{3. /**4. * 多线程下载5. * @param args6. */7. public static void main(String[] args)8. {9. String path = "/QQWubiSetup.exe";10. try11. {12. new MulThreadDownload().download(path, 3);13. }14. catch (Exception e)15. {16. e.printStackTrace();17. }18. }19. /**20. * 从路径中获取文件名称21. * @param path 下载路径22. * @return23. */24. public static String getFilename(String path)25. {26. return path.substring(stIndexOf('/')+1);27. }28. /**29. * 下载文件30. * @param path 下载路径31. * @param threadsize 线程数32. */33. public void download(String path, int threadsize) throwsException34. {35. URL url = new URL(path);36. HttpURLConnection conn = (HttpURLConnection)url.openConnection();37. conn.setRequestMethod("GET");38. conn.setConnectTimeout(5 * 1000);39. //获取要下载的文件的长度40. int filelength = conn.getContentLength();41. //从路径中获取文件名称42. String filename = getFilename(path);43. File saveFile = new File(filename);44. RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");45. //设置本地文件的长度和下载文件相同46. accessFile.setLength(filelength);47. accessFile.close();48. //计算每条线程下载的数据长度49. int block = filelength%threadsize==0? filelength/threadsize : filelength/threadsize+1;50. for(int threadid=0 ; threadid < threadsize ; threadid++){51. new DownloadThread(url, saveFile, block, threadid).start();52. }53. }54.55. private final class DownloadThread extends Thread56. {57. private URL url;58. private File saveFile;59. private int block;//每条线程下载的数据长度60. private int threadid;//线程id61. public DownloadThread(URL url, File saveFile, int block, int threadid)62. {63. this.url = url;64. this.saveFile = saveFile;65. this.block = block;66. this.threadid = threadid;67. }68.@Override69. public void run()70. {71. //计算开始位置公式:线程id*每条线程下载的数据长度= ?72. //计算结束位置公式:(线程id +1)*每条线程下载的数据长度-1 =?73. int startposition = threadid * block;74. int endposition = (threadid + 1 ) * block - 1;75. try76. {77. RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");78. //设置从什么位置开始写入数据79. accessFile.seek(startposition);80. HttpURLConnection conn = (HttpURLConnection)url.openConnection();81. conn.setRequestMethod("GET");82. conn.setConnectTimeout(5 * 1000);83. conn.setRequestProperty("Range", "bytes="+ startposition+ "-"+ endposition);84. InputStream inStream = conn.getInputStream();85. byte[] buffer = new byte[1024];86. int len = 0;87. while( (len=inStream.read(buffer)) != -1 )88. {89. accessFile.write(buffer, 0, len);90. }91. inStream.close();92. accessFile.close();93. System.out.println("线程id:"+ threadid+ "下载完成");94. }95. catch (Exception e)96. {97. e.printStackTrace();98. }99. }100. }101.}2.多线程断点下载功能,这里把断点数据保存到数据库中:注意代码注释的理解(0)主Activity,关键点使用Handler更新进度条与开启线程下载避免ANR若不使用Handler却要立即更新进度条数据,可使用://resultView.invalidate(); UI线程中立即更新进度条方法//resultView.postInvalidate(); 非UI线程中立即更新进度条方法Java代码1./**2. * 注意这里的设计思路,UI线程与参数保存问题,避免ANR问题,UI控件的显示3. * @author kay4. *5. */6.public class DownloadActivity extends Activity7.{8. private EditText downloadpathText;9. private TextView resultView;10. private ProgressBar progressBar;11.12.@Override13. public void onCreate(Bundle savedInstanceState)14. {15. super.onCreate(savedInstanceState);16. setContentView(yout.main);17.18. downloadpathText = (EditText) this.findViewById(R.id.downloadpath);19. progressBar = (ProgressBar) this.findViewById(R.id.downloadbar);20. resultView = (TextView) this.findViewById(R.id.result);21. Button button = (Button) this.findViewById(R.id.button);22. button.setOnClickListener(new View.OnClickListener()23. {24.@Override25. public void onClick(View v)26. {27. //取得下载路径28. String path = downloadpathText.getText().toString();29. //判断SDCard是否存在30. if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))31. {32. //下载操作33. download(path, Environment.getExternalStorageDirectory());34. }35. else36. {37. Toast.makeText(DownloadActivity.this, R.string.sdcarderror, 1).show();38. }39. }40. });41. }42. //主线程(UI线程)43. //业务逻辑正确,但是该程序运行的时候有问题(可能出现ANR问题)44. //对于显示控件的界面更新只是由UI线程负责,如果是在非UI线程更新控件的属性值,更新后的显示界面不会反映到屏幕上45. /**46. * 参数类型:因为启动一个线程还需要使用到上面方法的参数,而主方法启动后很快就会销毁,47. * 那么使用Final可以解决参数丢失的问题48. * path 注意是Final类型49. * savedir 注意是Final类型50. */51. private void download(final String path, final File savedir)52. {53. //这里开启一个线程避免ANR错误54. new Thread(new Runnable()55. {56.@Override57. public void run()58. {59. FileDownloader loader = new FileDownloader(DownloadActivity.this, path, savedir, 3);60. //设置进度条的最大刻度为文件的长度61. progressBar.setMax(loader.getFileSize());62. try63. {64. loader.download(new DownloadProgressListener()65. {66. /**67. * 注意这里的设计,显示进度条数据需要使用Handler来处理68. * 因为非UI线程更新后的数据不能被刷新69. */70.@Override71. public void onDownloadSize(int size)72. {73. //实时获知文件已经下载的数据长度74. Message msg = new Message();75. //设置消息标签76. msg.what = 1;77. msg.getData().putInt("size", size);78. //使用Handler对象发送消息79. handler.sendMessage(msg);80. }81. });82. }83. catch (Exception e)84. {85. //发送一个空消息到消息队列86. handler.obtainMessage(-1).sendToTarget();87. /**88. * 或者使用下面的方法发送一个空消息89. * Message msg = new Message();90. * msg.what = 1;91. * handler.sendMessage(msg);92. */93. }94. }95. }).start();96. }97.98.99. /**Handler原理:当Handler被创建时会关联到创建它的当前线程的消息队列,该类用于往消息队列发送消息100. *101. * 消息队列中的消息由当前线程内部进行处理102. */103. private Handler handler = new Handler()104. {105. //重写Handler里面的handleMessage方法处理消息106.@Override107. public void handleMessage(Message msg)108. {109. switch (msg.what)110. {111. case 1:112. //进度条显示113. progressBar.setProgress(msg.getData().getInt("size"));114. float num = (float)progressBar.getProg ress()/(float)progressBar.getMax();115. int result = (int)(num*100);116. //resultView.invalidate(); UI线程中立即更新进度条方法117. //resultView.postInvalidate(); 非UI线程中立即更新进度条方法118. resultView.setText(result+ "%"); 119. //判断是否下载成功120. if(progressBar.getProgress()==progress Bar.getMax())121. {122. Toast.makeText(DownloadActivity.th is, R.string.success, 1).show();123. }124. break;125. case -1:126. Toast.makeText(DownloadActivity.this, R.string.error, 1).show();127. break;128. }129. }130. };131.132.}(1)下载类:注意计算每条线程的下载长度与下载起始位置的方法Java代码1.public class DownloadThread extends Thread2.{3. private static final String TAG = "DownloadThread";4. private File saveFile;5. private URL downUrl;6. private int block;7. //下载开始位置8. private int threadId = -1;9. //下载文件长度10. private int downLength;11. private boolean finish = false;12. private FileDownloader downloader;13. public DownloadThread(FileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId)14. {15. this.downUrl = downUrl;16. this.saveFile = saveFile;17. this.block = block;18. this.downloader = downloader;19. this.threadId = threadId;20. this.downLength = downLength;21. }22.23.@Override24. public void run()25. {26. //未下载完成27. if(downLength < block)28. {29. try30. {31. HttpURLConnection http = (HttpURLConnection)downUrl.openConnection();32. http.setConnectTimeout(5 * 1000);33. http.setRequestMethod("GET");34. http.setRequestProperty("Accept", "image/gif,image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave -flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, applicati on/vnd.ms-excel, application/vnd.ms-powerpoint, application/ms word, */*");35. http.setRequestProperty("Accept-Language", "zh-CN");36. http.setRequestProperty("Referer", downUrl.toString());37. http.setRequestProperty("Charset", "UTF-8");38. //下载开始位置:线程id*每条线程下载的数据长度 = ?39. int startPos = block * (threadId - 1) + downLength;40. //下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?41. int endPos = block * threadId -1;42. //设置获取实体数据的范围43. http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);44. http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");45. http.setRequestProperty("Connection", "Keep-Alive");46.47. InputStream inStream = http.getInputStream();48. byte[] buffer = new byte[1024];49. int offset = 0;50. print("Thread " + this.threadId + " start download from position "+ startPos);51. RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");52. threadfile.seek(startPos);53. while ((offset = inStream.read(buffer, 0, 1024)) != -1)54. {55. threadfile.write(buffer, 0, offset);56. downLength += offset;57. downloader.update(this.threadId, downLength);58. downloader.append(offset);59. }60. threadfile.close();61. inStream.close();62. print("Thread " + this.threadId + " downloadfinish");63. //标记是否完成64. this.finish = true;65. }66. catch (Exception e)67. {68. this.downLength = -1;69. print("Thread "+ this.threadId+ ":"+ e);70. }71. }72. }73.74. private static void print(String msg)75. {76. Log.i(TAG, msg);77. }78.79. /**80. * 下载是否完成81. * @return82. */83. public boolean isFinish()84. {85. return finish;86. }87.88. /**89. * 已经下载的内容大小90. * @return 如果返回值为-1,代表下载失败91. */92. public long getDownLength()93. {94. return downLength;95. }96.}文件下载器,使用Java代码1./**2. * 文件下载器,使用这个类的方法如下示例:3. * FileDownloader loader = new FileDownloader(context, "http:///ejb3/ActivePort.exe",4. * new File("D:\\androidsoft\\test"), 2);5. * loader.getFileSize();//得到文件总大小6. * try {7. * loader.download(new DownloadProgressListener(){8. * public void onDownloadSize(int size) {9. * print("已经下载:"+ size);10. * }11. * });12. * }13. * catch (Exception e)14. * {15. * e.printStackTrace();16. * }17. */18.public class FileDownloader19.{20. private static final String TAG = "FileDownloader";21. private Context context;22. private FileService fileService;23. //已下载文件长度24. private int downloadSize = 0;25. //原始文件长度26. private int fileSize = 0;27. ///线程数28. private DownloadThread[] threads;29. //本地保存文件30. private File saveFile;31. //缓存各线程下载的长度32. private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();33. //每条线程下载的长度34. private int block;35. //下载路径36. private String downloadUrl;37. //获取线程数38. public int getThreadSize()39. {40. return threads.length;41. }42. /**43. * 获取文件大小44. * @return45. */46. public int getFileSize()47. {48. return fileSize;49. }50. /**51. * 累计已下载大小52. * @param size53. */54. protected synchronized void append(int size)55. {56. downloadSize += size;57. }58. /**59. * 更新指定线程最后下载的位置60. * @param threadId 线程id61. * @param pos 最后下载的位置62. */63. protected synchronized void update(int threadId, int pos)64. {65. this.data.put(threadId, pos);66. this.fileService.update(this.downloadUrl, this.data);67. }68. /**69. * 文件下载构造器70. * @param downloadUrl 下载路径71. * @param fileSaveDir 文件保存目录72. * @param threadNum 下载线程数73. */74. public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum)75. {76. try77. {78. this.context = context;79. this.downloadUrl = downloadUrl;80. fileService = new FileService(this.context);81. URL url = new URL(this.downloadUrl);82. if(!fileSaveDir.exists()) fileSaveDir.mkdirs();83. this.threads = new DownloadThread[threadNum];84. HttpURLConnection conn = (HttpURLConnection) url.openConnection();85. conn.setConnectTimeout(5*1000);86. conn.setRequestMethod("GET");87. conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-fla sh, application/xaml+xml, application/vnd.ms-xpsdocument, appl ication/x-ms-xbap, application/x-ms-application, application/v nd.ms-excel, application/vnd.ms-powerpoint, application/msword , */*");88. conn.setRequestProperty("Accept-Language", "zh-CN");89. conn.setRequestProperty("Referer", downloadUrl);90. conn.setRequestProperty("Charset", "UTF-8");91. conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR1.1.4322; .NET CLR2.0.50727; .NET CLR3.0.04506.30; .NET CLR3.0.4506.2152; .NET CLR 3.5.30729)");92. conn.setRequestProperty("Connection", "Keep-Alive");93. conn.connect();94. printResponseHeader(conn);95. if (conn.getResponseCode()==200)96. {97. //根据响应获取文件大小98. this.fileSize = conn.getContentLength();99. if (this.fileSize <= 0) throw new RuntimeException("Unkown file size ");100. //获取文件名称101. String filename = getFileName(conn); 102. //构建保存文件103. this.saveFile = new File(fileSaveDir, file name);104. //获取下载记录105. Map<Integer, Integer> logdata = fileService.getData(downloadUrl);106. //如果存在下载记录107. if(logdata.size()>0)108. {109. //把各条线程已经下载的数据长度放入data 中110. for(Map.Entry<Integer, Integer> entry : logdata.entrySet())111. data.put(entry.getKey(), entry.get Value());112. }113. //下面计算所有线程已经下载的数据长度114. if(this.data.size()==this.threads.length)115. {116. for (int i = 0; i < this.threads.lengt h; i++)117. {118. this.downloadSize += this.data.get (i+1);119. }120. print("已经下载的长度"+ this.downloadSize);121. }122. //计算每条线程下载的数据长度123. this.block = (this.fileSize % this.threads .length)==0? this.fileSize / this.threads.length : this.fileSi ze / this.threads.length + 1;124. }125. else126. {127. throw new RuntimeException("server no resp onse ");128. }129. }130. catch (Exception e)131. {132. print(e.toString());133. throw new RuntimeException("don't connection t his url");134. }135. }136. /**137. * 获取文件名138. */139. private String getFileName(HttpURLConnection conn) 140. {141. String filename = this.downloadUrl.substring(this.stIndexOf('/') + 1);142. if(filename==null || "".equals(filename.trim())){/ /如果获取不到文件名称143. for (int i = 0;; i++) {144. String mine = conn.getHeaderField(i); 145. if (mine == null) break;146. if("content-disposition".equals(conn.getHe aderFieldKey(i).toLowerCase())){147. Matcher m = pile(".*filenam e=(.*)").matcher(mine.toLowerCase());148. if(m.find()) return m.group(1); 149. }150. }151. filename = UUID.randomUUID()+ ".tmp";//默认取一个文件名152. }153. return filename;154. }155.156. /**157. * 开始下载文件158. * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null159. * @return 已下载文件大小160. * @throws Exception161. */162. public int download(DownloadProgressListener listener) throws Exception{163. try164. {165. //创建本地文件166. RandomAccessFile randOut = new RandomAccessFil e(this.saveFile, "rw");167. if(this.fileSize>0) randOut.setLength(this.fil eSize);168. randOut.close();169. URL url = new URL(this.downloadUrl);170. if(this.data.size() != this.threads.length) 171. {172. this.data.clear();173. for (int i = 0; i < this.threads.length; i ++)174. {175. //初始化每条线程已经下载的数据长度为176. this.data.put(i+1, 0);177. }178. }179. //开启线程进行下载180. for (int i = 0; i < this.threads.length; i++)181. {182. int downLength = this.data.get(i+1); 183. //判断线程是否已经完成下载,否则继续下载184. if(downLength < this.block && this.downloa dSize<this.fileSize)185. {186. this.threads[i] = new DownloadThread(t his, url, this.saveFile, this.block, this.data.get(i+1), i+1);187. this.threads[i].setPriority(7);//可删除这条188. this.threads[i].start();189. }190. else191. {192. this.threads[i] = null;193. }194. }195. this.fileService.save(this.downloadUrl, this.d ata);196. //下载未完成197. boolean notFinish = true;198. // 循环判断所有线程是否完成下载199. while (notFinish)200. {201. Thread.sleep(900);202. //假定全部线程下载完成203. notFinish = false;204. for (int i = 0; i < this.threads.length; i ++)205. {206. //如果发现线程未完成下载207. if (this.threads[i] != null && !this.t hreads[i].isFinish())208. {209. //设置标志为下载没有完成210. notFinish = true;211. //如果下载失败,再重新下载212. if(this.threads[i].getDownLength() == -1)213. {214. this.threads[i] = new Download Thread(this, url, this.saveFile, this.block, this.data.get(i+1 ), i+1);215. this.threads[i].setPriority(7) ;216. this.threads[i].start(); 217. }218. }219. }220. //通知目前已经下载完成的数据长度221. if(listener!=null) listener.onDownloadSize (this.downloadSize);222. }223. //删除数据库中下载信息224. fileService.delete(this.downloadUrl);225. }226. catch (Exception e)227. {228. print(e.toString());229. throw new Exception("file download fail"); 230. }231. return this.downloadSize;232. }233.234. /**235. * 获取Http响应头字段236. * @param http237. * @return238. */239. public static Map<String, String> getHttpResponseHeade r(HttpURLConnection http) {240. Map<String, String> header = new LinkedHashMap<Str ing, String>();241. for (int i = 0;; i++) {242. String mine = http.getHeaderField(i); 243. if (mine == null) break;244. header.put(http.getHeaderFieldKey(i), mine);245. }246. return header;247. }248. /**249. * 打印Http头字段250. * @param http251. */252. public static void printResponseHeader(HttpURLConnecti on http)253. {254. Map<String, String> header = getHttpResponseHeader (http);255. for(Map.Entry<String, String> entry : header.entry Set())256. {257. String key = entry.getKey()!=null ? entry.getK ey()+ ":" : "";258. print(key+ entry.getValue());259. }260. }261. private static void print(String msg)262. {263. Log.i(TAG, msg);264. }265.}Java代码1.public interface DownloadProgressListener2.{3. public void onDownloadSize(int size);4.}(2)文件操作,断点数据库存储Java代码1.public class DBOpenHelper extends SQLiteOpenHelper2.{3. private static final String DBNAME = "itcast.db";4. private static final int VERSION = 1;5.6. public DBOpenHelper(Context context)7. {8. super(context, DBNAME, null, VERSION);9. }10.11.@Override12. public void onCreate(SQLiteDatabase db)13. {14. db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");15. }16.@Override17. public void onUpgrade(SQLiteDatabase db, int oldVersion,int newVersion)18. {19. db.execSQL("DROP TABLE IF EXISTS filedownlog");20. onCreate(db);21. }22.}Java代码1./**2. * 文件下载业务bean3. */4.public class FileService5.{6. private DBOpenHelper openHelper;7. public FileService(Context context)8. {9. openHelper = new DBOpenHelper(context);10. }11. /**12. * 获取每条线程已经下载的文件长度13. * @param path14. * @return15. */16. public Map<Integer, Integer> getData(String path)17. {18. SQLiteDatabase db = openHelper.getReadableDatabase();19. Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?", new String[]{path});20. Map<Integer, Integer> data = new HashMap<Integer, Integer>();21. while(cursor.moveToNext())22. {23. data.put(cursor.getInt(0), cursor.getInt(1));24. }25. cursor.close();26. db.close();27. return data;28. }29. /**30. * 保存每条线程已经下载的文件长度31. * @param path32. * @param map33. */34. public void save(String path, Map<Integer, Integer> map)35. {//int threadid, int position36. SQLiteDatabase db = openHelper.getWritableDatabase();37. db.beginTransaction();38. try39. {40. for(Map.Entry<Integer, Integer> entry : map.entrySet())41. {42. db.execSQL("insert into filedownlog(downpath,threadid, downlength) values(?,?,?)",43. new Object[]{path, entry.getKey(), entry.getValue()});44. }45. db.setTransactionSuccessful();46. }47. finally48. {49. db.endTransaction();50. }51. db.close();52. }53. /**54. * 实时更新每条线程已经下载的文件长度55. * @param path56. * @param map57. */58. public void update(String path, Map<Integer, Integer> map)59. {60. SQLiteDatabase db = openHelper.getWritableDatabase();61. db.beginTransaction();62. try{63. for(Map.Entry<Integer, Integer> entry : map.entrySet()){64. db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?",65. new Object[]{entry.getValue(), path,entry.getKey()});66. }67. db.setTransactionSuccessful();68. }finally{69. db.endTransaction();70. }71. db.close();72. }73. /**74. * 当文件下载完成后,删除对应的下载记录75. * @param path76. */77. public void delete(String path)78. {79. SQLiteDatabase db = openHelper.getWritableDatabase();80. db.execSQL("delete from filedownlog where downpath=?", new Object[]{path});81. db.close();82. }83.}。
⽤Java实现断点续传(HTTP)在web项⽬中上传⽂件夹现在已经成为了⼀个主流的需求。
在OA,或者企业ERP系统中都有类似的需求。
上传⽂件夹并且保留层级结构能够对⽤户⾏成很好的引导,⽤户使⽤起来也更⽅便。
能够提供更⾼级的应⽤⽀撑。
数据表结构⽂件⽂件夹数据表结构⽂件数据表结构该项⽬核⼼就是⽂件分块上传。
前后端要⾼度配合,需要双⽅约定好⼀些数据,才能完成⼤⽂件分块,我们在项⽬中要重点解决的以下问题。
* 如何分⽚;* 如何合成⼀个⽂件;* 中断了从哪个分⽚开始。
如何分,利⽤强⼤的js库,来减轻我们的⼯作,市场上已经能有关于⼤⽂件分块的轮⼦,虽然程序员的天性曾迫使我重新造轮⼦。
但是因为时间的关系还有⼯作的关系,我只能罢休了。
最后我选择了百度的WebUploader来实现前端所需。
如何合,在合之前,我们还得先解决⼀个问题,我们如何区分分块所属那个⽂件的。
刚开始的时候,我是采⽤了前端⽣成了唯⼀uuid来做⽂件的标志,在每个分⽚请求上带上。
不过后来在做秒传的时候我放弃了,采⽤了Md5来维护分块和⽂件关系。
在服务端合并⽂件,和记录分块的问题,在这⽅⾯其实⾏业已经给了很好的解决⽅案了。
参考迅雷,你会发现,每次下载中的时候,都会有两个⽂件,⼀个⽂件主体,另外⼀个就是⽂件临时⽂件,临时⽂件存储着每个分块对应字节位的状态。
⽂件夹准备逻辑这些都是需要前后端密切联系才能做好,前端需要根据固定⼤⼩对⽂件进⾏分⽚,并且请求中要带上分⽚序号和⼤⼩。
前端发送请求顺利到达后台后,服务器只需要按照请求数据中给的分⽚序号和每⽚分块⼤⼩(分⽚⼤⼩是固定且⼀样的)算出开始位置,与读取到的⽂件⽚段数据,写⼊⽂件即可。
为了便于开发,我将服务端的业务逻辑进⾏了如下划分,分成初始化,块处理,⽂件上传完毕等。
服务端的业务逻辑模块如下功能分析:⽂件夹⽣成模块⽂件夹上传完毕后由服务端进⾏扫描代码如下初始化⽂件的逻辑初始化⽂件夹的逻辑保存⽂件(将⽂件信息写⼊到数据库)逻辑写⼊⽂件夹的逻辑分块上传,分块处理逻辑应该是最简单的逻辑了,up6已经将⽂件进⾏了分块,并且对每个分块数据进⾏了标识,这些标识包括⽂件块的索引,⼤⼩,偏移,⽂件MD5,⽂件块MD5(需要开启)等信息,服务端在接收这些信息后便可以⾮常⽅便的进⾏处理了。
java的http断点续传原理(二)//获得文件长度public long getFileSize(){int nFileLength = -1;try{URL url = new URL(siteInfoBean.getSSiteURL());HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection (); httpConnection.setRequestProperty("User-Agent","NetFox");int responseCode=httpConnection.getResponseCode();if(responseCode>=400){processErrorCode(responseCode);return -2; //-2 represent access is error}String sHeader;for(int i=1;;i++){//DataInputStream in = new DataInputStream(httpConnection.getInputStream ()); //Utility.log(in.readLine());sHeader=httpConnection.getHeaderFieldKey(i);if(sHeader!=null){if(sHeader.equals("Content-Length")){nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader)); break;}}elsebreak;}}catch(IOException e){e.printStackTrace ();}catch(Exception e){e.printStackTrace ();}Utility.log(nFileLength);return nFileLength;}//保存下载信息(文件指针位置)private void write_nPos(){try{output = new DataOutputStream(new FileOutputStream(tmpFile));output.writeInt(nStartPos.length);for(int i=0;i< p>{// output.writeLong(nPos[i]);output.writeLong(fileSplitterFetch[i].nStartPos);output.writeLong(fileSplitterFetch[i].nEndPos);}output.close();}catch(IOException e){e.printStackTrace ();}catch(Exception e){e.printStackTrace ();}}//读取保存的下载信息(文件指针位置)private void read_nPos(){try{DataInputStream input = new DataInputStream(new FileInputStream(tmpFile)); int nCount = input.readInt();nStartPos = new long[nCount];nEndPos = new long[nCount];for(int i=0;i< p>{nStartPos[i] = input.readLong();nEndPos[i] = input.readLong();}input.close();}catch(IOException e){e.printStackTrace ();}catch(Exception e){e.printStackTrace ();}}private void processErrorCode(int nErrorCode) {System.err.println("Error Code : " + nErrorCode); }//停止文件下载public void siteStop(){bStop = true;for(int i=0;i< p>fileSplitterFetch[i].splitterStop();}}/***FileSplitterFetch.java*/package NetFox;import java.io.*;import .*;public class FileSplitterFetch extends Thread {String sURL; //File URLlong nStartPos; //File Snippet Start Positionlong nEndPos; //File Snippet End Positionint nThreadID; //Thread's IDboolean bDownOver = false; //Downing is overboolean bStop = false; //Stop identicalFileAccessI fileAccessI = null; //File Access interfacepublic FileSplitterFetch(String sURL,String sName,long nStart,long nEnd,int id) throws IOException{this.sURL = sURL;this.nStartPos = nStart;this.nEndPos = nEnd;nThreadID = id;fileAccessI = new FileAccessI(sName,nStartPos);}public void run(){while(nStartPos < nEndPos && !bStop){try{URL url = new URL(sURL);HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();httpConnection.setRequestProperty("User-Agent","NetFox");String sProperty = "bytes="+nStartPos+"-";httpConnection.setRequestProperty("RANGE",sProperty);Utility.log(sProperty);InputStream input = httpConnection.getInputStream();//logResponseHead(httpConnection);byte[] b = new byte[1024];int nRead;while((nRead=input.read(b,0,1024)) > 0 && nStartPos < nEndPos && !bStop) {nStartPos += fileAccessI.write(b,0,nRead);//if(nThreadID == 1)// Utility.log("nStartPos = " + nStartPos + ", nEndPos = " + nEndPos); }Utility.log("Thread " + nThreadID + " is over!");bDownOver = true;//nPos = fileAccessI.write (b,0,nRead);}catch(Exception e){e.printStackTrace ();}}}//打印回应的头信息public void logResponseHead(HttpURLConnection con){for(int i=1;;i++){String header=con.getHeaderFieldKey(i);if(header!=null)//responseHeaders.put(header,httpConnection.getHeaderField(header)); Utility.log(header+" : "+con.getHeaderField(header));elsebreak;}}public void splitterStop(){bStop = true;}}/***FileAccess.java*/package NetFox;import java.io.*;public class FileAccessI implements Serializable{ RandomAccessFile oSavedFile;long nPos;public FileAccessI() throws IOException{this("",0);}public FileAccessI(String sName,long nPos) throws IOException {oSavedFile = new RandomAccessFile(sName,"rw");this.nPos = nPos;oSavedFile.seek(nPos);}public synchronized int write(byte[] b,int nStart,int nLen) {int n = -1;try{oSavedFile.write(b,nStart,nLen);n = nLen;}catch(IOException e){e.printStackTrace ();}return n;}}/***SiteInfoBean.java*/package NetFox;public class SiteInfoBean {private String sSiteURL; //Site's URLprivate String sFilePath; //Saved File's Pathprivate String sFileName; //Saved File's Nameprivate int nSplitter; //Count of Splited Downloading Filepublic SiteInfoBean(){//default value of nSplitter is 5this("","","",5);}public SiteInfoBean(String sURL,String sPath,String sName,int nSpiltter) {sSiteURL= sURL;sFilePath = sPath;sFileName = sName;this.nSplitter = nSpiltter;}public String getSSiteURL(){return sSiteURL;}public void setSSiteURL(String value) {sSiteURL = value;}public String getSFilePath(){return sFilePath;}public void setSFilePath(String value) {sFilePath = value;}public String getSFileName(){return sFileName;}public void setSFileName(String value) {sFileName = value;}public int getNSplitter(){return nSplitter;}public void setNSplitter(int nCount) {nSplitter = nCount;}}/***Utility.java*/package NetFox;public class Utility {public Utility(){}public static void sleep(int nSecond) {try{Thread.sleep(nSecond);}catch(Exception e){e.printStackTrace ();}}public static void log(String sMsg){System.err.println(sMsg);}public static void log(int sMsg){System.err.println(sMsg);}}/***TestMethod.java*/package NetFox;public class TestMethod {public TestMethod(){ ///xx/weblogic60b2_win.exetry{SiteInfoBean bean = newSiteInfoBean("http://localhost/xx/weblogic60b2_win.exe","L:\\temp","weblogic60b2_w in.exe",5);//SiteInfoBean bean = newSiteInfoBean("http://localhost:8080/down.zip","L:\\temp","weblogic60b2_win.exe",5) ;SiteFileFetch fileFetch = new SiteFileFetch(bean);fileFetch.start();}catch(Exception e){e.printStackTrace ();}}public static void main(String[] args){new TestMethod();。
Android开发框架系列OkHttp⽂件下载功能实现(含断点续传)前⾔代码部分package .httpBase;import android.util.Log;import org.json.JSONObject;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import okhttp3.Call;import okhttp3.Callback;import okhttp3.FormBody;import okhttp3.MediaType;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.RequestBody;import okhttp3.Response;import okhttp3.ResponseBody;import .httpBase.listener.HttpDownListener;/***@content: okhttp的下载功能⼯具类 (分别包含:1.⽆断点续传的get下载 2.有断点续传的get下载 3.⽆断点续传的post下载 4.有断点续传的post下载)*@time:2018-12-12*@build:z*/public class OkHttpDownUtil {private static final String TAG = "OkHttpDownUtil";private Call mCall;private long mAlreadyDownLength = 0;//已经下载长度private long mTotalLength = 0;//整体⽂件⼤⼩private int mSign = 0; //标记当前运⾏的是那个⽅法private String mDownUrl;//下载⽹络地址private File mPath;//⽂件保存路径private JSONObject mJson;private HttpDownListener mHttpDownListener;//下载进度接⼝回调/*** 没有断点续传功能的get请求下载* @param downUrl 下载⽹络地址* @param saveFilePathAndName 保存路径*/public void getDownRequest(final String downUrl, final File saveFilePathAndName, final HttpDownListener listener) {mSign = 1;mDownUrl = downUrl;mPath = saveFilePathAndName;mHttpDownListener = listener;mAlreadyDownLength = 0;Request request = new Request.Builder().url(mDownUrl).get().build();mCall = OkHttpClientCreate.CreateClient().newCall(request);//构建了⼀个完整的http请求mCall.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {if (mHttpDownListener != null) {mHttpDownListener.onFailure(call, e);}}@Overridepublic void onResponse(Call call, Response response) throws IOException {ResponseBody responseBody = response.body();mTotalLength = responseBody.contentLength();//下载⽂件的总长度InputStream inp = responseBody.byteStream();FileOutputStream fileOutputStream = new FileOutputStream(mPath);try {byte[] bytes = new byte[2048];int len = 0;while ((len = inp.read(bytes)) != -1) {mAlreadyDownLength = mAlreadyDownLength + len;fileOutputStream.write(bytes, 0, len);if (mHttpDownListener != null) {mHttpDownListener.onResponse(call, response, mTotalLength, mAlreadyDownLength);}}} catch (Exception e) {Log.e(TAG, "Get下载异常");} finally {fileOutputStream.close();inp.close();Log.e(TAG, "流关闭");}}});}/*** 有断点续传功能的get下载* @param downUrl 下载地址* @param saveFilePathAndName 保存路径* @param listener 进度监听*/public void getRenewalDownRequest(final String downUrl, final File saveFilePathAndName, final HttpDownListener listener){ mSign = 2;mDownUrl = downUrl;mPath = saveFilePathAndName;mHttpDownListener = listener;Request request = new Request.Builder().url(mDownUrl).header("RANGE", "bytes=" + mAlreadyDownLength + "-").build();mCall = OkHttpClientCreate.CreateClient().newCall(request);//构建了⼀个完整的http请求mCall.enqueue(new Callback() { //发送请求@Overridepublic void onFailure(Call call, IOException e) {if (mHttpDownListener != null) {mHttpDownListener.onFailure(call, e);}Log.e(TAG, "onFailure: 异常报错=" + e.toString());}@Overridepublic void onResponse(Call call, Response response) throws IOException {ResponseBody responseBody = response.body();InputStream inputStream = responseBody.byteStream();//得到输⼊流RandomAccessFile randomAccessFile = new RandomAccessFile(mPath, "rw");//得到任意保存⽂件处理类实例if (mTotalLength == 0){mTotalLength = responseBody.contentLength();//得到⽂件的总字节⼤⼩randomAccessFile.setLength(mTotalLength);//预设创建⼀个总字节的占位⽂件}if (mAlreadyDownLength != 0){randomAccessFile.seek(mAlreadyDownLength);}byte[] bytes = new byte[2048];int len = 0;try {while ((len = inputStream.read(bytes)) != -1) {randomAccessFile.write(bytes,0,len);mAlreadyDownLength = mAlreadyDownLength + len;if (mHttpDownListener != null) {mHttpDownListener.onResponse(call, response, mTotalLength, mAlreadyDownLength);}}} catch (Exception e) {Log.e(TAG, "Get下载异常");} finally {mAlreadyDownLength = randomAccessFile.getFilePointer();//记录当前保存⽂件的位置randomAccessFile.close();inputStream.close();Log.e(TAG, "流关闭下载的位置="+mAlreadyDownLength);}}});}/*** 没有断点续传的post下载* @param downUrl* @param saveFilePathAndName* @param json* @param listener*/public void postDownRequest(final String downUrl, final File saveFilePathAndName, final JSONObject json,final HttpDownListener listener){mSign = 3;mDownUrl = downUrl;mPath = saveFilePathAndName;mJson = json;mHttpDownListener = listener;mAlreadyDownLength = 0;Request request = new Request.Builder().url(mDownUrl).post(changeJSON(json)).build();mCall = OkHttpClientCreate.CreateClient().newCall(request);mCall.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {if (mHttpDownListener!=null){mHttpDownListener.onFailure(call,e);}}@Overridepublic void onResponse(Call call, Response response) throws IOException {ResponseBody responseBody = response.body();mTotalLength = responseBody.contentLength();InputStream inputStream = responseBody.byteStream();FileOutputStream fileOutputStream = new FileOutputStream(mPath);byte[] bytes = new byte[2048];int len = 0;try {while ((len = inputStream.read(bytes)) != -1) {fileOutputStream.write(bytes, 0, len);mAlreadyDownLength = mAlreadyDownLength + len;if (mHttpDownListener != null) {mHttpDownListener.onResponse(call, response, mTotalLength, mAlreadyDownLength);}}}catch (Exception e){Log.e(TAG, "Post下载异常");}finally {fileOutputStream.close();inputStream.close();Log.e(TAG, "流关闭");}}});}/*** ⽀持断点续传的post下载* @param downUrl 下载⽹址* @param saveFilePathAndName ⽂件保存路径* @param json 参数* @param listener 接⼝回调*/public void postRenewalDownRequest(final String downUrl, final File saveFilePathAndName, final JSONObject json, final HttpDownListener listener){ mSign = 4;mDownUrl = downUrl;mPath = saveFilePathAndName;mJson = json;mHttpDownListener = listener;Request request = new Request.Builder().url(mDownUrl).header("RANGE","bytes="+mAlreadyDownLength+"-").post(changeJSON(json)).build();mCall = OkHttpClientCreate.CreateClient().newCall(request);mCall.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {if (mHttpDownListener != null){mHttpDownListener.onFailure(call,e);}}@Overridepublic void onResponse(Call call, Response response) throws IOException {ResponseBody responseBody = response.body();InputStream inputStream = responseBody.byteStream();RandomAccessFile randomAccessFile = new RandomAccessFile(mPath,"rw");if (mTotalLength == 0){mTotalLength = responseBody.contentLength();randomAccessFile.setLength(mTotalLength);}if (mAlreadyDownLength!=0){randomAccessFile.seek(mAlreadyDownLength);}byte[] bytes = new byte[2048];int len = 0;try {while ((len = inputStream.read(bytes)) != -1) {randomAccessFile.write(bytes, 0, len);mAlreadyDownLength = mAlreadyDownLength + len;if (mHttpDownListener != null) {mHttpDownListener.onResponse(call, response, mTotalLength, mAlreadyDownLength); }}}catch (Exception e){Log.e(TAG, "Post下载异常");}finally {mAlreadyDownLength = randomAccessFile.getFilePointer();randomAccessFile.close();inputStream.close();Log.e(TAG, "流关闭下载的位置="+mAlreadyDownLength);}}});}/*** 恢复下载*/public void resume(){if (mSign==0){return;}switch (mSign){case 1:getDownRequest(mDownUrl,mPath,mHttpDownListener);break;case 2:getRenewalDownRequest(mDownUrl,mPath,mHttpDownListener);break;case 3:postDownRequest(mDownUrl,mPath,mJson,mHttpDownListener);break;case 4:postRenewalDownRequest(mDownUrl,mPath,mJson,mHttpDownListener);break;default:break;}}/*** 暂停下载*/public void stop(){if (mCall!=null){mCall.cancel();}}/*** 删除下载⽂件*/public void deleteCurrentFile(){if (mPath == null){Log.e(TAG, "deleteCurrentFile error : 没有路径");return;}if (!mPath.exists()){Log.e(TAG, "deleteCurrentFile error: ⽂件不存在");return;}mPath.delete();mAlreadyDownLength = 0;mTotalLength = 0;mSign = 0;}/*** 销毁*/public void destroy(){if (mCall!=null){mCall.cancel();mCall = null;}mSign = 0;mDownUrl = null;mPath = null;mHttpDownListener = null;mAlreadyDownLength = 0;mTotalLength = 0;}/*** 转换Json参数为RequestBody* @param jsonParam json对象* @return RequestBody*/private RequestBody changeJSON(JSONObject jsonParam){RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8") , String.valueOf(jsonParam));return requestBody;}}。
java实现⽂件断点续传下载功能本⽂实例为⼤家分享了java断点续传下载的代码,供⼤家参考,具体内容如下1. Java代码//实现⽂件下载功能public String downloadFile(){File dir = new File(filepath);//获取⽂件路劲if(!dir.exists()) {System.out.println("⽂件路径错误");log.debug("⽂件路径错误");return "failed";// 判断⽂件或⽂件夹是否存在}File downloadFile = new File(dir, filename);//在指定⽬录下查找⽂件if(!downloadFile.isFile()){System.out.println("⽂件不存在");log.debug("⽂件不存在");return "failed";// 判断⽂件或⽂件夹是否存在}try {downloadFileRanges(downloadFile);} catch(ClientAbortException e){System.out.println("连接被终⽌");log.debug("连接被终⽌");} catch (IOException e) {e.printStackTrace();}return null;}private void downloadFileRanges(File downloadFile) throws IOException {// 要下载的⽂件⼤⼩long fileLength = downloadFile.length();// 已下载的⽂件⼤⼩long pastLength = 0;// 是否快车下载,否则为迅雷或其他boolean isFlashGet = true;// ⽤于记录需要下载的结束字节数(迅雷或其他下载)long lenEnd = 0;// ⽤于记录客户端要求下载的数据范围字串String rangeBytes = request.getHeader("Range");//⽤于随机读取写⼊⽂件RandomAccessFile raf = null;OutputStream os = null;OutputStream outPut = null;byte b[] = new byte[1024];// 如果客户端下载请求中包含了范围if (null != rangeBytes){// 返回码 206response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);rangeBytes = request.getHeader("Range").replaceAll("bytes=", "");// 判断 Range 字串模式if (rangeBytes.indexOf('-') == rangeBytes.length() - 1){// ⽆结束字节数,为快车isFlashGet = true;rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));pastLength = Long.parseLong(rangeBytes.trim());}else{// 迅雷下载isFlashGet = false;String startBytes = rangeBytes.substring(0,rangeBytes.indexOf('-'));String endBytes = rangeBytes.substring(rangeBytes.indexOf('-') + 1, rangeBytes.length());// 已下载⽂件段pastLength = Long.parseLong(startBytes.trim());// 还需下载的⽂件字节数(从已下载⽂件段开始)lenEnd = Long.parseLong(endBytes);}// 通知客户端允许断点续传,响应格式为:Accept-Ranges: bytesresponse.setHeader("Accept-Ranges", "bytes");// response.reset();// 如果为第⼀次下载,则状态默认为 200,响应格式为: HTTP/1.1 200 okif (0 != pastLength){// 内容范围字串String contentRange = "";// 响应格式// Content-Range: bytes [⽂件块的开始字节]-[⽂件的总⼤⼩ - 1]||[⽂件的总⼤⼩]if (isFlashGet){contentRange = new StringBuffer("bytes").append(new Long(pastLength).toString()).append("-").append(new Long(fileLength - 1).toString()).append("/").append(new Long(fileLength).toString()).toString();}else{contentRange = new StringBuffer(rangeBytes).append("/").append(new Long(fileLength).toString()).toString();}response.setHeader("Content-Range", contentRange);}String fileName = getDownloadChineseFileName(filename);response.setHeader("Content-Disposition","attachment;filename=" + fileName + "");// 响应的格式是:response.setContentType("application/octet-stream");response.addHeader("Content-Length", String.valueOf(fileLength));try{os = response.getOutputStream();outPut = new BufferedOutputStream(os);raf = new RandomAccessFile(downloadFile, "r");// 跳过已下载字节raf.seek(pastLength);if (isFlashGet){// 快车等int n = 0;while ((n = raf.read(b, 0, 1024)) != -1){outPut.write(b, 0, n);}}else{// 迅雷等while (raf.getFilePointer() < lenEnd){outPut.write(raf.read());}}outPut.flush();}catch (IOException e){/*** 在写数据的时候对于 ClientAbortException 之类的异常* 是因为客户端取消了下载,⽽服务器端继续向浏览器写⼊数据时,抛出这个异常,这个是正常的。
利用Http协议实现断点续传利用Http协议实现断点续传朱爱梅摘要:本文介绍断点续传的原理和实现,并说明了如何利用Java语言通过Http协议实现断点续传功能的程序。
关键词:java;断点续传;序列化;流;Http协议;In ternet中图分类号:F123.16文献标识码:A文章编号:CN43-1027/F(2007)9-181-01作者:湖南科技职业学院软件学院;湖南,长沙,410006一、断点续传说明断点续传指当信号中断后(掉线或关机等),下次能够从上次中断地方开始接着传送(一般指下载或上传),不支持断点续传就意味着下次下载或上传必须从零开始,断点续传能提高下载的效率。
二、断点续传的实现。
11虽然断点续传的原理非常简单,但是在Internet上如何实现了?在Internet上,所有的连接都是无状态的,即当用户与服务器进行一次通信完成后,服务器将不再保留本次通信的状态与信息。
那么当文件从服务器下载到客户时,如果因为掉线或者关机,此时连接将被中断,下次开机后想继续下载,怎样才能恢复到上一次文件下载的位置了,如果不能恢复,则必须重新开始下载。
其实要解决这个问题也非常简单,通过对Http协议的分析后将会非常清楚。
21当客户与服务器在In ternet上通过Http协议进行联系时,客户首先向服务器发出请求,所发出的请求如下:假设服务器名为http://localhost/,请求的文件名为:mysite-2.rarGE T/mysite-2.rar HTTP/1.1Accep t:i mage/gif,i mage/x-xbitmap,image/jpeg,image/pjpeg,application/vnd.ms-excel,ap-plication/msword,application/vnd.ms-powerpoint,*/*Accep t-Language:zh-cn Accept-Encoding:gzip,deflate User-Agent: Mozilla/4.0(compatible;MSIE6.0;Windows NT5.0)Connection:Keep-Alive服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下: 200Content-Length=106786028Accept-Ranges=bytes Date= Wed,30May200711:56:11GMTETag=W//02ca57e173c11:95b0Content-Type=application/ octet-streamServer=Microsoft-IIS/6.0Last-Modified=Wed,30M ay 2007110:56:11GMT其中200表示连接正常。
java断点续传原理断点续传是指在网络传输过程中,当传输中断或失败后,能够从中断处继续传输,而不需要重新开始传输的一种技术。
在Java中实现断点续传主要依赖于HTTP协议和文件流操作。
HTTP协议是一种无连接的协议,每个请求和响应都是独立的。
当客户端发送请求时,服务器会返回一个状态码和文件的相关信息,如果状态码为206,则表示服务器支持部分内容传输,并返回相应的Content-Range头字段,用于指定从哪个字节到哪个字节的内容。
客户端根据这些信息可以知道文件已经传输到哪个位置,从而进行断点续传。
在Java中,可以使用URL类和HttpURLConnection类来实现HTTP连接和文件传输操作。
下面是一个简单的示例代码:```javaimport java.io.*;public class ResumeUploadExamplepublic static void main(String[] args)String savePath = "path/to/save/file";tryURL url = new URL(fileUrl);HttpURLConnection connection = (HttpURLConnection)url.openConnection(;File file = new File(savePath);long startPos = 0;if (file.exists()startPos = file.length(;connection.setRequestProperty("Range", "bytes=" + startPos + "-");}connection.connect(;int responseCode = connection.getResponseCode(;if (responseCode == HttpURLConnection.HTTP_PARTIAL)long contentLength = connection.getContentLengthLong(;InputStream inputStream = connection.getInputStream(;RandomAccessFile outputFile = new RandomAccessFile(file, "rw");outputFile.seek(startPos);byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1)outputFile.write(buffer, 0, bytesRead);}outputFile.close(;inputStream.close(;System.out.println("File downloaded successfully!");} elseSystem.out.println("Server does not support resume!");}} catch (IOException e)e.printStackTrace(;}}```在上述代码中,首先创建URL对象并打开HTTP连接。
由于业务需要,手机需要采用http方式传输文件到后台WEB服务器,1、2百K的小文件不会有太大问题,几M甚至几百M的文件就很容易传输失败。
所以考虑实现HTTP文件断点续传功能,基本流程如下:1.客户端计算本地要上传的文件的hashcode2.根据指定的块大小和文件大小计算最终的块数3.发送文件信息到服务器包括要上传的文件名、大小、类型、块数、Hashcode4.服务器根据hashcode查询服务器上是否已经存在对应的文件,以及文件的上传状态(上传是否完成、是否组装完成、已经上传了哪些块)5.6.已经上传完成的读取文件URL地址返回给客户端7.8.未上传完成的返回已经上传的块编号9.客户端根据返回值判断,如果未上传完成则从本地文件中读取未上传完成的块内容10.使用HTTP方式上传到服务器11.记录已经上传完成的块到数据库12.检查整个文件是否已经上传完成13.未完成则返回已经上传的块编号到客户端让它继续上传14.上传完成则进行块文件合并过程,将其合并成目标文件15.合并完成后返回目标文件的URL地址首先是数据库表格:createtable tb_fileupload(fseq intprimarykey auto_increment, #自增序列fusername varchar(50), #上传者fhashcode varchar(100), #hash码fsize int, #文件大小fblocks int, #块数ftype varchar(50), #文件类型fready varchar(1024), #已上传完成的块编号finerpath varchar(200), #内部存储路径fouterpath varchar(200), #外部存储路径fisfinished intdefault0, #要否上传完成ftime datetime #创建时间)接下来是客户端代码:import java.io.FileInputStream;publicclass Auth{publicstaticbyte[] create(String filename) throws Excepiton{ InputStreamfis = new FileInputStream(filename);byte[] buf= newbyte[1024];MessageDigest com=MessageDigest.getInstance("MD5");int num;do{num=fis.read(buf);if(num>0){com.update(buf,0,num);}}while(num!=-1)fis.close();return com.digest();}publicstatic String getMD5(String filename) throw Exception {byte[] b =create(filename);String result="";for(int i=0;i<b.length;i++){result+=Integer.toString( (b[i]&0xff)+0x100,16).substring(1);}return result;}}服务器端代码包括以下几部分:1.新增要上传的文件信息。
Http多线程下载与断点续传分析找了很多天的工作,真是找得有点郁闷,也就惰了下来!发现现在的简历真是不值钱。
上次面试的时候本来投的是“高级程序员”职位,笔试也笔试,面试也面了。
本来还是信心满满的. 不过后来在谈到薪水的时候,被贬到了初级程序员,给的高级程序员标准我还达不到,中级程序员的标准也需要一定条件--中级证书,郁闷的是我也没有!最后被定位为初级程序员!还真是有点打击人。
不过我到现在也不知道初中高级程序员的标准究竟在哪里,平时总是尽量让自己提高,也很少去考滤到证书的重要性!总之最后就是泡汤了好了,口水就吐到这里吧,转入正题!投简历归投简历,剩余时间总是喜欢做一点自己喜欢做的事。
有时候发现最快乐的事就是静静的听着音乐,敲着代码,写一些东西与朋友一起分享,一起研究。
上次的- “Mp3在线搜索工具”还有很多可以改进的地方,也得到一些朋友的建议,非常感谢。
这个版本中加入了断点续传的功能,使用了XML文件保存任务列表及状态信息,并且支持多线程分段下载, 提高下载速度,在这一个版本中,我把它叫做: JLoading 因为我还想不出一个更好听一点或更酷一些的名字,而且我还想让他可以下载一些其它文件。
程序不想做大,但想做得极致一些,比如从速度上。
欢迎交流学习, huliqing(huliqing@)Jloading完整程序下载Jloading源码下载(仅供学习研究使用,有任何问题请与本人联系)协议针对于Http,先谈一下简单原理。
因为代码太多,在这里只取重点分析。
如果你对http协议比较了解,那么你应该已经知道原理了,只要在请求头中加入以下代码就可以只请求部分数据:Content-Range: bytes 20000-40000/47000 ,即从第20000字节请求到第40000个字节,(文件长度是47000字节).知道了这一点之后,请求数据就非常容易了,只要利用Java中的URL,先探测数据的长度,然后再将这个长度分片段进行多段程下载就可以了,以下是我的实现方式:// 对文件进行分块try {totalBytes = new URL(url).openConnection().getContentLength();if (totalBytes == -1) state = STATE_NONE;} catch (IOException ioe) {return;}// 创建分块,并创建相应的负责下载的线程long pieceSize = (long) Math.ceil((double) totalBytes / (double) threads);long pStart = 0;long pEnd = 0;tasksAll.clear();tasks.clear();for (int i = 0; i < threads; i++) {if (i == 0) {pStart = pieceSize * i;}if (i == threads - 1) {pEnd = totalBytes;} else {pEnd = pStart + pieceSize;}Piece piece = new Piece(pStart, pStart, pEnd);tasksAll.add(piece);tasks.add(piece);pStart = pEnd + 1;}程序根据线程数目划分成相应的分片信息(Piece)并放入任务列表中,由线程池中的线程去负责下载,每个线程负责一个片段(Piece),当线程下载完自己的分片之后并不立即消毁,而是到任务队列中继续获取任务,若任务池为空,则将自己放入空闲池中并等待新任务,其他线程在发现有空闲线程时,则将自己所负责的任务分片再进行切割,并放入到任务队列中,同时唤醒空闲线程帮助下载,这样不会出现懒惰线程,也可以实现动态增删线程的功能,注意的是一些线程同步的问题。
很简单的Java断点续传实现原理原理解析在开发当中,“断点续传”这种功能很实⽤和常见,听上去也是⽐较有“逼格”的感觉。
所以通常我们都有兴趣去研究研究这种功能是如何实现的?以Java来说,⽹络上也能找到不少关于实现类似功能的资料。
但是呢,⼤多数都是举个Demo然后贴出源码,真正对其实现原理有详细的说明很少。
于是我们在最初接触的时候,很可能就是直接Crtl + C/V代码,然后捣⿎捣⿎,然⽽最终也能把效果弄出来。
但初学时这样做其实很显然是有好有坏的。
好处在于,源码很多,解释很少;如果我们肯下功夫,针对于别⼈贴出的代码⾥那些⾃⼰不明⽩的东西去查资料,去钻研。
最终多半会收获颇丰。
坏处也很明显:作为初学者,⾯对⼀⼤堆的源码,感觉好多东西都很陌⽣,就很容易望⽽⽣畏。
即使最终⼤致了解了⽤法,但也不⼀定明⽩实现原理。
我们今天就⼀起从最基本的⾓度切⼊,来看看所谓的“断点续传”这个东西是不是真的如此“⾼逼格”。
其实在接触⼀件新的“事物”的时候,将它拟化成⼀些我们本⾝⽐较熟悉的事物,来参照和对⽐着学习。
通常会事半功倍。
如果我们刚接触“断点续传”这个概念,肯定很难说清楚个⼀⼆三。
那么,“玩游戏”我们肯定不会陌⽣。
OK,那就假设我们现在有⼀款“通关制的RPG游戏”。
想想我们在玩这类游戏时通常会怎么做?很明显,第⼀天我们浴⾎奋战,⼤杀四⽅,假设终于来到了第四关。
虽然激战正酣,但⼀看墙上的时钟,已经凌晨12点,该睡觉了。
这个时候就很尴尬了,为了能够在下⼀次玩的时候,顺利接轨上我们本次游戏的进度,我们应该怎么办呢?很简单,我们不关掉游戏,直接去睡觉,第⼆天再接着玩呗。
这样是可以,但似乎总觉着有哪⾥让⼈不爽。
那么,这个时候,如果这个游戏有⼀个功能叫做“存档”,就很关键了。
我们直接选择存档,输⼊存档名“第四关”,然后就可以关闭游戏了。
等到下次进⾏游戏时,我们直接找到“第四关”这个存档,然后进⾏读档,就可以接着进⾏游戏了。
这个时候,所谓的“断点续传”就很好理解了。
JAVA支持HTTP断点续传文件下载是WEB网站提供的最基本服务,然而你知道HTTP的断点续传是怎么实现的吗?背景这两天在实现一个基于HTML5在线音视频播放,由于文件是存放于企业网盘中的,HTTP不可达,因此需要用程序来实现文件的读取和HTTP协议的下载。
用Java实现文件下载也不用多说了,读取文件,通过二进制流的方式往response里写就行了。
H5播放器调用也能进行播放了;然而当我控制进度的前进和后退时,问题来了,居然一点效果都没有!没有快进播放器还叫播放器吗?分析首先看到播放器无法取得音视频文件的时间长度,很自然想到Content-Length属性,后台通过file.length()取得文件长度并设置到Content-Length上(代码如下),前台播放器里可以显示音视频的长度了,并且可以快进了;然而当我快退的时候,还是无效,同时后台报错。
response.addHeader('Content-Length', file.length());换了一个HTTP文件进行比较测试,发现直接HTTP访问的能够正常快进快退。
仔细分析两者的request和response头,发现了区别,请求参数多了如下图所示属性,该属性表名需要从服务端获取的资源范围,默认从第一个字节开始取,快进快退实际上就是通过指定这个Range属性来确定你所期望的起始点。
然而这个属性是在请求头上的,客户端又是怎么知道要添加这个属性呢?继续寻找发现了Accept-Ranges这个属性,属性值是bytes,其表明是否接受获取其某个实体的一部分(比如文件的一部分)的请求。
bytes:表示接受,none:表示不接受。
与此对应的response中另外一个属性Content-Range,其表名该响应包含的部分对象为整个对象的哪个部分。
完整响应头如下:解决根据上面的分析,我们就知道在服务端该怎么处理了,首先在响应头上添加Accept-Ranges。
Java下载支持断点续传服务端private void downFile(HttpServletResponse response, HttpServletRequest request, String location){BufferedInputStream bis = null;try {File file = new File(location);if (file.exists()) {long p = 0L;long toLength = 0L;long contentLength = 0L;int rangeSwitch = 0; // 0,从头开始的全文下载;1,从某字节开始的下载(bytes=27000-);2,从某字节开始到某字节结束的下载(bytes=27000-39000)long fileLength;String rangBytes = "";fileLength = file.length();// get file contentInputStream ins = new FileInputStream(file);bis = new BufferedInputStream(ins);// tell the client to allow accept-rangesresponse.reset();response.setHeader("Accept-Ranges", "bytes");// client requests a file block download start byteString range = request.getHeader("Range");if (range != null && range.trim().length() > 0 && !"null".equals(range)) {response.setStatus(javax.servlet.http.HttpServletResponse.S C_PARTIAL_CONTENT);rangBytes = range.replaceAll("bytes=", "");if (rangBytes.endsWith("-")) { // bytes=270000-rangeSwitch = 1;p = Long.parseLong(rangBytes.substring(0, rangBytes.indexOf("-")));contentLength = fileLength - p; // 客户端请求的是270000之后的字节(包括bytes下标索引为270000的字节)} else { // bytes=270000-320000rangeSwitch = 2;String temp1 = rangBytes.substring(0, rangBytes.indexOf("-"));String temp2 = rangBytes.substring(rangBytes.indexOf("-") + 1, rangBytes.length());p = Long.parseLong(temp1);toLength = Long.parseLong(temp2);contentLength = toLength - p + 1; // 客户端请求的是 270000-320000 之间的字节}} else {contentLength = fileLength;}// 如果设设置了Content-Length,则客户端会自动进行多线程下载。
java+下载+⼤⽂件断点续传java两台服务器之间,⼤⽂件上传(续传),采⽤了Socket通信机制以及JavaIO流两个技术点,具体思路如下:实现思路:1、服:利⽤ServerSocket搭建服务器,开启相应端⼝,进⾏长连接操作2、服:使⽤ServerSocket.accept()⽅法进⾏阻塞,接收客户端请求3、服:每接收到⼀个Socket就建⽴⼀个新的线程来处理它4、客:利⽤Socket进⾏远程连接,询问已上传进度5、客:使⽤FileInputStream.skip(long length)从指定位置读取⽂件,向服务器发送⽂件流6、服:接收客户端输⼊流,使⽤RandomAccessFile.seek(long length)随机读取,将游标移动到指定位置进⾏读写7、客/服:⼀个循环输出,⼀个循环读取写⼊8、⽰例:以下是具体代码,仅供参考⽂件介绍:FileUpLoadServer.java(服务器接收⽂件类)FileUpLoadClient.java(客户端发送⽂件类)FinalVariables.java(⾃定义参数类)SocketServerListener.java(JavaWeb启动Socket操作类)web.xml(配置⽂件,跟随项⽬启动)断点上传(服务端)package .csdn.seesun2012.socket;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.RandomAccessFile;import java.math.RoundingMode;import .ServerSocket;import .Socket;import java.text.DecimalFormat;public class FileUpLoadServer extends ServerSocket {// ⽂件⼤⼩private static DecimalFormat df = null;// 退出标识private boolean quit = false;static {// 设置数字格式,保留⼀位有效⼩数df = new DecimalFormat("#0.0");df.setRoundingMode(RoundingMode.HALF_UP);df.setMinimumFractionDigits(1);df.setMaximumFractionDigits(1);}public FileUpLoadServer(int report) throws IOException {super(report);}/*** 使⽤线程处理每个客户端传输的⽂件** @throws Exception*/public void load() throws Exception {System.out.println("【⽂件上传】服务器:" + this.getInetAddress() + " 正在运⾏中...");while (!quit) {// server尝试接收其他Socket的连接请求,server的accept⽅法是阻塞式的Socket socket = this.accept();/*** 我们的服务端处理客户端的连接请求是同步进⾏的,每次接收到来⾃客户端的连接请求后,* 都要先跟当前的客户端通信完之后才能再处理下⼀个连接请求。
一)断点续传的原理其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已。
打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:假设服务器域名为,文件名为down.zip。
GET /down.zip HTTP/1.1Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms- excel, application/msword, application/vnd.ms-powerpoint, */*Accept-Language: zh-cnAccept-Encoding: gzip, deflateUser-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)Connection: Keep-Alive服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:200Content-Length=106786028Accept-Ranges=bytesDate=Mon, 30 Apr 2001 12:56:11 GMTETag=W/"02ca57e173c11:95b"Content-Type=application/octet-streamServer=Microsoft-IIS/5.0Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT所谓断点续传,也就是要从文件已经下载的地方开始继续下载。
所以在客户端浏览器传给Web服务器的时候要多加一条信息--从哪里开始。
下面是用自己编的一个"浏览器"来传递请求信息给Web服务器,要求从2000070字节开始。
GET /down.zip HTTP/1.0User-Agent: NetFoxRANGE: bytes=2000070-Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2仔细看一下就会发现多了一行RANGE: bytes=2000070-这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。
JavaSE02 Day03Top1.断点续传下载工具——DownloadInfo类编写以及TestHttpDownload的testHttpDownload1方法2.断点续传下载工具——DownloadInfo序列化3.断点续传下载工具——DownloadInfo的fetchFileSize方法和DownloadThread类4.断点续传下载工具——完成DownloadFrame的UI及DownloadItem内部类1 断点续传下载工具——DownloadInfo类编写以及TestHttpDownload的testHttpDownload1方法1.1 问题如何使用Java类表示要下载的文件的信息;下载网络上的真实存在的文件来测试Java连接网络的方式。
下面介绍断点续传下载工具的功能和界面效果。
断点续传下载工具的主要功能是实现同时下载多个文件,下载的过程中可以临时终止下载,可以随时继续下载。
程序启动后,显示的界面如图-1所示。
图-1点击“选择”按钮,弹出输入下载地址的输入框,如图-2和图-3所示。
图-2图-3输入下载地址后,点击“确定”,弹出输出保存文件路径的输入框,如图-4和图-5所示。
图-4图-5点击“确定”后,开始下载,进度条变化,按钮变成“终止”,如图-6所示。
图-6点击“终止”按钮,按钮上变成“开始”,如图-7所示。
图-7点击“开始“按钮,继续下载,如图-8。
图-8可以多个要下载的文件,同时下载,如图-9所示。
图-91.2 方案定义DownloadInfo类表示要下载的文件的信息,包括属性有:要下载的文件的网络地址(url),下载到文件的位置(pos),文件的大小(fileSize),文件名(fileName),并定义这些属性的getter和setter方法;定义参数为url 和fileName的构造方法;定义toString方法。
1. private String url;2. private long pos;3. private long fileSize;4. private String fileName;创建TestHttpDownload类测试DownloadInfo的正确性。
JA V A实现HTTP断点续传(一)断点续传的原理其实断点续传的原理很简单,就是在Http的请求上和一般的下载有所不同而已。
打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:假设服务器域名为,文件名为down.zip。
GET /down.zip HTTP/1.1Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, */*Accept-Language: zh-cnAccept-Encoding: gzip, deflateUser-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)Connection: Keep-Alive服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:200Content-Length=106786028Accept-Ranges=bytesDate=Mon, 30 Apr 2001 12:56:11 GMTETag=W/"02ca57e173c11:95b"Content-Type=application/octet-streamServer=Microsoft-IIS/5.0Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT所谓断点续传,也就是要从文件已经下载的地方开始继续下载。
所以在客户端浏览器传给Web服务器的时候要多加一条信息--从哪里开始。
下面是用自己编的一个"浏览器"来传递请求信息给Web服务器,要求从2000070字节开始。
GET /down.zip HTTP/1.0User-Agent: NetFoxRANGE: bytes=2000070-Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2仔细看一下就会发现多了一行RANGE: bytes=2000070-;这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:206Content-Length=106786028Content-Range=bytes 2000070-106786027/106786028Date=Mon, 30 Apr 2001 12:55:20 GMTETag=W/"02ca57e173c11:95b"Content-Type=application/octet-streamServer=Microsoft-IIS/5.0Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT和前面服务器返回的信息比较一下,就会发现增加了一行:Content-Range=bytes 2000070-106786027/106786028返回的代码也改为206了,而不再是200了。
知道了以上原理,就可以进行断点续传的编程了。
(二)Java实现断点续传的关键几点(1)用什么方法实现提交RANGE: bytes=2000070-。
当然用最原始的Socket是肯定能完成的,不过那样太费事了,其实Java的net包中提供了这种功能。
代码如下:URL url = new URL(" /down.zip";;);HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();//设置User-AgenthttpConnection.setRequestProperty("User-Agent","NetFox");//设置断点续传的开始位置httpConnection.setRequestProperty("RANGE","bytes=2000070");//获得输入流InputStream input = httpConnection.getInputStream();从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。
大家看,其实断点续传用Java实现起来还是很简单的吧。
接下来要做的事就是怎么保存获得的流到文件中去了。
保存文件采用的方法我采用的是IO包中的RandAccessFile类。
操作相当简单,假设从2000070处开始保存文件,代码如下:RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw");long nPos = 2000070;//定位文件指针到nPos位置oSavedFile.seek(nPos);byte[] b = new byte[1024];int nRead;//从输入流中读入字节流,然后写到文件中while((nRead=input.read(b,0,1024)) > 0){oSavedFile.write(b,0,nRead);}接下来要做的就是整合成一个完整的程序了。
包括一系列的线程控制等等。
(三)断点续传内核的实现主要用了6个类,包括一个测试类:SiteFileFetch.java负责整个文件的抓取,控制内部线程(FileSplitterFetch类)。
FileSplitterFetch.java负责部分文件的抓取。
FileAccess.java负责文件的存储。
SiteInfoBean.java要抓取的文件的信息,如文件保存的目录,名字,抓取文件的URL 等。
Utility.java工具类,放一些简单的方法。
TestMethod.java测试类。
下面是源程序:/***SiteFileFetch.java*/package NetFox;import java.io.*;import .*;public class SiteFileFetch extends Thread {SiteInfoBean siteInfoBean = null; //文件信息Beanlong[] nStartPos; //开始位置long[] nEndPos; //结束位置FileSplitterFetch[] fileSplitterFetch; //子线程对象long nFileLength; //文件长度boolean bFirst = true; //是否第一次取文件boolean bStop = false; //停止标志File tmpFile; //文件下载的临时信息DataOutputStream output; //输出到文件的输出流public SiteFileFetch(SiteInfoBean bean) throws IOException{siteInfoBean = bean;//tmpFile = File.createTempFile ("zhong","1111",new File(bean.getSFilePath()));tmpFile = new File(bean.getSFilePath()+File.separator + bean.getSFileName()+".info");if(tmpFile.exists ()){bFirst = false;read_nPos();}else{nStartPos = new long[bean.getNSplitter()];nEndPos = new long[bean.getNSplitter()];}}public void run(){//获得文件长度//分割文件//实例FileSplitterFetch//启动FileSplitterFetch线程//等待子线程返回try{if(bFirst){nFileLength = getFileSize();if(nFileLength == -1){System.err.println("File Length is not known!");}else if(nFileLength == -2){System.err.println("File is not access!");}else{for(int i=0;i<nStartPos.length;i++){nStartPos[i] = (long)(i*(nFileLength/nStartPos.length));}for(int i=0;i<nEndPos.length-1;i++){nEndPos[i] = nStartPos[i+1];}nEndPos[nEndPos.length-1] = nFileLength;}}//启动子线程fileSplitterFetch = new FileSplitterFetch[nStartPos.length];for(int i=0;i<nStartPos.length;i++){fileSplitterFetch[i] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(), nStartPos[i],nEndPos[i],i);Utility.log("Thread " + i + " , nStartPos = " + nStartPos[i] + ", nEndPos = " + nEndPos[i]);fileSplitterFetch[i].start();}// fileSplitterFetch[nPos.length-1] = new FileSplitterFetch(siteInfoBean.getSSiteURL(), siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(),nPos[nPos.length-1],nFileLength,nPos.length-1);// Utility.log("Thread " + (nPos.length-1) + " , nStartPos = " + nPos[nPos.length-1] + ", nEndPos = " + nFileLength);// fileSplitterFetch[nPos.length-1].start();//等待子线程结束//int count = 0;//是否结束while循环boolean breakWhile = false;while(!bStop){write_nPos();Utility.sleep(500);breakWhile = true;for(int i=0;i<nStartPos.length;i++){if(!fileSplitterFetch[i].bDownOver){breakWhile = false;break;}}if(breakWhile)break;//count++;//if(count>4)// siteStop();}System.err.println("文件下载结束!");}catch(Exception e){e.printStackTrace ();}}//获得文件长度public long getFileSize(){int nFileLength = -1;try{URL url = new URL(siteInfoBean.getSSiteURL());HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();httpConnection.setRequestProperty("User-Agent","NetFox");int responseCode=httpConnection.getResponseCode();if(responseCode>=400){processErrorCode(responseCode);return -2; //-2 represent access is error}String sHeader;for(int i=1;;i++){//DataInputStream in = new DataInputStream(httpConnection.getInputStream ());//Utility.log(in.readLine());sHeader=httpConnection.getHeaderFieldKey(i);if(sHeader!=null){if(sHeader.equals("Content-Length")){nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));break;}}elsebreak;}}catch(IOException e){e.printStackTrace ();}catch(Exception e){e.printStackTrace ();}Utility.log(nFileLength);return nFileLength;}//保存下载信息(文件指针位置)private void write_nPos(){try{output = new DataOutputStream(new FileOutputStream(tmpFile));output.writeInt(nStartPos.length);for(int i=0;i<nStartPos.length;i++){// output.writeLong(nPos[i]);output.writeLong(fileSplitterFetch[i].nStartPos);output.writeLong(fileSplitterFetch[i].nEndPos);}output.close();}catch(IOException e){e.printStackTrace ();}catch(Exception e){e.printStackTrace ();}}//读取保存的下载信息(文件指针位置)private void read_nPos(){try{DataInputStream input = new DataInputStream(new FileInputStream(tmpFile));int nCount = input.readInt();nStartPos = new long[nCount];nEndPos = new long[nCount];for(int i=0;i<nStartPos.length;i++){nStartPos[i] = input.readLong();nEndPos[i] = input.readLong();}input.close();}catch(IOException e){e.printStackTrace ();}catch(Exception e){e.printStackTrace ();}}private void processErrorCode(int nErrorCode){System.err.println("Error Code : " + nErrorCode);}//停止文件下载public void siteStop(){bStop = true;for(int i=0;i<nStartPos.length;i++)fileSplitterFetch[i].splitterStop();}}/***FileSplitterFetch.java*/package NetFox;import java.io.*;import .*;public class FileSplitterFetch extends Thread {String sURL; //File URLlong nStartPos; //File Snippet Start Positionlong nEndPos; //File Snippet End Positionint nThreadID; //Thread's IDboolean bDownOver = false; //Downing is overboolean bStop = false; //Stop identicalFileAccessI fileAccessI = null; //File Access interfacepublic FileSplitterFetch(String sURL,String sName,long nStart,long nEnd,int id) throws IOException{this.sURL = sURL;this.nStartPos = nStart;this.nEndPos = nEnd;nThreadID = id;fileAccessI = new FileAccessI(sName,nStartPos);}public void run(){while(nStartPos < nEndPos && !bStop){try{URL url = new URL(sURL);HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();httpConnection.setRequestProperty("User-Agent","NetFox");String sProperty = "bytes="+nStartPos+"-";httpConnection.setRequestProperty("RANGE",sProperty);Utility.log(sProperty);InputStream input = httpConnection.getInputStream();//logResponseHead(httpConnection);byte[] b = new byte[1024];int nRead;while((nRead=input.read(b,0,1024)) > 0 && nStartPos < nEndPos && !bStop){nStartPos += fileAccessI.write(b,0,nRead);//if(nThreadID == 1)// Utility.log("nStartPos = " + nStartPos + ", nEndPos = " + nEndPos);}Utility.log("Thread " + nThreadID + " is over!");bDownOver = true;//nPos = fileAccessI.write (b,0,nRead);}catch(Exception e){e.printStackTrace ();}}}//打印回应的头信息public void logResponseHead(HttpURLConnection con){for(int i=1;;i++){String header=con.getHeaderFieldKey(i);if(header!=null)//responseHeaders.put(header,httpConnection.getHeaderField(header));Utility.log(header+" : "+con.getHeaderField(header));elsebreak;}}public void splitterStop(){bStop = true;}}/***FileAccess.java*/package NetFox;import java.io.*;public class FileAccessI implements Serializable{RandomAccessFile oSavedFile;long nPos;public FileAccessI() throws IOException{this("",0);}public FileAccessI(String sName,long nPos) throws IOException{oSavedFile = new RandomAccessFile(sName,"rw");this.nPos = nPos;oSavedFile.seek(nPos);}public synchronized int write(byte[] b,int nStart,int nLen){int n = -1;try{oSavedFile.write(b,nStart,nLen);n = nLen;}catch(IOException e){e.printStackTrace ();}return n;}}/***SiteInfoBean.java*/package NetFox;public class SiteInfoBean {private String sSiteURL; //Site's URLprivate String sFilePath; //Saved File's Pathprivate String sFileName; //Saved File's Nameprivate int nSplitter; //Count of Splited Downloading Filepublic SiteInfoBean(){//default value of nSplitter is 5this("","","",5);}public SiteInfoBean(String sURL,String sPath,String sName,int nSpiltter) {sSiteURL= sURL;sFilePath = sPath;sFileName = sName;this.nSplitter = nSpiltter;}public String getSSiteURL(){return sSiteURL;}public void setSSiteURL(String value) {sSiteURL = value;}public String getSFilePath(){return sFilePath;}public void setSFilePath(String value) {sFilePath = value;}public String getSFileName(){return sFileName;}public void setSFileName(String value) {sFileName = value;}public int getNSplitter(){return nSplitter;}public void setNSplitter(int nCount){nSplitter = nCount;}}/***Utility.java*/package NetFox;public class Utility {public Utility(){}public static void sleep(int nSecond){try{Thread.sleep(nSecond);}catch(Exception e){e.printStackTrace ();}}public static void log(String sMsg){System.err.println(sMsg);}public static void log(int sMsg){System.err.println(sMsg);}}/***TestMethod.java*/package NetFox;public class TestMethod {public TestMethod(){ ///xx/weblogic60b2_win.exetry{SiteInfoBean bean = new SiteInfoBean(" http://localhost/xx/weblogic60b2_win.exe";;,"L:\temp","weblogic60b2_win.exe",5);//SiteInfoBean bean = new SiteInfoBean(" http://localhost:8080/down.zip";;,"L:\temp","weblogic60b2_win.exe",5);SiteFileFetch fileFetch = new SiteFileFetch(bean);fileFetch.start();}catch(Exception e){e.printStackTrace ();}}public static void main(String[] args) {new TestMethod();}}。