Java Socket编程 - 基于Socket实现HTTP下载客户端

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 没有借助任何第三方库,完全基于JAVA Socket实现一个最小化的HTTP文件下载客 户端。完整的演示如何通过Socket实现下载文件的HTTP请求(request header)发送 如何从Socket中接受HTTP响应(Response header, Response body)报文并解析与 保存文件内容。

没有借助任何第三方库,完全基于JAVA Socket实现一个最小化的HTTP文件下载客

户端。完整的演示如何通过Socket实现下载文件的HTTP请求(request header)发送

如何从Socket中接受HTTP响应(Response header, Response body)报文并解析与

保存文件内容。如何通过SwingWork实现UI刷新,实时显示下载进度。

首先看一下UI部分:


【添加下载】按钮:

点击弹出URL输入框,用户Copy要下载文件URL到输入框以后,点击[OK]按钮即开始

下载


【清除完成】按钮:

清除所有已经下载完成的文件列表

文件下载状态分为以下几种:

package com.gloomyfish.socket.tutorial.http.download;

public enum DownLoadStatus {
	NOT_STARTED,
	IN_PROCESS,
	COMPLETED,
	ERROR
}

UI部分主要是利用Swing组件完成。点击【添加下载】执行的代码如下:

final JDialog dialog = new JDialog(this,"Add File Link",true);
dialog.getContentPane().setLayout(new BorderLayout());
// dialog.setSize(new Dimension(400,200));
final URLFilePanel panel = new URLFilePanel();
panel.setUpListener(new ActionListener(){
	@Override
	public void actionPerformed(ActionEvent e) {
		if("OK".equals(e.getActionCommand())){
			if(panel.validateInput()) {
				DownloadDetailStatusInfoModel data = new DownloadDetailStatusInfoModel(panel.getValidFileURL());
				tableModel.getData().add(data);
				startDownlaod();
				refreshUI();
			}
			dialog.setVisible(false);
			dialog.dispose();
		} else if("Cancel".equals(e.getActionCommand())) {
			dialog.setVisible(false);
			dialog.dispose();
		}
	}});

dialog.getContentPane().add(panel, BorderLayout.CENTER);
dialog.pack();
centre(dialog);
dialog.setVisible(true);
【清除完成】按钮执行的代码如下:

private void clearDownloaded() {
	List<DownloadDetailStatusInfoModel> downloadedList = new ArrayList<DownloadDetailStatusInfoModel>();
	for(DownloadDetailStatusInfoModel fileStatus : tableModel.getData()) {
		if(fileStatus.getStatus().toString().equals(DownLoadStatus.COMPLETED.toString())) {
			downloadedList.add(fileStatus);
		}
	}
	tableModel.getData().removeAll(downloadedList);
	refreshUI();
}
让JFrame组件居中显示的代码如下:

	public static void centre(Window w) {
		Dimension us = w.getSize();
		Dimension them = Toolkit.getDefaultToolkit().getScreenSize();
		int newX = (them.width - us.width) / 2;
		int newY = (them.height - us.height) / 2;
		w.setLocation(newX, newY);
	}

HTTP协议实现部分:

概述:HTTP请求头与相应头报文基本结构与解释

HTTP请求:一个标准的HTTP请求报文如


其中请求头可以有多个,message-body可以没有,不是必须的。请求行的格式如下:

Request-Line = Method SP Request-URI SPHTTP-Version CRLF 举例说明如下:

Request-Line = GET http://www.w3.org/pub/WWW/TheProject.htmlHTTP/1.1\r\n

其中SP表示空格, CRLF表示回车换行符\r\n

当你想要上传文件时候,使用Post方式来填写数据到message-body中即可。发送一个

简单的HTTP请求报文如下:

GET /pub/WWW/TheProject.html HTTP/1.1\r\n

Host: www.w3.org\r\n

\r\n

HTTP响应:一个标准的HTTP响应报文如下


最先得到是状态行,其格式如下:

Status-Line = HTTP-Version SP Status-CodeSP Reason-Phrase CRLF, 一个状态行的

简单例子如下:Status-Line = HTTP/1.1 200 OK一般大家最喜欢的就是Status-Code会

给你很多提示,最常见的就是404,500等状态码。状态码的意思可以参考RFC2616中

的解释。下载文件最要紧是的检查HTTP响应头中的Content-Length与Content-Type两

个中分别声明了文件的长度与文件的类型。其它如Accept-Ranges表示接受多少到多少

的字节。可能在多线程下载中使用。搞清楚了HTTP请求与响应的报文格式以后,我们

就可以通过Socket按照报文格式解析内容,发送与读取HTTP请求与响应。具体步骤

如下:

一:根据用户输入的文件URL建立Socket连接

URL url = new URL(fileInfo.getFileURL());
String host = url.getHost();
int port = (url.getPort() == -1)  ? url.getDefaultPort():url.getPort();
System.out.println("Host Name = " + host);
System.out.println("port = " + port);
System.out.println("File URI = " + url.getFile());

// create socket and start to construct the request line
Socket socket = new Socket();
SocketAddress address = new InetSocketAddress(host, port);
socket.connect(address);
用了URL类来把用户输入的url string变成容易解析一点的URL。

二:构造HTTP请求

BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF8"));
String requestStr = "GET " + url.getFile() + " HTTP/1.1\r\n"; // request line

// construct the request header - 构造HTTP请求头(request header)
String hostHeader = "Host: " + host + "\r\n";
String acceptHeader = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
String charsetHeader = "Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3\r\n";
String languageHeader = "Accept-Language: zh-CN,zh;q=0.8\r\n";
String keepHeader = "Connection: close\r\n";
三:发送HTTP请求
// 发送HTTP请求
bufferedWriter.write(requestStr);
bufferedWriter.write(hostHeader);
bufferedWriter.write(acceptHeader);
bufferedWriter.write(charsetHeader);
bufferedWriter.write(languageHeader);
bufferedWriter.write(keepHeader);
bufferedWriter.write("\r\n"); // 请求头信息发送结束标志
bufferedWriter.flush();

四:接受HTTP响应并解析内容,写入创建好的文件

// 准备接受HTTP响应头并解析
CustomDataInputStream input = new CustomDataInputStream(socket.getInputStream());
File myFile = new File(fileInfo.getStoreLocation() + File.separator + fileInfo.getFileName());
String content = null;
HttpResponseHeaderParser responseHeader = new HttpResponseHeaderParser();
BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(myFile));
boolean hasData = false;
while((content = input.readHttpResponseHeaderLine()) != null) {
	System.out.println("response header contect -->> " + content);
	responseHeader.addResponseHeaderLine(content);
	if(content.length() == 0) {
		hasData = true;
	}
	if(hasData) {
		int totalBytes = responseHeader.getFileLength();
		if(totalBytes == 0) break; // no response body and data
		int offset = 0;
		byte[] myData = null;
		if(totalBytes >= 2048) {
			myData = new byte[2048];
		} else {
			myData = new byte[totalBytes];
		}
		int numOfBytes = 0;
		while((numOfBytes = input.read(myData, 0, myData.length)) > 0 && offset < totalBytes) {
			offset += numOfBytes;
			float p = ((float)offset) / ((float)totalBytes) * 100.0f;
			if(offset > totalBytes) {
				numOfBytes = numOfBytes + totalBytes - offset;
				p = 100.0f;
			}
			output.write(myData, 0, numOfBytes);
			updateStatus(p);
		}
		hasData = false;
		break;
	}
}
简单的HTTP响应头解析类HttpResponseHeaderParser代码如下:

package com.gloomyfish.socket.tutorial.http.download;

import java.util.HashMap;
import java.util.Map;

/**
 * it can parse entity header, response head
 * and response line <status code, CharSet, ect...>
 * refer to RFC2616,关于HTTP响应头,请看RFC文档,描写的很详细啊!!
 * 
 * @author fish
 *
 */
public class HttpResponseHeaderParser {
	public final static String CONTENT_LENGTH = "Content-Length";
	public final static String CONTENT_TYPE = "Content-Type";
	public final static String ACCEPT_RANGES = "Accetp-Ranges";
	
	private Map<String, String> headerMap;
	public HttpResponseHeaderParser() {
		headerMap = new HashMap<String, String>();
	}
	/**
	 * <p> get the response header key value pair </p>
	 * @param responseHeaderLine
	 */
	public void addResponseHeaderLine(String responseHeaderLine) {
		if(responseHeaderLine.contains(":")) {
			String[] keyValue = responseHeaderLine.split(": ");
			if(keyValue[0].equalsIgnoreCase(CONTENT_LENGTH)) {
				headerMap.put(CONTENT_LENGTH, keyValue[1]);
			} else if(keyValue[0].equalsIgnoreCase(CONTENT_TYPE)) {
				headerMap.put(CONTENT_TYPE, keyValue[1]);
			} else {
				headerMap.put(keyValue[0], keyValue[1]);
			}
		}
	}
	
	public int getFileLength() {
		if(headerMap.get(CONTENT_LENGTH) == null){
			return 0;
		}
		return Integer.parseInt(headerMap.get(CONTENT_LENGTH));
	}
	
	public String getFileType() {
		return headerMap.get(CONTENT_TYPE);
	}
	public Map<String, String> getAllHeaders() {
		return headerMap;
	}

}

可执行的Jar文件下载地址(这次我要点分):

http://download.csdn.net/detail/jia20003/4862076

转载请务必注明


目录
相关文章
|
22天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
24天前
|
安全 Java UED
深入浅出Java多线程编程
【10月更文挑战第40天】在Java的世界中,多线程是提升应用性能和响应能力的关键。本文将通过浅显易懂的方式介绍Java中的多线程编程,从基础概念到高级特性,再到实际应用案例,带你一步步深入了解如何在Java中高效地使用多线程。文章不仅涵盖了理论知识,还提供了实用的代码示例,帮助你在实际开发中更好地应用多线程技术。
41 5
|
10天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
4天前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
60 34
|
13天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
13天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
7天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
7天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
24 3
|
12天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
32 2
|
17天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界里,异常是程序运行中不可忽视的“惊喜”。它们可能突如其来,也可能悄无声息地潜伏。掌握异常处理的艺术,意味着你能够优雅地面对程序的不完美,并确保它即使在风雨飘摇中也能继续航行。本文将引导你理解Java异常的本质,探索捕获和处理这些异常的方法,并最终学会如何利用自定义异常为你的代码增添力量。