使用URL和URLConnection(多线程下载)

17.2.3 使用URL和URLConnection URL(Uniform Resource Locator)对象代表统一资源定位器,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象引用,例如对数据库或搜 索引擎的查询。通常情况而言,URL可以由协议名、主机、端口和资源组成。即满足如下格式: protocol://host:port/resourceName 例如如下的URL地址: http://www.oneedu.cn/Index.htm JDK中还提供了一个URI(Uniform Resource Identifiers)类,其实例代表一个统一资源标识符,Java的URI不能用于定位任何资源,它的唯一作用就是解析。与此对应的是,URL则包含 一个可打开到达该资源的输入流,因此我们可以将URL理解成URI的特例。 URL类提供了多个构造器用于创建URL对象,一旦获得了URL对象之后,可以调用如下方法来访问该URL对应的资源: String getFile():获取此URL的资源名。 String getHost():获取此URL的主机名。 String getPath():获取此URL的路径部分。 int getPort():获取此 URL 的端口号。 String getProtocol():获取此 URL 的协议名称。 String getQuery():获取此 URL 的查询字符串部分。 URLConnection openConnection():返回一个URLConnection对象,它表示到URL所引用的远程对象的连接。 InputStream openStream():打开与此URL的连接,并返回一个用于读取该URL资源的InputStream。 URL对象中前面几个方法都非常容易理解,而该对象提供的openStream()可以读取该URL资源的InputStream,通过该方法可以非常方便地读取远程资源——甚至实现多线程下载。如下程序所示: 程序清单:codes/17/17-2/MutilDown.java //定义下载从start到end的内容的线程 class DownThread extends Thread { //定义字节数组(取水的竹筒)的长度 private final int BUFF_LEN = 32; //定义下载的起始点 private long start; //定义下载的结束点 private long end; //下载资源对应的输入流 private InputStream is; //将下载到的字节输出到raf中 private RandomAccessFile raf ; //构造器,传入输入流,输出流和下载起始点、结束点 public DownThread(long start , long end , InputStream is , RandomAccessFile raf) { //输出该线程负责下载的字节位置 System.out.println(start + "---->" + end); this.start = start; this.end = end; this.is = is; this.raf = raf; } public void run() { try { is.skip(start); raf.seek(start); //定义读取输入流内容的缓存数组(竹筒) byte[] buff = new byte[BUFF_LEN]; //本线程负责下载资源的大小 long contentLen = end - start; //定义最多需要读取几次就可以完成本线程的下载 long times = contentLen / BUFF_LEN + 4; //实际读取的字节数 int hasRead = 0; for (int i = 0; i < times ; i++) { hasRead = is.read(buff); //如果读取的字节数小于0,则退出循环! if (hasRead < 0) { break; } raf.write(buff , 0 , hasRead); } } catch (Exception ex) { ex.printStackTrace(); } //使用finally块来关闭当前线程的输入流、输出流 finally { try { if (is != null) { is.close(); } if (raf != null) { raf.close(); } } catch (Exception ex) { ex.printStackTrace(); } } } } public class MutilDown { public static void main(String[] args) { final int DOWN_THREAD_NUM = 4; final String OUT_FILE_NAME = "down.jpg"; InputStream[] isArr = new InputStream[DOWN_THREAD_NUM]; RandomAccessFile[] outArr = new RandomAccessFile[DOWN_THREAD_NUM]; try { //创建一个URL对象 URL url = new URL("http://images.china-pub.com/ + "ebook35001-40000/35850/shupi.jpg"); //以此URL对象打开第一个输入流 isArr[0] = url.openStream(); long fileLen = getFileLength(url); System.out.println("网络资源的大小" + fileLen); //以输出文件名创建第一个RandomAccessFile输出流 outArr[0] = new RandomAccessFile(OUT_FILE_NAME , "rw"); //创建一个与下载资源相同大小的空文件 for (int i = 0 ; i < fileLen ; i++ ) { outArr[0].write(0); } //每线程应该下载的字节数 long numPerThred = fileLen / DOWN_THREAD_NUM; //整个下载资源整除后剩下的余数 long left = fileLen % DOWN_THREAD_NUM; for (int i = 0 ; i < DOWN_THREAD_NUM; i++) { //为每个线程打开一个输入流、一个RandomAccessFile对象, //让每个线程分别负责下载资源的不同部分。 if (i != 0) { //以URL打开多个输入流 isArr[i] = url.openStream(); //以指定输出文件创建多个RandomAccessFile对象 outArr[i] = new RandomAccessFile(OUT_FILE_NAME , "rw"); } //分别启动多个线程来下载网络资源 if (i == DOWN_THREAD_NUM - 1 ) { //最后一个线程下载指定numPerThred+left个字节 new DownThread(i * numPerThred , (i + 1) * numPerThred + left , isArr[i] , outArr[i]).start(); } else { //每个线程负责下载一定的numPerThred个字节 new DownThread(i * numPerThred , (i + 1) * numPerThred, isArr[i] , outArr[i]).start(); } } } catch (Exception ex) { ex.printStackTrace(); } } //定义获取指定网络资源的长度的方法 //定义获取指定网络资源的长度的方法 public static long getFileLength(URL url) throws Exception { long length = 0; //打开该URL对应的URLConnection。 URLConnection con = url.openConnection(); //获取连接URL资源的长度 long size = con.getContentLength(); length = size; return length; } } 上面程序中定义了DownThread线程类,该线程从InputStream中读取从start开始,到end结束的所有字节数据,并写入 RandomAccessFile对象。这个DownThread线程类的run()就是一个简单的输入、输出实现。 程序中MutilDown类中的main方法负责按如下步骤来实现多线程下载: 创建URL对象。 获取指定URL对象所指向资源的大小(由getFileLength方法实现),此处用到了URLConnection类,该类代表Java应用程序和URL之间的通信链接。下面还有关于URLConnection更详细的介绍。 在本地磁盘上创建一个与网络资源相同大小的空文件。 计算每条线程应该下载网络资源的哪个部分(从哪个字节开始,到哪个字节结束)。 依次创建、启动多条线程来下载网络资源的指定部分。 上面程序已经实现了多线程下载的核心代码,如果要实现断点下载,则还需要额外增加一个配置文件(读者可以发现所有断点下载工具都会在下载开始生成两 个文件:一个是与网络资源相同大小的空文件,一个是配置文件),该配置文件分别记录每个线程已经下载到了哪个字节,当网络断开后再次开始下载时,每个线程 根据配置文件里记录的位置向后下载即可。 URL的openConnection()方法将返回一个URLConnection对象,该对象表示应用程序和 URL 之间的通信链接。程序可以通过URLConnection实例向该URL发送请求、读取URL引用的资源。 通常创建一个和 URL的连接,并发送请求、读取此URL引用的资源需要如下几个步骤: 通过调用URL对象openConnection()方法来创建URLConnection对象。 设置URLConnection的参数和普通请求属性。 如果只是发送GET方式请求,使用connect方法建立和远程资源之间的实际连接即可;如果需要发送POST方式的请求,需要获取 URLConnection实例对应的输出流来发送请求参数。 远程资源变为可用,程序可以访问远程资源的头字段或通过输入流读取远程资源的数据。 在建立和远程资源的实际连接之前,程序可以通过如下方法来设置请求头字段: setAllowUserInteraction:设置该URLConnection的allowUserInteraction请求头字段的值。 setDoInput:设置该URLConnection的doInput请求头字段的值。 setDoOutput:设置该URLConnection的doOutput请求头字段的值。 setIfModifiedSince:设置该URLConnection的ifModifiedSince请求头字段的值。 setUseCaches:设置该URLConnection的useCaches请求头字段的值。 除此之外,还可以使用如下方法来设置或增加通用头字段: setRequestProperty(String key, String value):设置该URLConnection的key请求头字段的值为value。如下代码所示: conn.setRequestProperty("accept" , "*/*") addRequestProperty(String key, String value):为该URLConnection的key请求头字段的增加value值,该方法并不会覆盖原请求头字段的值,而是将新值追加到原请求头字段中。 当远程资源可用之后,程序可以使用以下方法用于访问头字段和内容: Object getContent():获取该URLConnection的内容。 String getHeaderField(String name):获取指定响应头字段的值。 getInputStream():返回该URLConnection对应的输入流,用于获取URLConnection响应的内容。 getOutputStream():返回该URLConnection对应的输出流,用于向URLConnection发送请求参数。 如果既要使用输入流读取URLConnection响应的内容,也要使用输出流发送请求参数,一定要先使用输出流,再使用输入流。 getHeaderField方法用于根据响应头字段来返回对应的值。而某些头字段由于经常需要访问,所以Java提供以下方法来访问特定响应头字段的值: getContentEncoding:获取content-encoding响应头字段的值。 getContentLength:获取content-length响应头字段的值。 getContentType:获取content-type响应头字段的值。 getDate():获取date响应头字段的值。 getExpiration():获取expires响应头字段的值。 getLastModified():获取last-modified响应头字段的值。 下面程序示范了如何向Web站点发送GET请求、POST请求,并从Web站点取得响应的示例。 程序清单:codes/17/17-2/TestGetPost.java public class TestGetPost { /** * 向指定URL发送GET方法的请求 * @param url 发送请求的URL * @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。 * @return URL所代表远程资源的响应 */ public static String sendGet(String url , String param) { String result = ""; BufferedReader in = null; try { String urlName = url + "?" + param; URL realUrl = new URL(urlName); //打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); //设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); //建立实际的连接 conn.connect(); //获取所有响应头字段 Map > map = conn.getHeaderFields(); //遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } //定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine())!= null) { esult += "/n" + line; } } catch(Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } //使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } /** * 向指定URL发送POST方法的请求 * @param url 发送请求的URL * @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。 * @return URL所代表远程资源的响应 */ public static String sendPost(String url,String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(url); //打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); //设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); //发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); //获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); //发送请求参数 out.print(param); //flush输出流的缓冲 out.flush(); //定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine())!= null) { result += "/n" + line; } } catch(Exception e) { System.out.println("发送POST请求出现异常!" + e); e.printStackTrace(); } //使用finally块来关闭输出流、输入流 finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } //提供主方法,测试发送GET请求和POST请求 public static void main(String args[]) { //发送GET请求 String s = TestGetPost.sendGet("http://localhost:8888/abc/ login.jsp",null); System.out.println(s); //发送POST请求 String s1 = TestGetPost.sendPost("http://localhost:8888/abc/a.jsp", "user=李刚&pass=abc"); System.out.println(s1); } } 上面程序中发送GET请求时只需将请求参数放在URL字符串之后,以?隔开,程序直接调用URLConnection对象的connect方法即 可,如程序中sendGet方法中粗体字代码所示;如果程序需要发送POST请求,则需要先设置doIn和doOut两个请求头字段的值,再使用 URLConnection对应的输出流来发送请求参数即可,如程序中sendPost()方法中粗体字代码所示。 不管是发送GET请求,还是发送POST请求,程序获取URLConnection响应的方式完全一样:如果程序可以确定远程响应是字符流,则可以使用字符流来读取;如果程序无法确定远程响应是字符流,则使用字节流读取即可。 上面程序中发送请求的两个URL是笔者在本机部署的Web应用,关于如何创建Web应用,编写JSP页面请参考笔者所著的《轻量级J2EE企业应用 实战》。由于程序可以使用这种方式直接向服务器发送请求——相当于提交Web应用中的登录表单页,这样就可以让程序不断地变换用户名、密码来提交登录请 求,直到返回登录成功,这就是所谓的暴力破解。 转载于:http://hi.baidu.com/lfcaolibin/blog/item/25bf243205ce1248ad4b5fe5.html

你可能感兴趣的:(多线程,exception,String,null,企业应用,url)