[疯狂Java]网络:URL、URLConnection

来源:互联网 发布:电脑玩手游用什么软件 编辑:程序博客网 时间:2024/05/16 00:44

1. URL类:

    1) 其对象用来表示一个URL地址;

    2) URL格式:protocol://host_name:port/resource_path?arg1=val1&arg2=val2...

    3) 构造器:URL(String spec); // spce就是一个URL地址(字符串)

    4) 通过URL提供的对象方法获取关于URL的信息:

         i. String getProtocol(); // 返回protocol部分

         ii. String getHost(); // 返回host_name部分

         iii. int getPort(); // 返回port这个数字(整型)

         iv. String getPath(); // 返回/resource_path,注意开头的/表示应用程序的环境根目录

         v. String getQuery(); // 返回arg1=val1&arg2=val2...部分,即请求参数部分

         vi. String getFile(); // 返回/resource_path到最后(包括中间的?),这里的file指的是资源,返回的是资源的完整名称,包括请求参数部分

!!以上方法对URL的解析完全就是基于字符串的,如果你传的URL是"www.xxx.com",而你调用的是getProtocol则会抛出异常,因为你给的字符串中并没有协议部分!

!!因此URL给出的解析方法仅仅就是纯字符串层面上的解析,并不会像大多数浏览器那样可以智能解析,比如你在浏览器中只输入"www.xxx.com"就可以自动判断出该网站使用的通信协议,而在底层自动补齐缺少的URL部分;

    5) URL的最重要的对象方法就是openConnection,用于和服务器建立连接:

         i. 原型:URLConnection openConnection();

         ii. 该方法会根据里面包装的URL地址(构造时传入的URL地址)和目标服务器建立连接,并返回表示该连接的句柄URLConnection对象;

         iii. 该步骤就是三次握手协议的第一步,即建立连接(后面两步是请求、应答),但是其它什么都没做;


2. 使用URLConnection和服务器进行交流:

    1) URLConnection类的对象表示本机与URL所引用的资源的远程连接的对象;

    2) 获的了URLConnection连接句柄后只是完成了三次握手的第一步,即建立连接,但并没有发生实质的数据交流;

    3) 但在进行数据交流之前通常应该先商量好一些前提,比如客户端能接受什么语言、什么编码,这次只交换什么类型的数据等等,这都需要在进行数据交换之前先商量好,这就需要设置请求条件了;

    4) 设置请求条件:使用URLConnection提供的对象方法进行设置

         i. void setConnectTimeout(int timeout); // 设置客户端等待响应时长(单位是毫秒)

         ii. void setRequestMethod(String method); // 设置请求方式,只能是"GET"、"POST"、"TRACE"等字符串中之一,但必须是服务器端支持的,否则会抛出异常

         iii. void setRequestProperty(String key, String value); // 设置请求属性(即标头中的键值对,在标头中格式是"key:value"),如果有多条属性那就多次调用该方法一条一条添加

!!只有设置完请求条件之后才能正常通信;

    5) 通过connection向服务器端发送请求:还是调用URLConnection的对象方法完成,这里只介绍几种常用的方法

         i. int getContentLength(); // 请求资源的大小(服务器返回URL定位的资源的大小)

         ii. InputStream getInputStream(); // 请求远程资源的下载链接,返回的InputStream其实不在本地的内存中,而是在服务器端的内存中,但用起来跟平时使用的InputStream没有两样,Java在底层隐藏了这些细节,在调用其read方法时其实是从远程下载到本地

!!完整的一次上述方法的调用就是完成了三次握手的后两步(请求-响应);

    6) 实际使用的时候用的是URLConnection的实现类,URLConnection过于抽象,现实中进行网络通信都是要基于某种通信协议的,其中最常用的就是HTTP协议,因此用的最广泛的一个实现类就是HttpURLConnection了,前面讲的方法它都有,这里再补充一个HttpURLConnection独有的方法disconnect:

        i. 原型:void HttpURLConnection.disconnect(); // 断开本次连接

        ii. 每当完成一次完整的请求响应就应该及时关闭连接资源,这很重要,否则这些资源会一直占用内存,如果局部数据庞大会导致运行效率下降;


3. 示例:多线程下载工具

// DownUitl.javapublic class DownUtil {private String url; // 目标资源的URLprivate String savePath; // 下载的文件保存在本机的位置private int threadNum; // 线程数private int resSize; // 资源的大小(字节)private DownThread[] threads; // 下载线程组public DownUtil(String url, String savePath, int threadNum) {this.url = url;this.savePath = savePath;this.threadNum = threadNum;threads = new DownThread[threadNum];}private static HttpURLConnection getConnection(String url) throws IOException {URL urlObj = new URL(url); // 创建URL对象HttpURLConnection conn = (HttpURLConnection)urlObj.openConnection(); // 建立Http连接conn.setConnectTimeout(5000); // 设置响应时间不超过5秒conn.setRequestMethod("GET"); // 请求方式为GET// 设置请求属性(标头中的内容)conn.setRequestProperty("Accept", // 设置请求端可以接受的资源类型(用MIME指定)"image/gif, image/jpeg, image/pjpeg, image/pjpeg, " +"application/x-shockwave-flash, application/xaml+xml, " +"application/vnd.ms-xpsdocument, application/x-ms-xbap, " +"application/x-ms-application, application/vnd.ms-excel, " +"application/vnd.ms-powerpoint, application/msword, */*");conn.setRequestProperty("Accept-Language", "zh-CN"); // 设置请求端可接受的编码conn.setRequestProperty("Charset", "UTF-8"); // 设置通信使用的字符集conn.setRequestProperty("Connection", "Keep-Alive"); // 要求连接不是一次性的,而是长时间保持return conn;}public void download() throws IOException {HttpURLConnection conn = getConnection(url);resSize = conn.getContentLength(); // 获取资源的大小(字节)conn.disconnect(); // 此次建立连接仅仅是为了获取resSize信息为后面的下载做准备,因此就先关闭连接int partSize = resSize / threadNum + 1; // 计算每条线程平均下载多少字节RandomAccessFile totalFile = new RandomAccessFile(savePath, "rw"); // 然后创建保存的文件totalFile.setLength(resSize); // 只预留空间,但先不下载东西进去,下载的工作留给线程处理totalFile.close(); // 仅仅创建一个文件,创建完就关掉for (int i = 0; i < threadNum; i++) {// 每个线程写的都是同一个文件,因此文件不能加锁(不同步)// 直接并发访问,只不过每条线程访问文件的不同部分,这些部分之间没有交集// 因此没关系int startPos = i * partSize; // 每个线程的起始下载点RandomAccessFile partFile = new RandomAccessFile(savePath, "rw"); // 获取文件句柄partFile.seek(startPos); // 定位到起始点threads[i] = new DownThread(i + 1, startPos, partSize, partFile); // 创建并启动线程threads[i].start();}}public double getCompleteRate() {int sum = 0;for (int i = 0; i < threadNum; i++) {sum += threads[i].getCompleted();}return sum * 100.0 / resSize;}private class DownThread extends Thread {private int threadNo;private int startPos;private int partSize;private RandomAccessFile partFile;private int completed; // 当前完成了多少字节public DownThread(int threadNo, int startPos, int partSize, RandomAccessFile partFile) {this.threadNo = threadNo;this.startPos = startPos;this.partSize = partSize;this.partFile = partFile;this.setName("Sub Thread " + this.threadNo);}public int getCompleted() {return completed;}@Overridepublic void run() {// TODO Auto-generated method stub// super.run();try {System.out.println(getName() + " started!");HttpURLConnection conn = getConnection(url); // 获取连接InputStream is = conn.getInputStream(); // 获取远程资源的输入流is.skip(startPos); // 跳过startPos个字节,从自己负责的那部分开始下载byte[] buf = new byte[1024]; // 下载缓冲区int lenRead = 0; // 每次读取了多少个字节while (completed < partSize && (lenRead = is.read(buf)) != -1) {partFile.write(buf, 0, lenRead);completed += lenRead;} // 多处partSize的部分会被其他线程覆盖掉,没关系partFile.close(); // 关闭资源is.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

// Test.javapublic class Test {public static void main(String[] args) throws IOException {// TODO Auto-generated method stubfinal DownUtil downUtil = new DownUtil("http://www.crazyit.org/"+ "attachments/month_1403/1403202355ff6cc9a4fbf6f14a.png", "ios.png", 4);downUtil.download();Thread showComplete = new Thread(() -> { // 实时显示完成比例的线程放入后台while (true) {System.out.println(downUtil.getCompleteRate() + "% compeleted.");try {Thread.sleep(1000);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}, "Complete Rate Thread");showComplete.setDaemon(true);showComplete.start();}}
!!还可以基于上面的代码实现断点下载,只不过断点下载还需要一个配置文件,记录每次断开之前下载到了哪个位置;

0 0
原创粉丝点击