第 17 章 网络编程

第 17 章 网络编程

现代的应用程序都离不开网络,网络编程是非常重要的技术。Java SE提供java.net包,其中包含了网络编程所需要的最基础一些类和接口。这些类和接口面向两个不同的层次:基于Socket的低层次网络编程和基于URL的高层次网络编程,所谓高低层次就是通信协议的高低层次,Socket采用TCP、UDP等协议,这些协议属于低层次的通信协议;URL采用HTTP和HTTPS这些属于高层次的通信协议。低层次网络编程,因为它面向底层,比较复杂,但是“低层次网络编程”并不等于它功能不强大。恰恰相反,正因为层次低,Socket编程与基于URL的高层次网络编程比较,能够提供更强大的功能和更灵活的控制,但是要更复杂一些。

本章会介绍基于URL的高层次网络编程,以及数据交换格式。

17.1 网络基础

网络编程需要程序员掌握一下基础的网络知识,这一节先介绍一些网络基础知识。

17.1.1 网络结构

首先了解一下网络结构,网络结构是网络的构建方式,目前流行的有客户端服务器结构网络和对等结构网络。

  1. 客户端服务器结构网络

    客户端服务器(Client Server,缩写C/S)结构网络,是一种主从结构网络。如图17-1所示,服务器一般处于等待状态,如果有客户端请求,服务器响应请求建立连接提供服务。服务器是被动的,有点像在餐厅吃饭时候的服务员。而客户端是主动的,像在餐厅吃饭的顾客。

    {%}

    图17-1 客户端服务器结构网络

    事实上,生活中很多网络服务都采用这种结构。例如:Web服务、文件传输服务和邮件服务等。虽然它们存在的目的不一样,但基本结构是一样的。这种网络结构与设备类型无关,服务器不一定是电脑,也可能是手机等移动设备。

  2. 对等结构网络

    对等结构网络也叫点对点网络(Peer to Peer,缩写P2P),每个节点之间是对等的。它们如图17-2所示,每个节点既是服务器又是客户端,这种结构有点像吃自助餐。

    {%}

    图17-2 对等结构网络

    对等结构网络分布范围比较小。通常在一间办公室或一个家庭内,因此它非常适合于移动设备间的网络通讯,网络链路层是由蓝牙和WiFi实现。

17.1.2 TCP/IP协议

网络通信会用到协议,其中TCP/IP协议是非常重要的。TCP/IP协议是由IP和TCP两个协议构成的,IP(Internet Protocol)协议是一种低级的路由协议,它将数据拆分成许多小的数据包中,并通过网络将它们发送到某一特定地址,但无法保证都所有包都抵达目的地,也不能保证包的顺序。

由于IP协议传输数据的不安全性,网络通信时还需要TCP协议,传输控制协议(Transmission Control Protocol,TCP)是一种高层次的协议,面向连接的可靠数据传输协议,如果有些数据包没有收到会重发,并对数据包内容准确性检查并保证数据包顺序,所以该协议保证数据包能够安全地按照发送时顺序送达目的地。

17.1.3 IP地址

为实现网络中不同计算机之间的通信,每台计算机都必须有一个与众不同的标识,这就是IP地址,TCP/IP使用IP地址来标识源地址和目的地址。最初所有的IP地址都是32位数字构成,由4个8位的二进制数组成,每8位之间用圆点隔开,如:192.168.1.1,这种类型的地址通过IPv4指定。而现在有一种新的地址模式称为IPv6,IPv6使用128位数字表示一个地址,分为8个16位块。尽管IPv6比IPv4有很多优势,但是由于习惯的问题,很多设备还是采用IPv4。不过Java语言同时指出IPv4和IPv6。

在IPv4地址模式中IP地址分为A、B、C、D和E等5类。

  • A类地址用于大型网络,地址范围:1.0.0.1~126.155.255.254。

  • B类地址用于中型网络,地址范围:128.0.0.1~191.255.255.254。

  • C类地址用于小规模网络,192.0.0.1~223.255.255.254。

  • D类地址用于多目的地信息的传输和作为备用。

  • E类地址保留仅作实验和开发用。

另外,有时还会用到一个特殊的IP地址127.0.0.1,127.0.0.1称为回送地址,指本机。主要用于网络软件测试以及本地机进程间通信,使用回送地址发送数据,不进行任何网络传输,只在本机进程间通信。

17.1.4 端口

一个IP地址标识这一台计算机,每一台计算机又有很多网络通信程序在运行,提供网络服务或进行通信,这就需要不同的端口进行通信。如果把IP地址比作电话号码,那么端口就是分机号码,进行网络通信时不仅要指定IP地址,还要指定端口号。

TCP/IP系统中的端口号是一个16位的数字,它的范围是0~65535。小于1024的端口号保留给预定义的服务,如HTTP是80,FTP是21,Telnet是23,Email是25等,除非要和那些服务进行通信,否则不应该使用小于1024的端口。

17.2 数据交换格式

数据交换格式就像两个人在聊天一样,采用彼此都能听得懂的语言,你来我往,其中的语言就相当于通信中的数据交换格式。有时候,为了防止聊天被人偷听,可以采用暗语。同理,计算机程序之间也可以通过数据加密技术防止“偷听”。

数据交换格式主要分为纯文本格式、XML格式和JSON格式,其中纯文本格式是一种简单的、无格式的数据交换方式。

例如,为了告诉别人一些事情,我会写下如图17-3所示的留言条。

{%}

图17-3 留言条

留言条有一定的格式,共有4部分:称谓、内容、落款和时间,如图17-4所示。

{%}

图17-4 留言条格式

如果用纯文本格式描述留言条,可以按照如下的形式:

"云龙同学","你好!\n今天上午,我到你家来想向你借一本《小学生常用成语词典》。可是不巧,你不在。我准备晚上6时再来借书。请你在家里等我,谢谢!","关东升","2012年12月08日"

留言条中的4部分数据按照顺序存放,各个部分之间用逗号分隔。数据量小的时候,可以采用这种格式。但是随着数据量的增加,问题也会暴露出来,可能会搞乱它们的顺序,如果各个数据部分能有描述信息就好了。而XML格式和JSON格式可以带有描述信息,它们叫做“自描述的”结构化文档。

将上面的留言条写成XML格式,具体如下:

<?xml version="1.0" encoding="UTF-8"?>
<note>
    <to>云龙同学</to>
    <conent>你好!\n今天上午,我到你家来想向你借一本《小学生常用成语词典》。
        可是不巧,你不在。我准备晚上6时再来借书。请你在家里等我,谢谢!</conent>
    <from>关东升</from>
    <date>2012年12月08日</date>
</note>

上述代码中位于尖括号中的内容(<to>…</to>等)就是描述数据的标识,在XML中称为“标签”。

将上面的留言条写成JSON格式,具体如下:

{to:"云龙同学",conent:"你好!\n今天上午,我到你家来想向你借一本《小学生常用成语词典》。可是不巧,你不在。我准备晚上6时再来借书。请你在家里等我,谢谢!",from:"关东升",date:"2012年12月08日"}

数据放置在大括号{}之中,每个数据项目之前都有一个描述名字(如to等),描述名字和数据项目之间用冒号(:)分开。

可以发现,一般来讲,JSON所用的字节数要比XML少,这也是很多人喜欢采用JSON格式的主要原因,因此JSON也被称为“轻量级”的数据交换格式。接下来,重点介绍JSON数据交换格式。

17.2.1 JSON文档结构

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。所谓轻量级,是与XML文档结构相比而言的,描述项目的字符少,所以描述相同数据所需的字符个数要少,那么传输速度就会提高,而流量却会减少。

如果留言条采用JSON描述,可以设计成下面的样子:

{"to":"云龙同学",
    "conent": "你好!\n今天上午,我到你家来想向你借一本《小学生常用成语词典》。可是
        不巧,你不在。我准备晚上6时再来借书。请你在家里等我,谢谢!",
    "from": "关东升",
    "date": "2012年12月08日"}

由于Web和移动平台开发对流量的要求是要尽可能少,对速度的要求是要尽可能快,而轻量级的数据交换格式JSON就成为理想的数据交换格式。

构成JSON文档的两种结构为对象和数组。对象是“名称-值”对集合,它类似于Java中Map类型,而数组是一连串元素的集合。

对象是一个无序的“名称/值”对集合,一个对象以{(左括号)开始,}(右括号)结束。每个“名称”后跟一个:(冒号),“名称-值”对之间使用,(逗号)分隔。JSON对象的语法表如图17-5所示。

{%}

图17-5 JSON对象的语法表

下面是一个JSON对象的例子:

{
    "name":"a.htm",
    "size":345,
    "saved":true
}

数组是值的有序集合,以[(左中括号)开始,](右中括号)结束,值之间使用,(逗号)分隔。JSON数组的语法表如图17-6所示。

{%}

图17-6 JSON数组的语法表

下面是一个JSON数组的例子:

["text","html","css"]

在数组中,值可以是双引号括起来的字符串、数值、true、false、null、对象或者数组,而且这些结构可以嵌套。数组中值的JSON语法结构如图17-7所示。

{%}

图17-7 JSON值的语法结构图

17.2.2 使用第三方JSON库

由于目前Java官方没有提供JSON编码和解码所需要的类库,所以需要使用第三方JSON库,笔者推荐JSON-java库,JSON-java库提供源代码,最重要的是不依赖于其他第三方库,需要再起找其他的库了。读者可以在https://github.com/stleary/JSON-java下载源代码。API在线文档http://stleary.github.io/JSON-java/index.html

下载JSON-java获得源代码文件,解压后文件如图17-8所示。

{%}

图17-8 JSON-java源代码文件

将JSON-java库源代码文件添加到工程中,需要两个步骤:

  1. 创建org.json包

    JSON-java库中的源代码文件都隶属于org.json包,从图17-8可见源文件夹下没有与包对应的目录结构,为此需要在Eclipse的项目中创建org.json包。选择Eclipse项目的src源代码文件夹,右击菜单中选择“新建”→“包”,弹出新建包对话框,如图17-9所示在名称的中输入org.json,然后单击完成,就成功创建org.json包。

    {%}

    图17-9 在Eclipse中创建包

  2. 复制源代码文件

    org.json包创建好后,需要将JSON-java库文件夹中的源代码文件复制到Eclipse工程的org.json包中。由于操作系统的资源管理器与Eclipse工具之间互相复制粘贴,Eclipse中复制和粘贴操作的快捷键和右键菜单与操作系统下完全一样。如图17-10所示,将源代码文件复制到Eclipse中。

    {%}

    图17-10 复制源代码文件到Eclipse工程

17.2.3 JSON数据编码和解码

JSON和XML真正在进行数据交换时候,它们存在的形式就是一个很长的字符串,这个字符串在网络中传输或者存储于磁盘等介质中。在传输和存储之前需要把JSON对象转换成为字符串才能传输和存储,这个过程称之为“编码”过程。接收方需要将接收到的字符串转换成为JSON对象,这个过程称之为“解码”过程。编码和解码过程就像发电报时发送方把语言变成能够传输的符号,而接收时要将符号转换成为能够看懂的语言。

下面具体介绍一下JSON数据编码和解码过程。

  1. 编码

    如果想获得如下这样JSON字符串:

    {"name": "tony", "age": 30, "a": [1, 3]}
    
    

    应该如何实现编码过程,参考代码如下:

    try {
        JSONObject jsonObject = new JSONObject();    ①
        jsonObject.put("name", "tony");                ②
        jsonObject.put("age", 30);                ③
     
        JSONArray jsonArray = new JSONArray();        ④
        jsonArray.put(1).put(3);                    ⑤
        jsonObject.put("a", jsonArray);                ⑥
        //编码完成
        System.out.println(jsonObject.toString());        ⑦
    } catch (JSONException e) {
        e.printStackTrace();
    }
    
    

    上述代码第①行是创建JSONObject(JSON对象),代码第②行和第③行是把JSON数据项添加到JSON对象jsonObject中,代码第④行创建JSONArray(JSON数组),代码第⑤行是向JSON数组中添加1和3两个元素。代码第⑥是将JSON数组jsonArray作为JSON对象jsonObject的数据项添加到JSON对象。

    代码第⑦行jsonObject.toString()是将JSON对象转换为字符串,真正完成了JSON编码过程。

  2. 解码

    解码过程是编码反向操作,如果有如下JSON字符串:

    {"name":"tony", "age":30, "a":[1, 3]}
    
    

    那么如何把这个JSON字符串解码成JSON对象或数组,参考代码如下:

    String jsonString = "{\"name\":\"tony\", \"age\":30, \"a\":[1, 3]}";    ①
    try {
        JSONObject jsonObject = new JSONObject(jsonString);    ②
        String name = jsonObject.getString("name");            ③
        System.out.println("name : " + name);
        int age = jsonObject.getInt("age");
        System.out.println("age : " + age);
        JSONArray jsonArray = jsonObject.getJSONArray("a");    ④
        int n1 = jsonArray.getInt(0);                          ⑤
        System.out.println("数组a第一个元素 : " + n1);
        int n2 = jsonArray.getInt(1);
        System.out.println("数组a第二个元素 : " + n2);
    } catch (JSONException e) {
        e.printStackTrace();
    }
    
    

    上述代码第①行是声明一个JSON字符串,网络通信过程中JSON字符串是从服务器返回的。代码第②行通过JSON字符串创建JSON对象,这个过程事实上就是JSON字符串解析过程,如果能够成功地创建JSON对象,说明解析成功,如果发生异常则说明解析失败。

    代码第③行从JSON对象中按照名称取出JSON中对应的数据。代码第④行是取出一个JSON数组对象,代码第⑤行取出JSON数组第一个元素。

    注意 如果按照规范的JSON文档要求,每个JSON数据项目的“名称”必须使用双引号括起来,不能使用单引号或没有引号。在下面的代码文档中,“名称”省略了双引号,该文档在其他平台解析时会出现异常,而在Java平台则可以通过,这得益于Java解析类库的强大,但这并不是规范的做法。如果与其他平台进行数据交换时,采用这种不规范的JSON文档进行数据交换,那么很有可能会导致严重的问题发生。

    {ResultCode:0,Record:[
        {ID:'1',CDate:'2012-12-23',Content:'发布iOSBook0',UserID:'tony'},
        {ID:'2',CDate:'2012-12-24',Content:'发布iOSBook1',UserID:'tony'}]}。
    
    

17.3 访问互联网资源

Java的java.net包中还提供了高层次网络编程类——URL,通过URL类访问互联网资源。使用URL进行网络编程,不需要对协议本身有太多的了解,相对而言是比较简单的。

17.3.1 URL概念

互联网资源是通过URL指定的,URL是Uniform Resource Locator简称,翻译过来是“一致资源定位器”,但人们都习惯URL简称。

URL组成格式如下:

协议名://资源名

“协议名”指明获取资源所使用的传输协议,如http、ftp、gopher和file等,“资源名”则应该是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。例如:

http://www.sina.com/
http://home.sohu.com/home/welcome.html
http://www.51work6.com:8800/Gamelan/network.html#BOTTOM

17.3.2 HTTP/HTTPS协议

访问互联网大多都基于HTTP/HTTPS协议。下面介绍一下HTTP/HTTPS协议。

  1. HTTP协议

    HTTP是Hypertext Transfer Protocol的缩写,即超文本传输协议。HTTP是一个属于应用层的面向对象的协议,其简捷、快速的方式适用于分布式超文本信息的传输。它于1990年提出,经过多年的使用与发展,得到不断完善和扩展。HTTP协议支持C/S网络结构,是无连接协议,即每一次请求时建立连接,服务器处理完客户端的请求后,应答给客户端然后断开连接,不会一直占用网络资源。

    HTTP/1.1协议共定义了8种请求方法:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE和CONNECT。在HTTP访问中,一般使用GET和HEAD方法,其他方法都是可选的。

    • GET方法:是向指定的资源发出请求,发送的信息“显式”地跟在URL后面。GET方法应该只用在读取数据,例如静态图片等。GET方法有点像使用明信片给别人写信,“信内容”写在外面,接触到的人都可以看到,因此是不安全的。

    • POST方法:是向指定资源提交数据,请求服务器进行处理,例如提交表单或者上传文件等。数据被包含在请求体中。POST方法像是把“信内容”装入信封中,接触到的人都看不到,因此是安全的。

  2. HTTPS协议

    HTTPS是Hypertext Transfer Protocol Secure,即超文本传输安全协议,是超文本传输协议和SSL的组合,用以提供加密通信及对网络服务器身份的鉴定。

    简单地说,HTTPS是HTTP的升级版,HTTPS与HTTP的区别是:HTTPS使用https://代替http://,HTTPS使用端口443,而HTTP使用端口80来与TCP/IP进行通信。SSL使用40位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。HTTPS和SSL支持使用X.509数字认证,如果需要的话,用户可以确认发送者是谁。

17.3.3 使用URL类

Java 的java.net.URL类用于请求互联网上的资源,采用HTTP/HTTPS协议,请求方法是GET方法,一般是请求静态的、少量的服务器端数据。

URL类常用构造方法:

  • URL(String spec):根据字符串表示形式创建URL对象。

  • URL(String protocol, String host, String file):根据指定的协议名、主机名和文件名称创建URL对象。

  • URL(String protocol, String host, int port, String file):根据指定的协议名、主机名、端口号和文件名称创建URL对象。

URL类常用方法:

  • InputStream openStream():打开到此URL的连接,并返回一个输入流。

  • URLConnection openConnection():打开到此URL的新连接,返回一个URLConnection对象。

下面通过一个示例介绍一下如何使用java.net.URL类,示例代码如下:

//HelloWorld.java文件
package com.a51work6;
...
public class HelloWorld {

    public static void main(String[] args) {
        // Web网址
        String url = "http://www.sina.com.cn/";

        URL reqURL;
        try {
            reqURL = new URL(url);                                 ①
        } catch (MalformedURLException e1) {
            return;
        }

        try ( // 打开网络通信输入流
                InputStream is = reqURL.openStream();                    ②
                InputStreamReader isr = new InputStreamReader(is, "utf-8");
                BufferedReader br = new BufferedReader(isr)) {

            StringBuilder sb = new StringBuilder();
            String line = br.readLine();
            while (line != null) {
                sb.append(line);
                sb.append('\n');
                line = br.readLine();
            }
            // 日志输出
            System.out.println(sb);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上述代码第①行创建URL对象,参数是一个HTTP网址。代码第②行通过URL对象的openStream()方法打开输入流。

17.3.4 使用HttpURLConnection发送GET请求

由于URL类只能发送HTTP/HTTPS的GET方法请求,如果要想发送其他的情况或者对网络请求有更深入的控制时,可以使用HttpURLConnection类型。

示例代码如下:

//HelloWorld.java文件
package com.a51work6;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HelloWorld {

    // Web服务网址
    static String urlString = "http://www.51work6.com/service/mynotes/WebService.php?"
            + "email=<换成你在51work6.com注册时填写的邮箱>&type=JSON&action=query"; ①

    public static void main(String[] args) {

        BufferedReader br = null;
        HttpURLConnection conn = null;

        try {
            URL reqURL = new URL(urlString);
            conn = (HttpURLConnection) reqURL.openConnection();            ②
            conn.setRequestMethod("GET");                                  ③

            // 打开网络通信输入流
            InputStream is = conn.getInputStream();                         ④
            // 通过is创建InputStreamReader对象
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            // 通过isr创建BufferedReader对象
            br = new BufferedReader(isr);

            StringBuilder sb = new StringBuilder();
            String line = br.readLine();
            while (line != null) {
                sb.append(line);
                line = br.readLine();
            }
            // 日志输出
            System.out.println(sb);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();                            ⑤
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

上述代码第①行是一个Web服务网址字符串。

提示 发送GET请求时发送给服务器的参数是放在URL的“?”之后,参数采用键值对形式,例如:第①行的URL中type=JSON是一个参数,type是参数名,JSON是参数名,服务器端会根据参数名获得参数值。多个参数之间用“&”分隔,例如type=JSON&action=query就是两个参数。

代码第②行是用reqURL.openConnection()方法打开一个连接,返回URLConnection对象,由于本次连接是HTTP连接,所以返回的是HttpURLConnection对象。URLConnection是抽象子类,HttpURLConnection是URLConnection的子类。

代码第③行conn.setRequestMethod("GET")是设置请求方法为GET方法。代码第④行是通过conn.getInputStream()打开输入流,上一节实例使用的URL的openStream()方法获得输入流。代码第⑤行conn.disconnect()是断开连接,这可以释放资源。

从服务器端返回的数据是JSON字符串,格式化后内容如下:

{
    "ResultCode": 0,
    "Record": [
        {
            "ID": 5238,
            "CDate": "2017-05-18",
            "Content": "欢迎来到智捷课堂。"
        },
        {
            "ID": 5239,
            "CDate": "2018-10-18",
            "Content": "Welcome to zhijieketang."
        }
    ]
}

提示 上述示例中URL所指向的Web服务是由作者所在的智捷课堂提供的,读者要想使用这个Web服务需要在www.51work6.com进行注册,注册时需要提供自己有效的邮箱,这个邮箱用来激活用户。在网络请求时需要提交email参数,这个参数是注册时填写的邮箱。

17.3.5 使用HttpURLConnection发送POST请求

HttpURLConnection也可以发送HTTP/HTTPS的POST请求,下面介绍如何使用HttpURLConnection发送POST请求。

示例代码如下:

//HelloWorld.java文件
package com.a51work6;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HelloWorld {

    // Web服务网址
    static String urlString = "http://www.51work6.com/service/mynotes/WebService.php";    ①

    public static void main(String[] args) {

        BufferedReader br = null;
        HttpURLConnection conn = null;
        try {
            URL reqURL = new URL(urlString);
            conn = (HttpURLConnection) reqURL.openConnection();            ②
            conn.setRequestMethod("POST");                                 ③
            conn.setDoOutput(true);                                        ④

            String param = String.format("email=%s&type=%s&action=%s",
                             "<换成你在51work6.com注册时填写的邮箱>", "JSON", "query");  ⑤
            // 设置参数
            DataOutputStream dStream = new DataOutputStream(conn.getOutputStream());   ⑥
            dStream.writeBytes(param);          ⑦
            dStream.close();                    ⑧

            // 打开网络通信输入流
            InputStream is = conn.getInputStream();
            // 通过is创建InputStreamReader对象
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            // 通过isr创建BufferedReader对象
            br = new BufferedReader(isr);

            StringBuilder sb = new StringBuilder();
            String line = br.readLine();
            while (line != null) {
                sb.append(line);
                line = br.readLine();
            }
            // 日志输出
            System.out.println(sb);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

上述代码第①行URL后面不带参数,这是因为要发送的是POST请求,POST请求参数是放在请求体中。代码第②行是通过reqURL.openConnection()是建立HTTP连接,代码第③行是设置HTTP请求方法为POST,代码第④行conn.setDoOutput(true)是设置请求过程可以传递参数给服务器。

代码第⑤是设置请求参数格式化字符串"email=%s&type=%s&action=%s",其中%s是占位符。

代码第⑥行~第⑧行是将请求参数发送给服务器,代码第⑥行中conn.getOutputStream()是打开输出流,new DataOutputStream(conn.getOutputStream())是创建基于数据输出流。代码第⑦行dStream.writeBytes(param)是向输出流中写入数据,第⑧行dStream.close()是关闭流,并将数据写入到服务器端。

17.3.6 实例:Downloader

为了进一步熟悉URL类,这一节介绍一个下载程序Downloader。Downloader.java代码如下:

//Downloader.java文件
package com.a51work6;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class Downloader {

    // Web服务网址
    private static String urlString = "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/"
            + "static/superman/img/logo/bd_logo1_31bdc765.png";

    public static void main(String[] args) {
        download();
    }

    // 下载方法
    private static void download() {

        HttpURLConnection conn = null;

        try {
            // 创建URL对象
            URL reqURL = new URL(urlString);
            // 打开连接
            conn = (HttpURLConnection) reqURL.openConnection();                      ①

            try (// 从连接对象获得输入流
                    InputStream is = conn.getInputStream();                          ②
                    BufferedInputStream bin = new BufferedInputStream(is);           ③
                    // 创建文件输出流
                    OutputStream os = new FileOutputStream("./download.png");        ④
                    BufferedOutputStream bout = new BufferedOutputStream(os);) {     ⑤

                byte[] buffer = new byte[1024];
                int bytesRead = bin.read(buffer);
                while (bytesRead != -1) {
                    bout.write(buffer, 0, bytesRead);
                    bytesRead = bin.read(buffer);
                }
            } catch (IOException e) {
            }
            System.out.println("下载完成。");
        } catch (IOException e) {
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }
}

上述代码第①行打开连接获得HttpURLConnection对象。代码第②行是从连接对象获得输入流。代码第③行创建缓冲流输入流,使用缓冲流可以提高读写效率。

代码第④行是创建文件输出流,代码第⑤行是创建缓冲流输出流。

运行Downloader程序,如果成功会在当前目录获得一张图片。

本章小结

本章主要介绍了Java网络编程,首先介绍了一些网络方面的基本知识。重点介绍了JSON数据交换格式,由于Java官方没有提供JSON解码和编码库,需要是使用第三方库。最后介绍了使用URL类访问互联网资源。