27-网络编程

1. 网络编程概述

Java 是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序

网络程序:能够接收另一台计算机发送过来的数据或能够向另一台计算机发送数据的程序。

Java 提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。

计算机网络 把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。

  • 网络编程的目的:直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
  • 网络编程中有两个主要的问题:
    • 如何准确地定位网络上一台或多台主机(IP);定位主机上的特定的应用(Port)
    • 找到主机后如何可靠高效地进行数据传输

2. 网络通信要素

2.1 实现网络中的主机互相通信

  • 通信双方地址
    • IP
    • 端口号
  • 一定的规则(即:网络通信协议,进行网络中的数据交换(通信)而建立的规则、标准或约定)
    • OSI 参考模型:模型过于理想化,未能在因特网上进行广泛推广
    • TCP/IP 参考模型(或 TCP/IP 协议):事实上的国际标准

2.2 通信要素一:IP和端口号

2.2.1 IP

  • 概述
    • 能够在网络中唯一标识一台主机的编号
    • 网络中每台主机都必须有一个唯一的 IP 地址
    • IP 地址是一个逻辑地址
    • 因特网上的 IP 地址具有全球唯一性
  • 对应 Java 中的类:InetAddress
  • IPv4 和 IPv6
    • IPV4:4 个字节组成,4 个 0-255。大概 42 亿,30 亿都在北美,亚洲 4 亿。2011 年初已经用尽。以点分十进制表示,如192.168.0.1
    • IPV6:128 位(16个字节),写成 8 个无符号整数,每个整数用 4 个十六进制位表示,数之间用冒号 : 分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
  • 本地回环地址(hostAddress):127.0.0.1,主机名(hostName):localhost

2.2.2 端口号

  • 一台计算机上可以同时运行多个网络程序,而一台计算机从网卡接收过来的数据到底应该交给本地哪个网络程序来处理,这是由端口号来决定的;端口号标识正在计算机上运行的进程(程序)
  • 端口是用一个 16 位的数字来表示,范围:0~65535
  • 分类
    • 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP 占用端口 80,FTP 占用端口 21,Telnet 占用端口 23)
    • 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat 占用端口 8080,MySQL 占用端口 3306,Oracle 占用端口 1521等)
    • 动态/私有端口:49152~65535
  • IP地址和端口号的组合得出一个网络套接字:Socket

2.2.3 InetAddress

  • Internet 上的主机有两种方式表示地址:
    • 域名(hostName):www.taobao.com
    • IP 地址(hostAddress):223.111.230.234
  • Java 中有关网络方面的功能都定义在 java.net 程序包中。Java 用 InetAddress 对象表示 IP 地址,该对象里有两个字段:主机名(String) 和 IP 地址(int)
  • InetAddress 类主要表示 IP 地址,两个子类:Inet4AddressInet6Address
  • InetAddress 类对象含有一个 Internet 主机地址的域名和 IP 地址
  • 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成 IP 地址,这样才能和主机建立连接
  • InetAddress 类没有提供公共的构造器,而是提供了如下几个静态方法来获取 InetAddress 实例
    • public static InetAddress getLocalHost()
    • public static InetAddress getByName(String host)
  • InetAddress 提供了如下几个常用的方法
    • public String getHostAddress():返回 IP 地址字符串(以文本表现形式)
    • public String getHostName():获取此 IP 地址的主机名
    • public boolean isReachable(int timeout):测试是否可以达到该地址

2.3 通信要素二:协议

  • 网络通信协议:计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准
  • 网络协议太复杂,计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢? → 分层
  • 通信协议分层的思想:在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。

TCP/IP 协议簇:TCP/IP 以其两个主要协议:传输控制协议(TCP) 和网络互联协议(IP) 而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。TCP/IP 协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP 层、传输层和应用层。

3. Socket

[维基百科] socket 是计算机网络中用于在节点内发送或接收数据的内部端点。具体来说,它是网络软件 (协议栈) 中这个端点的一种表示,包含通信协议、目标地址、状态等,是系统资源的一种形式。

网络上具有唯一标识的 IP 地址和端口号组合在一起才能构成唯一能识别的标识符套接字(Socket)。传输层通信的两端都要有 Socket,是两台机器间通信的端点。网络通信其实就是 Socket 间的通信。

Socket 允许程序把网络连接当成一个流,数据在两个 Socket 间通过 IO 传输。一般主动发起通信的应用程序为 [客户端],等待通信请求的为 [服务端]。

客户端-服务器是一种最常见的网络应用程序模型。服务器是一个为其客户端提供某种特定服务的硬件或软件。客户机是一个用户应用程序,用于访问某台服务器提供的服务。端口号是对一个服务的访问场所,它用于区分同一物理计算机上的多个服务。套接字用于连接客户端和服务器,客户端和服务器之间的每个通信会话使用一个不同的套接字。TC P协议用于实现面向连接的会话。

Socket 分类:

  • 流套接字(stream socket):使用 TCP 提供可依赖的字节流服务
  • 数据报套接字(datagram socket):使用 UDP 提供“尽力而为”的数据报服务

4. 基于 TCP 的 Socket 编程

  • Java 语言的基于套接字编程分为服务端编程和客户端编程,其通信模型如图所示
  • 客户端 Socket 的工作过程包含以下 4 个基本的步骤:
    • 创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象(创建的同时会自动向服务器方发起连接)。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常
      • Socket(String host,int port) throws UnknownHostException, IOException:向服务器(域名是host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常
      • Socket(InetAddress address, int port) throws IOException:根据 InetAddress 对象所表示的 IP 地址以及端口号 port 发起连接
    • 打开连接到 Socket 的输入/出流: 使用 getInputStream() 获得输入流,使用 getOutputStream() 获得输出流,进行数据传输
    • 按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程
    • 关闭 Socket:断开客户端到服务器的连接,释放线路
  • 服务器端 ServerSocket 的工作过程包含以下 4 个基本的步骤:
    • 调用 ServerSocket(int port):创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
    • 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
    • 调用该 Socket 类对象的 getOutputStream()getInputStream():获取输出流和输入流,开始网络数据的发送和接收。
    • 关闭 ServerSocket 和 Socket 对象:客户端访问结束,关闭通信套接字。
  • Tips
    • 客户端建立 socketAtClient 对象的过程就是向服务器发出套接字连接请求
    • ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的 ServerSocket 对象。
    • 所谓“接收”客户的套接字请求,就是 accept() 会返回一个 Socket 对象,这个方法是一个阻塞方法,如果没有客户连接,它将一直等待。

代码演示1:客户端向服务器端发句话

public class TCPDemo {
    @Test
    public void client() {
        Socket socket = null;
        OutputStream os = null;
        InetAddress inetAddress = null;
        try {
            // 1. 创建 Socket 对象,指明服务器的ip和端口号
            inetAddress = InetAddress.getByName("127.0.0.1");
            socket = new Socket(inetAddress, 6677);
            // 2. 获取一个输出流,用于输出数据
            os = socket.getOutputStream();
            // 3. 写入数据
            os.write("你好鸭,我是客户端".getBytes());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭资源
            if(socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Test
    public void sever() {
        ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;

        try {
            // 1. 创建服务器端的 serverSocket
            ss = new ServerSocket(6677);
            // 2. 等待接收来自客户端的socket
            socket = ss.accept();
            // 3. 获取输入流
            is = socket.getInputStream();
            // 传统读方式可能会导致乱码
            baos = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int len;
            // 4. 读取输入流中的数据
            while((len = is.read(buf)) != -1)
                baos.write(buf, 0, len);
            System.out.println("收到了来自" + socket.getInetAddress().getHostAddress() + "的数据");
            System.out.println(baos.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //  5. 关闭资源
            if(baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ss != null) {
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

代码演示2:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端并关闭相应的连接

public class TCPDemo1 {
    @Test
    public void client() {
        Socket socket = null;
        OutputStream os = null;
        InputStream is = null;
        InetAddress inetAddress = null;
        ByteArrayOutputStream baos = null;
        try {
            // 1. 创建 Socket 对象,指明服务器的ip和端口号
            inetAddress = InetAddress.getByName("127.0.0.1");
            socket = new Socket(inetAddress, 6677);
            // 2. 获取一个输出流,用于输出数据
            os = socket.getOutputStream();
            // 3. 写入数据
            os.write("你好鸭,我是客户端".getBytes());
            // 4. 关闭数据输出
            socket.shutdownOutput();
            // 5. 接收来自服务端的答复
            is = socket.getInputStream();
            baos = new ByteArrayOutputStream();
            int len;
            byte[] buf = new byte[1024];
            while((len = is.read(buf)) != -1)
                baos.write(buf, 0, len);
            System.out.println(baos.toString());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭资源
            if(socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Test
    public void sever() {
        ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        BufferedOutputStream bos = null;
        OutputStream os = null;
        try {
            // 1. 创建服务器端的 serverSocket
            ss = new ServerSocket(6677);
            // 2. 等待接收来自客户端的socket
            socket = ss.accept();
            // 3. 获取输入流
            is = socket.getInputStream();
            bos = new BufferedOutputStream(new FileOutputStream("陶源.png"));
            byte[] buf = new byte[1024];
            int len;
            // 4. 读取输入流中的数据,然后写出到文件
            while((len = is.read(buf)) != -1)
                bos.write(buf, 0, len);
            System.out.println("收到了来自" + socket.getInetAddress().getHostAddress() + "的数据");
            // 5. 给客户端答复
            os = socket.getOutputStream();
            os.write("收到!".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //  6. 关闭资源
            if(bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(ss != null) {
                try {
                    ss.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • read() 是一个阻塞函数,如果客户端没有声明断开 outputStream 那么它就会认为客户端仍旧可能发送数据,像 read() 这种阻塞读取函数还有 BufferedReader 中的 readLine()、DataInputStream 中的 readUTF() 等。
  • Socket 任意一端在调用完 write() 时调用 shutdownOutput() 关闭输出流,这样对端的 inputStream 上的 read 操作就会返回 -1, 这里我们要注意下不能调用 socket.getInputStream().close()。因为它会导致 socket 直接被关闭。 当然如果不需要继续在 socket 上进行读操作,也可以直接关闭 socket。但是这个方法不能用于通信双方需要多次交互的情况。
  • 类 Socket 和 ServerSocket 实现了基于 TCP 协议的客户端-服务器程序。Socket 是客户端和服务器之间的一个连接,连接创建的细节被隐藏了。这个连接提供了一个安全的数据传输通道,这是因为 TCP 协议可以解决数据在传送过程中的丢失、损坏、重复、乱序以及网络拥挤等问题,它保证数据可靠的传送。

5. 基于 UDP 的 Socket 编程

  • DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
  • UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP 数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
  • DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号
  • UDP 协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。

  • 接收端程序编写:

    • 调用 DatagramSocket(int port) 创建一个数据报套接字,并绑定到指定端口上
    • 调用 DatagramPacket(byte[] buf, int length) 建立一个字节数组以接收 UDP 包
    • 调用 DatagramSocket 类的 receive(),接收 UDP 包
    • 关闭数据报套接字 close()
  • 发送端程序编写:

    • 调用 DatagramSocket() 创建一个数据报套接字
    • 调用 DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 建立要发送的 UDP 包
    • 调用 DatagramSocket 的 send(),发送 UDP 包
    • 关闭数据报套接字 close()
  • 接收端程序编写:

    • 调用 DatagramSocket(int port) 创建一个数据报套接字,并绑定到指定端口上
    • 调用 DatagramPacket(byte[] buf, int length) 建立一个字节数组以接收 UDP 包
    • 调用 DatagramSocket 类的 receive(),接收 UDP 包
    • 关闭数据报套接字 close()
  • 发送端程序编写:

    • 调用 DatagramSocket() 创建一个数据报套接字
    • 调用 DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 建立要发送的 UDP 包
    • 调用 DatagramSocket 的 send(),发送 UDP 包
    • 关闭数据报套接字 close()
  • Tips

    • 形象理解上述 4 步:① 定义码头 [DatagramSocket ds;] ② 定义用来接收/发送数据的集装箱 [DatagramPacket dp;] ③ 在码头上用集装箱接收对方发送过来的数据 [ds.recevie(dp)] / 在码头上把集装箱中的数据发送给对方 [ds.send(dp)] ④ 关闭码头 [ds.close()]
    • DatagramPacket 是个集装箱,既可以用来存放即将要发送到客户端的数据,也可以用来存放即将要接收的客户端数据
    • 如果 UDP 中两个网络程序要通信的话,这 2 个程序都必须得定义 DatagramPacket 包
    • 如果定义的 DatagramPacket 对象要用来接收客户端发送过来的数据,则不建议指定目的端口号和目的IP
    • 如果定义的 DatagramPacket 对象要用来存储发送到客户端的数据,则必须指定目的端口号和目的IP
    • DatagramPacket 所有的构造器的第一个形参都是一个 byte[],即:DatagramPacket 对象的内核都是个 byte[]
@Test
public void send() {
    DatagramSocket ds = null;
    try {
        // A:创建发送端的Socket对象
        // DatagramSocket 此类表示用来发送和接收数据报包的套接字
        ds = new DatagramSocket();
        
        /*
        B:创建数据并把数据打包
        void send(DatagramPacket p)
            从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要
            发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号
        DatagramPacket 此类表示数据报包
            DatagramPacket(byte[] buf, int length, InetAddress address, int port)
            构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
         */
        //创建数据
        byte[] bys = "Hello,World!".getBytes();
        //长度
        int length = bys.length;
        //IP地址对象
        InetAddress address = InetAddress.getByName("127.0.0.1");
        //端口
        int port = 10086;
        DatagramPacket dp = new DatagramPacket(bys, length, address, port);
        
        // C:通过调用Socket对象的发送方法,发送数据包
        ds.send(dp);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // D:释放资源
        ds.close();
    }
}

@Test
public void receive() {
    DatagramSocket ds = null;
    try {
        // A:创建接收端Socket对象
        // DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口
        ds = new DatagramSocket(10086);

        // B:创建一个数据包(接收容器)
        // DatagramPacket(byte[] buf, int length) 构造
        // DatagramPacket,用来接收长度为 length 的数据包
        byte[] bys = new byte[1024];
        int length = bys.length;
        DatagramPacket dp = new DatagramPacket(bys, length);

        // C:调用Socket对象的接收方法接收数据
        // void receive(DatagramPacket p) 从此套接字接收数据报包
        // <此方法在接收到数据报前一直阻塞>
        ds.receive(dp); // 阻塞式

        // D:解析数据包,并显示在控制台
        // 获取对方的 IP
        //public InetAddress getAddress() 返回某台机器的 IP 地址
        // ,此数据报将要发往该机器或者是从该机器接收到的
        InetAddress address = dp.getAddress();
        String ip = address.getHostAddress();

        // public byte[] getData() 返回数据缓冲区。
        // public int getLength() 返回将要发送或接收到的数据的长度
        byte[] bys2 = dp.getData();
        int len = dp.getLength();
        String s = new String(bys2,0,len);
        System.out.println(ip+"传递的数据是:"+s);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // E:释放资源
        ds.close();
    }
}
@Test
public void send() {
    DatagramSocket ds = null;
    try {
        // A:创建发送端的Socket对象
        // DatagramSocket 此类表示用来发送和接收数据报包的套接字
        ds = new DatagramSocket();
        
        /*
        B:创建数据并把数据打包
        void send(DatagramPacket p)
            从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要
            发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号
        DatagramPacket 此类表示数据报包
            DatagramPacket(byte[] buf, int length, InetAddress address, int port)
            构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
         */
        //创建数据
        byte[] bys = "Hello,World!".getBytes();
        //长度
        int length = bys.length;
        //IP地址对象
        InetAddress address = InetAddress.getByName("127.0.0.1");
        //端口
        int port = 10086;
        DatagramPacket dp = new DatagramPacket(bys, length, address, port);
        
        // C:通过调用Socket对象的发送方法,发送数据包
        ds.send(dp);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // D:释放资源
        ds.close();
    }
}

@Test
public void receive() {
    DatagramSocket ds = null;
    try {
        // A:创建接收端Socket对象
        // DatagramSocket(int port) 创建数据报套接字并将其绑定到本地主机上的指定端口
        ds = new DatagramSocket(10086);

        // B:创建一个数据包(接收容器)
        // DatagramPacket(byte[] buf, int length) 构造
        // DatagramPacket,用来接收长度为 length 的数据包
        byte[] bys = new byte[1024];
        int length = bys.length;
        DatagramPacket dp = new DatagramPacket(bys, length);

        // C:调用Socket对象的接收方法接收数据
        // void receive(DatagramPacket p) 从此套接字接收数据报包
        // <此方法在接收到数据报前一直阻塞>
        ds.receive(dp); // 阻塞式

        // D:解析数据包,并显示在控制台
        // 获取对方的 IP
        //public InetAddress getAddress() 返回某台机器的 IP 地址
        // ,此数据报将要发往该机器或者是从该机器接收到的
        InetAddress address = dp.getAddress();
        String ip = address.getHostAddress();

        // public byte[] getData() 返回数据缓冲区。
        // public int getLength() 返回将要发送或接收到的数据的长度
        byte[] bys2 = dp.getData();
        int len = dp.getLength();
        String s = new String(bys2,0,len);
        System.out.println(ip+"传递的数据是:"+s);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // E:释放资源
        ds.close();
    }
}

6. URL 编程

  • URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址
  • 它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源
  • 通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源
  • URL 的基本结构由 5 部分组成:<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
    • #片段名:即锚点,例如看小说,直接定位到章节
    • 参数列表格式:参数名=参数值&参数名=参数值....

6.1 URL

为了表示 URL,java.net 中实现了类 URL。我们可以通过下面的构造器来初始化一个 URL 对象,URL 类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用 try-catch 语句进行捕获。

  • public URL (String spec):通过一个表示 URL 地址的字符串可以构造一个URL对象
  • public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个 URL 对象
  • public URL(String protocol, String host, String file)
  • public URL(String protocol, String host, int port, String file)

一个 URL 对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:

  • public String getProtocol() 获取该 URL 的协议名
  • public String getHost() 获取该 URL 的主机名
  • public String getPort() 获取该 URL 的端口号
  • public String getPath() 获取该 URL 的文件路径
  • public String getFile() 获取该 URL 的文件名
  • public String getQuery() 获取该 URL 的查询名

6.2 URLConnection

针对 HTTP 协议的 URLConnection 类:

  • URL的 openStream():能从网络上读取数据
  • 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一些数据,则必须先与URL建立连接,然后才能对其进行读写,此时需要使用 URLConnection。
  • URLConnection:表示到 URL 所引用的远程对象的连接。当与一个 URL 建立连接时,首先要在一个 URL 对象上通过 openConnection() 生成对应的 URLConnection 对象。如果连接过程失败,将产生 IOException。
  • 通过 URLConnection 对象获取的输入流和输出流,即可以与现有的 CGI 程序进行交互。
    • public Object getContent() throws IOException
    • public int getContentLength()
    • public String getContentType()
    • public long getDate()
    • public long getLastModified()
    • public InputStream getInputStream() throws IOException
    • public OutputSteram getOutputStream() throws IOException

类 URL 和 URLConnection 提供了最高级网络应用。URL 的网络资源的位置来同一表示 Internet 上各种网络资源。通过URL对象可以创建当前应用程序和 URL 表示的网络资源之间的连接,这样当前程序就可以读取网络资源数据,或者把自己的数据传送到网络上去。

6.3 URI、URL 和 URN

6.4 下载服务器资源演示

public static void main(String[] args) {
    HttpURLConnection urlConnection = null;
    InputStream is = null;
    FileOutputStream fos = null;
    try {
        URL url = new URL("http://localhost:8080/examples/tree.jpg");
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.connect();
        is = urlConnection.getInputStream();
        fos = new FileOutputStream("net\tree.jpg");
        byte[] buffer = new byte[1024];
        int len;
        while((len = is.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
        System.out.println("下载完成");
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //关闭资源
        if(is != null){
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fos != null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(urlConnection != null){
            urlConnection.disconnect();
        }
    }
}
原文地址:https://www.cnblogs.com/liujiaqi1101/p/13340690.html