




已阅读5页,还剩10页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
annegu做了一个简单的Http多线程的下载程序,来讨论一下多线程并发下载以及断点续传的问题。 这个程序的功能,就是可以分多个线程从目标地址上下载数据,每个线程负责下载一部分,并可以支持断点续传和超时重连。 下载的方法是download(),它接收两个参数,分别是要下载的页面的url和编码方式。在这个负责下载的方法中,主要分了三个步骤。第一步是用来设置断点续传时候的一些信息的,第二步就是主要的分多线程来下载了,最后是数据的合并。 1、多线程下载: Java代码 1. publicStringdownload(StringurlStr,Stringcharset) 2. this.charset=charset; 3. longcontentLength=0; 4. CountDownLatchlatch=newCountDownLatch(threadNum); 5. longstartPos=newlongthreadNum; 6. longendPos=0; 7. 8. try 9. /从url中获得下载的文件格式与名字 10. this.fileName=urlStr.substring(urlStr.lastIndexOf(/)+1,urlStr.lastIndexOf(?)0?urlStr.lastIndexOf(?):urlStr.length(); 11. if(.equalsIgnoreCase(this.fileName) 12. this.fileName=UUID.randomUUID().toString(); 13. 14. 15. this.url=newURL(urlStr); 16. URLConnectioncon=url.openConnection(); 17. setHeader(con); 18. /得到content的长度 19. contentLength=con.getContentLength(); 20. /把context分为threadNum段的话,每段的长度。 21. this.threadLength=contentLength/threadNum; 22. 23. /第一步,分析已下载的临时文件,设置断点,如果是新的下载任务,则建立目标文件。在第4点中说明。 24. startPos=setThreadBreakpoint(fileDir,fileName,contentLength,startPos); 25. 26. /第二步,分多个线程下载文件 27. ExecutorServiceexec=Executors.newCachedThreadPool(); 28. for(inti=0;i0 ? urlStr.lastIndexOf(?) : urlStr.length();if(.equalsIgnoreCase(this.fileName)this.fileName = UUID.randomUUID().toString();this.url = new URL(urlStr);URLConnection con = url.openConnection();setHeader(con);/ 得到content的长度contentLength = con.getContentLength();/ 把context分为threadNum段的话,每段的长度。this.threadLength = contentLength / threadNum;/ 第一步,分析已下载的临时文件,设置断点,如果是新的下载任务,则建立目标文件。在第4点中说明。startPos = setThreadBreakpoint(fileDir, fileName, contentLength, startPos);/第二步,分多个线程下载文件ExecutorService exec = Executors.newCachedThreadPool();for (int i = 0; i threadNum; i+) / 创建子线程来负责下载数据,每段数据的起始位置为(threadLength * i + 已下载长度)startPosi += threadLength * i;/*设置子线程的终止位置,非最后一个线程即为(threadLength * (i + 1) - 1)最后一个线程的终止位置即为下载内容的长度*/if (i = threadNum - 1) endPos = contentLength; else endPos = threadLength * (i + 1) - 1;/ 开启子线程,并执行。ChildThread thread = new ChildThread(this, latch, i, startPosi, endPos);childThreadsi = thread;exec.execute(thread);try / 等待CountdownLatch信号为0,表示所有子线程都结束。latch.await();exec.shutdown();/ 第三步,把分段下载下来的临时文件中的内容写入目标文件中。在第3点中说明。tempFileToTargetFile(childThreads); catch (InterruptedException e) e.printStackTrace();首先来看最主要的步骤:多线程下载。 首先从url中提取目标文件的名称,并在对应的目录创建文件。然后取得要下载的文件大小,根据分成的下载线程数量平均分配每个线程需要下载的数据量,就是threadLength。然后就可以分多个线程来进行下载任务了。 在这个例子中,并没有直接显示的创建Thread对象,而是用Executor来管理Thread对象,并且用CachedThreadPool来创建的线程池,当然也可以用FixedThreadPool。CachedThreadPool在程序执行的过程中会创建与所需数量相同的线程,当程序回收旧线程的时候就停止创建新线程。FixedThreadPool可以预先新建参数给定个数的线程,这样就不用在创建任务的时候再来创建线程了,可以直接从线程池中取出已准备好的线程。下载线程的数量是通过一个全局变量threadNum来控制的,默认为5。 好了,这5个子线程已经通过Executor来创建了,下面它们就会各自为政,互不干涉的执行了。线程有两种实现方式:实现Runnable接口;继承Thread类。 ChildThread就是子线程,它作为DownloadTask的内部类,继承了Thread,它的构造方法需要5个参数,依次是一个对DownloadTask的引用,一个CountDownLatch,id(标识线程的id号),startPosition(下载内容的开始位置),endPosition(下载内容的结束位置)。 这个CountDownLatch是做什么用的呢? 现在我们整理一下思路,要实现分多个线程来下载数据的话,我们肯定还要把这多个线程下载下来的数据进行合。主线程必须等待所有的子线程都执行结束之后,才能把所有子线程的下载数据按照各自的id顺序进行合并。CountDownLatch就是来做这个工作的。 CountDownLatch用来同步主线程,强制主线程等待所有的子线程执行的下载操作完成。在主线程中,CountDownLatch对象被设置了一个初始计数器,就是子线程的个数5个,代码处。在新建了5个子线程并开始执行之后,主线程用CountDownLatch的await()方法来阻塞主线程,直到这个计数器的值到达0,才会进行下面的操作,代码处。 对每个子线程来说,在执行完下载指定区间与长度的数据之后,必须通过调用CountDownLatch的countDown()方法来把这个计数器减1。 2、在全面开启下载任务之后,主线程就开始阻塞,等待子线程执行完毕,所以下面我们来看一下具体的下载线程ChildThread。 Java代码 1. publicclassChildThreadextendsThread 2. publicstaticfinalintSTATUS_HASNOT_FINISHED=0; 3. publicstaticfinalintSTATUS_HAS_FINISHED=1; 4. publicstaticfinalintSTATUS_HTTPSTATUS_ERROR=2; 5. privateDownloadTasktask; 6. privateintid; 7. privatelongstartPosition; 8. privatelongendPosition; 9. privatefinalCountDownLatchlatch; 10. /privateRandomAccessFiletempFile=null; 11. privateFiletempFile=null; 12. /线程状态码 13. privateintstatus=ChildThread.STATUS_HASNOT_FINISHED; 14. 15. publicChildThread(DownloadTasktask,CountDownLatchlatch,intid,longstartPos,longendPos) 16. super(); 17. this.task=task; 18. this.id=id; 19. this.startPosition=startPos; 20. this.endPosition=endPos; 21. this.latch=latch; 22. 23. try 24. tempFile=newFile(this.task.fileDir+this.task.fileName+_+id); 25. if(!tempFile.exists() 26. tempFile.createNewFile(); 27. 28. catch(IOExceptione) 29. e.printStackTrace(); 30. 31. 32. 33. publicvoidrun() 34. System.out.println(Thread+id+run.); 35. HttpURLConnectioncon=null; 36. InputStreaminputStream=null; 37. BufferedOutputStreamoutputStream=null; 38. longcount=0; 39. longthreadDownloadLength=endPosition-startPosition; 40. 41. try 42. outputStream=newBufferedOutputStream(newFileOutputStream(tempFile.getPath(),true); 43. catch(FileNotFoundExceptione2) 44. e2.printStackTrace(); 45. 46. 47. for(intk=0;k0) 49. System.out.println(Nowthread+id+isreconnect,startpositionis+startPosition); 50. try 51. /打开URLConnection 52. con=(HttpURLConnection)task.url.openConnection(); 53. setHeader(con); 54. con.setAllowUserInteraction(true); 55. /设置连接超时时间为10000ms 56. con.setConnectTimeout(10000); 57. /设置读取数据超时时间为10000ms 58. con.setReadTimeout(10000); 59. 60. if(startPosition=threadDownloadLength) 99. status=ChildThread.STATUS_HAS_FINISHED; 100. 101. outputStream.flush(); 102. outputStream.close(); 103. inputStream.close(); 104. con.disconnect(); 105. else 106. status=ChildThread.STATUS_HAS_FINISHED; 107. 108. 109. System.out.println(Thread+id+finished.); 110. latch.countDown(); 111. break; 112. catch(IOExceptione) 113. try 114. outputStream.flush(); 115. TimeUnit.SECONDS.sleep(getSleepSeconds(); 116. catch(InterruptedExceptione1) 117. e1.printStackTrace(); 118. catch(IOExceptione2) 119. e2.printStackTrace(); 120. 121. continue; 122. 123. 124. 125. public class ChildThread extends Thread public static final int STATUS_HASNOT_FINISHED = 0;public static final int STATUS_HAS_FINISHED = 1;public static final int STATUS_HTTPSTATUS_ERROR = 2;private DownloadTask task;private int id;private long startPosition;private long endPosition;private final CountDownLatch latch;/ private RandomAccessFile tempFile = null;private File tempFile = null;/线程状态码private int status = ChildThread.STATUS_HASNOT_FINISHED;public ChildThread(DownloadTask task, CountDownLatch latch, int id, long startPos, long endPos) super();this.task = task;this.id = id;this.startPosition = startPos;this.endPosition = endPos;this.latch = latch;try tempFile = new File(this.task.fileDir + this.task.fileName + _ + id);if(!tempFile.exists()tempFile.createNewFile(); catch (IOException e) e.printStackTrace();public void run() System.out.println(Thread + id + run .);HttpURLConnection con = null;InputStream inputStream = null;BufferedOutputStream outputStream = null;long count = 0; long threadDownloadLength = endPosition - startPosition;try outputStream = new BufferedOutputStream(new FileOutputStream(tempFile.getPath(), true); catch (FileNotFoundException e2) e2.printStackTrace();for(int k = 0; k 0)System.out.println(Now thread + id + is reconnect, start position is + startPosition);try /打开URLConnectioncon = (HttpURLConnection) task.url.openConnection();setHeader(con);con.setAllowUserInteraction(true);/设置连接超时时间为10000mscon.setConnectTimeout(10000);/设置读取数据超时时间为10000mscon.setReadTimeout(10000);if(startPosition = threadDownloadLength) status = ChildThread.STATUS_HAS_FINISHED;outputStream.flush();outputStream.close();inputStream.close();con.disconnect(); else status = ChildThread.STATUS_HAS_FINISHED;System.out.println(Thread + id + finished.);latch.countDown();break; catch (IOException e) try outputStream.flush();TimeUnit.SECONDS.sleep(getSleepSeconds(); catch (InterruptedException e1) e1.printStackTrace(); catch (IOException e2) e2.printStackTrace();continue;在ChildThread的构造方法中,除了设置一些从主线程中带来的id, 起始位置之外,就是新建了一个临时文件用来存放当前线程的下载数据。临时文件的命名规则是这样的:下载的目标文件名+”_”+线程编号。 现在让我们来看看从网络中读数据是怎么读的。我们通过URLConnection来获得一个http的连接。有些网站为了安全起见,会对请求的http连接进行过滤,因此为了伪装这个http的连接请求,我们给httpHeader穿一件伪装服。下面的setHeader方法展示了一些非常常用的典型的httpHeader的伪装方法。比较重要的有:Uer-Agent模拟从Ubuntu的firefox浏览器发出的请求;Referer模拟浏览器请求的前一个触发页面,例如从skycn站点来下载软件的话,Referer设置成skycn的首页域名就可以了;Range就是这个连接获取的流文件的起始区间。 Java代码 1. privatevoidsetHeader(URLConnectioncon) 2. con.setRequestProperty(User-Agent,Mozilla/5.0(X11;U;Linuxi686;en-US;rv:)Gecko/2008092510Ubuntu/8.04(hardy)Firefox/3.0.3); 3. con.setRequestProperty(Accept-Language,en-us,en;q=0.7,zh-cn;q=0.3); 4. con.setRequestProperty(Accept-Encoding,aa); 5. con.setRequestProperty(Accept-Charset,ISO-8859-1,utf-8;q=0.7,*;q=0.7); 6. con.setRequestProperty(Keep-Alive,300); 7. con.setRequestProperty(Connection,keep-alive); 8. con.setRequestProperty(If-Modified-Since,Fri,02Jan200917:00:05GMT); 9. con.setRequestProperty(If-None-Match,1261d8-4290-df64d224); 10. con.setRequestProperty(Cache-Control,max-age=0); 11. con.setRequestProperty(Referer,); 12. private void setHeader(URLConnection con) con.setRequestProperty(User-Agent, Mozilla/5.0 (X11; U; Linux i686; en-US; rv:) 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, );另外,为了避免线程因为网络原因而阻塞,设置了ConnectTimeout和ReadTimeout,代码处。setConnectTimeout设置的连接的超时时间,而setReadTimeout设置的是读取数据的超时时间,发生超时的话,就会抛出socketTimeout异常,两个方法的参数都是超时的毫秒数。 这里对超时的发生,采用的是等候一段时间重新连接的方法。整个获取网络连接并读取下载数据的过程都包含在一个循环之中(代码处),如果发生了连接或者读取数据的超时,在抛出的异常里面就会sleep一定的时间(代码处),然后continue,再次尝试获取连接并读取数据,这个时间可以通过setSleepSeconds()方法来设置。我们在迅雷等下载工具的使用中,经常可以看到状态栏会输出类似“连接超时,等待*秒后重试”的话,这个就是通过ConnectTimeout,ReadTimeout来实现的。 连接建立好之后,我们要检查一下返回响应的状态码。常见的Http Response Code有以下几种: a) 200 OK 一切正常,对GET和POST请求的应答文档跟在后面。 b) 206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成。 c) 404 Not Found 无法找到指定位置的资源。这也是一个常用的应答。 d) 414 Request URI Too Long URI太长。 e) 416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头。 f) 500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求。 g) 503 Service Unavailable 服务器由于维护或者负载过重未能应答。例如,Servlet可能在数据库连接池已满的情况下返回503。 在这些状态里面,只有200与206才是我们需要的正确的状态。所以在代码处,进行了状态码的判断,如果返回不符合要求的状态码,则结束线程,返回主线程并提示报错。 假设一切正常,下面我们就要考虑从网络中读数据了。正如我之前在分析mysql的数据库驱动中看的一样,网络中发送数据都是以数据包的形式来发送的,也就是说不管是客户端向服务器发出的请求数据,还是从服务器返回给客户端
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025贵州黔东穗勤劳务有限公司招聘6人笔试参考题库附带答案详解
- 2025年龙港市国有资本运营有限公司面向社会公开招聘工作人员3人笔试参考题库附带答案详解
- 2025年福建省福州市罗源县丝路港湾勘测设计有限公司招聘6人笔试参考题库附带答案详解
- 2025年湖北省新能源有限公司社会招聘24人笔试参考题库附带答案详解
- 2025年安徽金柱控股集团有限公司招聘7人笔试参考题库附带答案详解
- 2025年华北油田公司招聘21人笔试参考题库附带答案详解
- 2025年中国水利水电第十六工程局有限公司高校毕业生招聘笔试参考题库附带答案详解
- 2025山东菏泽市通达协合劳务有限公司招聘劳务派遣制人员8人笔试参考题库附带答案详解
- 2025华远国际陆港集团所属企业校园招聘113人(山西)笔试参考题库附带答案详解
- 2025中国建设科技集团股份有限公司竞聘4人笔试参考题库附带答案详解
- 萨福双脉冲气保焊说明书DIGIPLUS课件
- 高中期中考试家长会PPT课件 (共51张PPT)
- JJG 573-2003膜盒压力表
- GB/T 39634-2020宾馆节水管理规范
- GB/T 13234-2018用能单位节能量计算方法
- 营业线施工单位“四员一长”施工安全知识培训考试题库
- 紧急采购申请单
- 全球卫生治理课件
- 工程地质学:第7章 岩体结构及其稳定性
- 实验室生物安全程序文件
- 非洲猪瘟防控讲座课件
评论
0/150
提交评论