`
nba
  • 浏览: 2513 次
文章分类
社区版块
存档分类
最新评论

多线程下载

阅读更多
网上关于多线程下载的教程基本都是以请求某个具体的文件url路径为例子, 这种例子与实际应用不太相符。
一般我们都是通过请求一个Controller路径或servlet路径来下载文件内容:
这里就涉及到根据请求头rang返回客户端需要的字节数:

服务端代码:
package org.wenchj.servlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;

public class DownloadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public void download(HttpServletRequest request, HttpServletResponse response) {
		try {
			String filePath = request.getSession().getServletContext().getRealPath("/115com_v2.1.1.exe");
			System.out.println(filePath);

			File f = new File(filePath);
			FileInputStream fis = new FileInputStream(f);
			response.reset();
			// 告诉客户端允许断点续传多线程连接下载
			// 响应的格式是:
			// Accept-Ranges: bytes
			response.setHeader("Accept-Ranges", "bytes");
			long p = 0;
			long l = 0;
			l = f.length();
			// 如果是第一次下,还没有断点续传,状态是默认的 200,无需显式设置
			// 响应的格式是:
			// HTTP/1.1 200 OK
			if (request.getHeader("Range") != null) // 客户端请求的下载的文件块的开始字节
			{
				// 如果是下载文件的范围而不是全部,向客户端声明支持并开始文件块下载
				// 要设置状态
				// 响应的格式是:
				// HTTP/1.1 206 Partial Content
				response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);// 206

				// 从请求中得到开始的字节
				// 请求的格式是:
				// Range: bytes=[文件块的开始字节]-
				p = Long.parseLong(request.getHeader("Range").replaceAll("bytes=", "").replaceAll("-", ""));
			}

			// 下载的文件(或块)长度
			// 响应的格式是:
			// Content-Length: [文件的总大小] - [客户端请求的下载的文件块的开始字节]
			response.setHeader("Content-Length", new Long(l - p).toString());

			if (p != 0) {
				// 不是从最开始下载,
				// 响应的格式是:
				// Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]
				response.setHeader(
						"Content-Range",
						"bytes " + new Long(p).toString() + "-" + new Long(l - 1).toString() + "/"
								+ new Long(l).toString());
			}

			// response.setHeader("Connection", "Close"); //如果有此句话不能用 IE 直接下载

			// 使客户端直接下载
			// 响应的格式是:
			// Content-Type: application/octet-stream
			response.setContentType("application/octet-stream");

			// 为客户端下载指定默认的下载文件名称
			// 响应的格式是:
			// Content-Disposition: attachment;filename="[文件名]"
			// response.setHeader("Content-Disposition",
			// "attachment;filename=\"" + s.substring(s.lastIndexOf("\\") + 1) +
			// "\""); //经测试 RandomAccessFile 也可以实现,有兴趣可将注释去掉,并注释掉
			// FileInputStream 版本的语句
			// response.setHeader("Content-Disposition", "attachment;filename=\"" + app.getName() + ".apk\"");

			// 可选
			setFileAttachmentHeader(response, f.getName());

			// raf.seek(p);
			fis.skip(p);

			byte[] b = new byte[1024];
			int i;

			// while ( (i = raf.read(b)) != -1 ) //经测试 RandomAccessFile
			// 也可以实现,有兴趣可将注释去掉,并注释掉 FileInputStream 版本的语句
			while ((i = fis.read(b)) != -1) {
				response.getOutputStream().write(b, 0, i);
			}
			// raf.close();//经测试 RandomAccessFile 也可以实现,有兴趣可将注释去掉,并注释掉
			// FileInputStream 版本的语句
			fis.close();

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		download2(request, response);
		// download2(request, response);
	}

	private void download2(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// String fileName = "/smack_3_3_1.zip";
		String fileName = "/115com_v2.1.1.exe";
		String filePath = request.getSession().getServletContext().getRealPath(fileName);
		System.out.println("[" + Thread.currentThread().getName() + "]" + filePath);

		File f = new File(filePath);

		response.setHeader("Content-Length", String.valueOf(f.length()));
		// 可选
		setFileAttachmentHeader(response, f.getName());

		System.out.println("Range--->" + request.getHeader("Range"));

		byte[] content = FileUtils.readFileToByteArray(f);
		System.out.println("文件总长度content.length-->" + content.length);

		String rangeHeader = request.getHeader("Range");
		if (rangeHeader != null) {

			String[] split = rangeHeader.split("=");
			String[] range = split[1].split("-");
			int start = Integer.valueOf(range[0]);
			int end = Integer.valueOf(range[1]);
			System.out.println("start:" + start + ", end:" + end);

			byte[] temp = new byte[end - start];

			// 截取数组
			temp = ArrayUtils.subarray(content, start, end + 1);

			response.setHeader("Content-Length", String.valueOf(end - start));
			responseFileContent(response, temp);
		} else {
			response.setHeader("Content-Length", String.valueOf(f.length()));
			responseFileContent(response, content);
		}
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
			IOException {
		this.doGet(request, response);
	}

	/*
	 * 返回文件内容
	 */
	private void responseFileContent(HttpServletResponse response, byte[] content) {
		try {
			OutputStream outStream = response.getOutputStream();
			outStream.write(content);
			outStream.flush();
			outStream.close();
		} catch (Exception e) {
			// e.printStackTrace();
		}
	}

	/*
	 * 以附件形式下载(用浏览器下载会弹出对话框)
	 */
	private static void setFileAttachmentHeader(HttpServletResponse response, String fileName) {
		try {
			String encodedFileName = new String(fileName.getBytes(), "ISO8859-1");
			response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

}





客户端代码:
package org.wenchj.servlet;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class MulThreadDownloader {

	public static void main(String[] args) throws Exception {
		String path = "http://localhost:8088/download/DownloadServlet";
		int threadsize = 3;
		new MulThreadDownloader().download(path, threadsize);
	}

	private void download(String path, int threadsize) throws Exception {
		URL downpath = new URL(path);
		HttpURLConnection conn = (HttpURLConnection) downpath.openConnection();
		conn.setConnectTimeout(5000);
		conn.setRequestMethod("GET");
		if (conn.getResponseCode() == 200) {
			int length = conn.getContentLength();// 获取网络文件的长度
			System.out.println("获取网络文件的长度" + length);
			File file = new File("115com_v2.1.1.exe");
			RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");// 生成本地文件
			accessFile.setLength(length);
			accessFile.close();
			// 计算每条线程负责下载的数据量
			int block = (length % threadsize) == 0 ? length / threadsize : (length / threadsize) + 1;
			System.out.println("block:" + block);
			for (int threadid = 0; threadid < threadsize; threadid++) {
				new DownloadThread(threadid, downpath, block, file).start();
			}
		}
	}

	// 负责下载操作
	private final class DownloadThread extends Thread {
		private int threadid;
		private URL downpath;
		private int block;
		private File file;

		public DownloadThread(int threadid, URL downpath, int block, File file) {
			this.threadid = threadid;
			this.downpath = downpath;
			this.block = block;
			this.file = file;
		}

		@Override
		public void run() {
			// if (threadid == 0) {
			// try {
			// System.out.println("线程" + threadid + "睡眠20s...");
			// Thread.sleep(20 * 1000);
			// } catch (InterruptedException e) {
			// e.printStackTrace();
			// }
			// }

			int startposition = threadid * block;// 从网络文件的什么位置开始下载数据
			int endposition = ((threadid + 1) * block) - 1;// 下载到网络文件的什么位置结束
			// 指示该线程要从网络文件的startposition位置开始下载,下载到endposition位置结束
			// Range:bytes=startposition-endposition
			try {
				RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
				accessFile.seek(startposition);// 移动指针到文件的某个位置

				HttpURLConnection conn = (HttpURLConnection) downpath.openConnection();
				conn.setConnectTimeout(5000);
				conn.setRequestMethod("GET");
				conn.setRequestProperty("Range", "bytes=" + startposition + "-" + endposition);

				InputStream inStream = conn.getInputStream();
				System.out.println(Thread.currentThread().getName() + ", ContentLength:" + conn.getContentLength());

				byte[] buffer = new byte[1024];
				int len = 0;
				while ((len = inStream.read(buffer)) != -1) {
					accessFile.write(buffer, 0, len);
				}
				accessFile.close();
				inStream.close();

				System.out.println("线程" + (threadid) + "下载完成");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

}

分享到:
评论

相关推荐

    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