`

多线程下载

    博客分类:
  • Java
阅读更多

网络蚂蚁、FlashGet、迅雷等支持HTTP协议的下载软件无一例外地使用了多线程下载技术。比起单线程下载,多线程下载在同一时间段内发出多个下载请求,每个下载请求负责下载一段内容,充分地利用了网络带宽。

当然多线程下载并非线程数越多越好。试想,一个极端的情况:一个尺寸为1 024个字节的远程文件,动用1 024个线程来下载,每个线程平均只下载一个字节,创建线程的代价和对自身网络出口造成的堵塞远远大于分工下载带来的好处。因此,多线程下载存在一个权衡的问题。一般来说,需要事先根据待下载的远程文件的尺寸来决定启用多少个线程,如果文件很小,则意味着使用单线程下载即可。而且,下载软件允许创建的线程数一般是设置上限的,例如即使下载一个超大文件,也不能开启过多的线程,毕竟创建下载线程是需要耗费客户端资源的,并且线程之间存在着竞争网络带宽的关系。总之,下载线程数往往是在待下载的远程文件尺寸、每个线程分担的字节数任务、线程数三者之间权衡的结果。

实战多线程下载,有几个技术难题有待攻克:

— 如何获取远程文件的尺寸,这关系到开启多少个下载线程。本节例程采用比较简单的线程数决策策略:固定每个线程分担的字节数任务,根据远程文件尺寸来决定需要开启的下载线程,也就是不考虑下载线程数过多带来的负面影响。因此,获取远程文件的尺寸就成为了很关键的一个步骤。

— 如何实现分工下载,即每个线程只下载远程文件的一段。这是多线程HTTP下载的核心技术。

— 如何存储、组织各个线程下载得到的文件碎片,最后将其拼成一个完整的文件。

首先来解决第一个问题:如何获取远程文件的尺寸。我们知道,在HTTP反馈报文的头(Header)部分有一些数据项,其中有一项便是Content-Length,表示的便是HTTP反馈报文的正文部分的字节数。我们经常以Post、Get等方式发起HTTP请求,实际上HTTP协议还支持以Head方式发出HTTP请求。Head方式发出的HTTP请求,表示仅需要HTTP服务器返回头部分、无须返回正文。解决了如何请求报文头的问题之后,在Java程序中如何读取HTTP反馈报文头呢?答案是:java.net.HttpURLConnection可以通过其getHeaderFieldKey(int n)和getHeaderField(int n)方法读取HTTP反馈报文头,其中getHeaderFieldKey(int n)方法返回的是第n个数据项的名称,getHeaderField(int n)方法返回的是第n个数据项的值。之所以只支持根据数据项编号来获取数据项值,是因为不同的HTTP服务器(如Apache和IIS)在返回HTTP反馈报文时,报文头中的数据项顺序是不一致的,而HTTP协议本身也并没有规定标准数据项的顺序。

接着来解决第二个问题:如何实现分工下载,即每个线程只下载文件的一段。HTTP请求报文头有一个不经常为人使用的数据项:RANGE,它代表的是下载的字节范围,如0~1 024代表从文件开始处下载到第1 024个字节处。一般情况下,Web浏览器在下载远程文件时都不使用这个数据项,这就代表无论多大的文件,浏览器都试图用一次HTTP请求来下载。请看下面使用了RANGE数据项的HTTP请求报文头:

GET /webform.html HTTP/1.1

RANGE: bytes=0-860

User-Agent: Java/1.6.0_01

Host: 127.0.0.1:88

Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

Connection: keep-alive

来看最后一个问题如何解决:如何存储、组织各个线程下载得到的文件碎片,最后将其拼成一个完整的文件。如果读者们阅读过本书的“文件系统”一章,应该已经知道答案了,那便是使用随机文件存取技术。随机文件存取允许在文件内任意移动指针,从而帮助我们实现一边下载、一边填充文件。当全部线程停止工作时,我们便最终得到了一个完整的文件拷贝。

以下是多线程下载的主程序的源代码。

代码清单7‑16  多线程下载的例程——DownloadManager

1.       import java.net.URL;

2.       import java.net.HttpURLConnection;

3.       import java.io.File;

4.       import java.io.RandomAccessFile;

5.       import java.io.IOException;

6.        

7.       public class DownloadManager

8.       {

9.           static final long unitSize=100*1024; //分配给每个下载线程的字节数

10.       

11.        public static void main(String[] args) throws IOException

12.        {

13.           if(args.length!=2)

14.           {

15.               System.out.println("Usage java DownloadManager URL local_file_name");

16.               return;

17.           }

18.           DownloadManager downloadManager=new DownloadManager();

19.           downloadManager.doDownload(args[0],args[1]);

20.        }

21.        

22.        public void doDownload(String remoteFileUrl, String localFileName) throws IOException

23.        {

24.           long fileSize=this.getRemoteFileSize(remoteFileUrl);

25.           this.createFile(localFileName,fileSize);

26.           long threadCount=fileSize/unitSize;

27.           System.out.println("共启动线程"+(fileSize%unitSize==0?threadCount:threadCount+1)+"个");

28.           long offset=0;

29.           if(fileSize<=unitSize) //如果远程文件尺寸小于等于unitSize

30.           {

31.               DownloadThread downloadThread=new DownloadThread(remoteFileUrl, localFileName, offset, fileSize);

32.               downloadThread.start();

33.           }

34.           else //如果远程文件尺寸大于unitSize

35.           {

36.               for(int i=1;i<=threadCount;i++)

37.               {

38.                   DownloadThread downloadThread=new DownloadThread(remoteFileUrl, localFileName, offset, unitSize);

39.                   downloadThread.start();

40.                   offset=offset+unitSize;

41.               }

42.               if( fileSize % unitSize != 0 ) //如果不能整除,则需要再创建一个线程下载剩余字节

43.               {

44.                   DownloadThread downloadThread=new DownloadThread(remoteFileUrl, localFileName, offset, fileSize-unitSize*threadCount);

45.                   downloadThread.start();

46.               }

47.           }     

48.        }

49.        

50.        //获取远程文件尺寸

51.        private long getRemoteFileSize(String remoteFileUrl) throws IOException

52.        {

53.            long result=0;

54.            HttpURLConnection httpConnection = (HttpURLConnection) new URL(remoteFileUrl).openConnection();

55.            httpConnection.setRequestMethod("HEAD");

56.            for(int i=1;i<=10;i++)

57.            {

58.               if( httpConnection.getHeaderFieldKey(i).equalsIgnoreCase ("Content-Length") )

59.               {

60.                   result=Long.parseLong( httpConnection.getHeaderField(i) );

61.                   break;

62.               }                 

63.            }

64.            return result;

65.        }

66.        

67.        //创建指定大小的文件

68.        private void createFile(String fileName, long fileSize) throws IOException

69.        {

70.            File newFile=new File(fileName);

71.            RandomAccessFile raf=new RandomAccessFile(newFile,"rw");

72.            raf.setLength(fileSize);

73.            raf.close();

74.        }    

75.    }

DownloadManager类本身不负责下载,而是负责获取远程文件的尺寸,然后决定开启多少个下载线程,进而为每一个下载线程分配下载任务:下载的偏移量、下载字节数。

— 利用getRemoteFileSize()方法获取远程文件的尺寸,获取的根据正是前面所讲的Content-Length数据项。这里使用了一个技巧,以Head方式发出的HTTP请求得到的HTTP反馈报文,其数据项的先后顺序,以及数据项的数量都是不可知的,但是一般的HTTP服务器返回的标准数据项不会超出十项,因此这里只在前面十个数据项中寻找Content-Length数据项。

— 本例程采取固定每线程固定字节数任务的策略来决定开启多少个线程。每个线程分配到的任务为最多为10K字节。

— 在给每个线程分配任务时,需要考虑到3种情况:

Ø 待下载的远程文件的尺寸较小(不超过10KB),只需开启一个线程。

Ø 待下载的远程文件的尺寸是10K的整数倍,那么开启的线程数为“远程文件的尺寸/10KB”。

Ø 待下载的远程文件的尺寸不能被10K整除,那么开启的线程数为“远程文件的尺寸/10KB+1”。

负责实现分段请求的DownloadThread线程的源代码如下。

代码清单7‑17  多线程下载的例程——DownloadThread

1.       import java.net.URL;

2.       import java.net.HttpURLConnection;

3.       import java.io.BufferedInputStream;

4.       import java.io.File;

5.       import java.io.RandomAccessFile;

6.       import java.io.IOException;

7.        

8.       public class DownloadThread extends Thread

9.       {

10.        private String url=null; //待下载的文件

11.        private String file=null;  //本地存储路径

12.        private long offset=0;  //偏移量

13.        private long length=0;  //分配给本线程的下载字节数

14.        

15.        public DownloadThread(String url, String file, long offset, long length)

16.        {

17.           this.url=url;

18.           this.file=file;

19.           this.offset=offset;

20.           this.length=length;

21.           System.out.println("偏移量="+offset+";字节数="+length);

22.        }

23.        

24.        public void run()

25.        {              

26.           try

27.           {

28.                HttpURLConnection httpConnection = (HttpURLConnection) new URL(this.url).openConnection();

29.                httpConnection.setRequestMethod("GET");

30.                httpConnection.setRequestProperty("RANGE","bytes="+this.offset+"-"+(this. offset+this.length-1));    

31.                BufferedInputStream bis=new BufferedInputStream(httpConnection.getInputStream());

32.                byte[] buff=new byte[1024];

33.                int bytesRead;

34.                while(-1 != (bytesRead = bis.read(buff, 0, buff.length)))

35.                {

36.                    this.writeFile( this.file, this.offset, buff, bytesRead );

37.                    this.offset=this.offset+bytesRead;

38.                }

39.            }

40.            catch(IOException ioe)

41.            {

42.               ioe.printStackTrace();

43.            }   

44.        }  

45.        

46.        //将字节数组以随机存取方式写入文件

47.        //fileName是被写入的文件

48.        //offset代表写入文件的位置偏移量

49.        //bytes是待写入的字节数组

50.        //realLength是实际需要写入的字节数(realLength<=bytes.length)

51.        private void writeFile(String fileName, long offset, byte[] bytes, int realLength) throws IOException

52.        {

53.             File newFile=new File(fileName);

54.            RandomAccessFile raf=new RandomAccessFile(newFile,"rw");

55.            raf.seek(offset);

56.            raf.write( bytes,0,realLength );

57.            raf.close();

58.        }

59.    }

对于DownloadThread线程,需要注意的是第32~38行,这里采取的是分段读取(每次只读取1 024个字节)、分段写入文件的做法。应该说,这是一种值得推荐的读取输入流的方式,因为开辟的读取缓存永远只需要1 024个字节,比起从输入流中全部读取完毕再一次性写入文件的做法,更有利于避免内存泄露。

以下载http://www.ietf.org/rfc/rfc2068.txt文件(该文件是HTTP 1.1协议的RFC文档,RFC是指Request For Comments)为例,运行例程的效果如下:

java DownloadManager

Usage java DownloadManager URL local_file_name

java DownloadManager  http://www.ietf.org/rfc/rfc2068.txt  rfc2068.txt

共启动线程4个

偏移量=0;字节数=102400

偏移量=102400;字节数=102400

偏移量=204800;字节数=102400

偏移量=307200;字节数=70914

运行完毕,请读者们检查当前目录是否生成了rfc2068.txt文件。不妨通过Web浏览器或者其他手段从相同的远程位置下载得到该文件,比较程序生成的文件和下载得到的文件是否完全相同。如果相同,意味着我们的多线程下载程序成功了!

<!-- page -->
分享到:
评论

相关推荐

    C#实现支持断点续传多线程下载客户端工具类

    多线程下载则能显著提高大文件下载的速度,通过将文件分割成多个部分并同时下载来利用多核处理器的优势。 这个C# DIY HttpWebClient工具类基于对System.Net.WebClient的修改和扩展。WebClient是.NET Framework提供...

    易语言多线程下载不卡速度快

    在本项目中,“易语言多线程下载不卡速度快”是一个利用易语言实现的多线程下载工具,旨在提供高效、稳定的文件下载体验。通过多线程技术,该工具能够同时处理多个下载链接,使得下载过程更加流畅,减少因单一线程...

    C# Winform 多线程下载

    在C# Winform应用中实现多线程下载是一项常见的任务,尤其在处理大文件或需要提高下载速度的情况下。本文将详细讲解如何利用C#的多线程技术来创建一个Winform应用程序,实现高效的文件下载功能。 首先,我们需要...

    利用idhttp进行多线程下载

    在Delphi编程环境中,利用 Indy (Internet Direct) 的 `TIdHTTP` 组件进行多线程下载是一项常见的任务,尤其适用于处理大文件,以提高下载效率并提供更好的用户体验。`TIdHTTP` 是Indy库中的一个核心组件,用于执行...

    C#实现多线程下载文件

    本文将深入探讨如何在C#中实现多线程下载文件,涉及的知识点包括线程池、异步编程、进度更新以及错误处理。 首先,我们需要了解线程的基本概念。在C#中,线程是程序执行的独立路径,每个线程都有自己的调用堆栈,...

    多线程下载器.zip易语言项目例子源码下载

    《多线程下载器——易语言项目实例解析》 在当今高速发展的互联网世界中,高效的数据传输成为了一项重要的需求。多线程下载器作为提升下载速度的有效工具,被广泛应用于各种场景。本文将深入剖析一个基于易语言实现...

    C#模仿迅雷的多线程下载类

    ### C#模仿迅雷的多线程下载类:深度解析与应用 在现代软件开发中,网络资源的高效下载已成为一个关键环节。特别是在处理大文件或在网络条件不佳的情况下,单线程下载往往效率低下,用户体验差。为此,多线程下载...

    多线程下载+进度条

    在IT领域,多线程下载和进度条显示是提高用户体验和优化下载效率的重要技术。这里我们将深入探讨这两个概念以及如何实现它们。 首先,多线程下载是指在一个下载任务中使用多个独立的线程同时从服务器获取数据。这种...

    SpringBoot版本的多线程下载文件,分段下载文件

    本篇将深入探讨如何利用SpringBoot实现多线程下载文件以及分段下载文件的技术。 首先,多线程下载文件是一种提高下载速度的方法,通过将大文件分成多个小部分,每个部分由一个单独的线程负责下载,从而充分利用多核...

    C# httpwebrequest 多线程下载类

    在多线程下载场景中,我们通常会为每个下载任务创建一个独立的线程,以便并发执行多个请求,提高下载效率。 下面,我们将逐步解析实现`C# httpwebrequest 多线程下载类`的关键步骤: 1. **创建下载类**:首先,...

    多线程下载工具

    标题中的“多线程下载工具”指的是一个利用多线程技术来提高文件下载速度的应用程序。在计算机编程中,多线程是指在一个程序内同时执行多个独立的线程,每个线程负责不同的任务,比如在下载场景中,一个线程负责处理...

    基于Qt5和libcurl的多线程下载器.zip

    【标题】基于Qt5和libcurl的多线程下载器是一种高效的C++应用程序,它利用了Qt5的图形用户界面(GUI)库和libcurl库的功能来实现文件的多线程下载。Qt5是一个功能丰富的跨平台应用开发框架,支持Windows、Linux、...

    多线程下载原理

    在IT领域,多线程下载是一项重要的技术,尤其在处理大文件或大量数据时,能够显著提高下载效率。本文将详细解析"多线程下载原理",并结合提供的java工程net和android工程videonews来深入理解这一概念。 首先,我们...

    易语言多线程下载源码

    在“易语言多线程下载源码”这个主题中,我们将深入探讨如何使用易语言实现高效的多线程下载功能。 在互联网上下载大文件时,单线程下载往往速度受限,而多线程下载可以显著提高下载效率,因为它允许同时从服务器...

    android断点续传_多线程下载demo

    在Android开发中,断点续传和多线程下载是提高用户下载体验的重要技术。本文将深入探讨如何在Android客户端实现这些功能,并结合服务器端的配合来完成整个流程。 首先,断点续传(Resumable Download)允许用户在...

    python m3u8多线程下载器

    在本项目中,我们关注的是利用Python实现的“m3u8多线程下载器”。M3U8是一种基于HTTP/HTTPS协议的流媒体格式,常用于在线视频播放,尤其在移动设备上。它将视频文件分割成多个小片段,方便流式传输。 这个下载器...

    多线程下载demo

    在IT领域,多线程下载是一种常见的优化网络资源获取的技术,尤其在大文件下载时能够显著提高下载速度。本文将详细解析多线程下载的原理、实现方式以及断点续传的概念。 首先,多线程下载的核心思想是将一个大的文件...

    Java多线程下载器

    Java多线程下载器是一种利用Java编程语言实现的高效文件下载工具,它通过将大文件分割成多个部分并同时下载,显著提高了下载速度。在Java中实现多线程下载器涉及许多关键概念和技术,包括线程、并发控制、网络I/O...

    (Java)FTP多线程下载源代码

    采用apache commons开发包,实现了FTP多线程下载,并有简单的包括进度条的界面,代码的运行:1 把自己的PC配置成FTP服务器(只需下载Serc-U软件)2 建立用户,用户名:zhangyu,密码:123,设置根目录,并拷贝进一些...

Global site tag (gtag.js) - Google Analytics