20220424 Java核心技术 卷2 高级特性 4

网络

连接到服务器

使用 telnet

telnet time-a.nist.gov 13
59603 22-01-24 02:03:04 00 0 0 526.8 UTC(NIST) *

这说明你已经连接到了大多数 UNIX 计算机都支持的“当日时间”服务。而你刚才所连接的那台服务器就是由国家标准与技术研究所运维的,这家研究所负责提供钝原子钟的计量时间

按照惯例,“当日时间”服务总是连接到端口 13

网络术语中,端口并不是指物理设备,而是为了便于实现服务器与客户端之间的通信所使用的抽象概念

如果一台 Web 服务器用相同的 IP 地址为多个域提供宿主环境,那么在连接这台 Web Server 时,就必须提供 Host 键/值对。如果服务器只为单个域提供宿主环境,则可以忽略该键/值对

用 Java 连接到服务器

try (Socket s = new Socket("time-a.nist.gov", 13);
     Scanner in = new Scanner(s.getInputStream(), "UTF-8")) {
    while (in.hasNextLine()) {
        String line = in.nextLine();
        System.out.println(line);
    }
}

套接字( Socket )是网络软件中的一个抽象概念,负责启动该程序内部和外部之间的通信。我们将远程地址和端口号传递给套接字的构造器,如果连接失败,它将抛出一个 UnknownHostException 异常;如果存在其他问题,它将抛出一个 IOException 异常。UnknownHostExceptionIOException 的一个子类

一旦套接字被打开, java.net.Socket 类中的 getInputStream 方法就会返回一个 InputStream 对象,该对象可以像其他任何流对象一样使用。而一旦获取了这个流,该程序将直接把每一行打印到标准输出。这个过程将一直持续到流发送完毕且服务器断开连接为止

该程序只适用于非常简单的服务器,比如“当日时间”之类的服务。在比较复杂的网络程序中,客户端发送请求数据给服务器,而服务器可能在响应结束时并不立刻断开连接

Socket 类非常简单易用,因为 Java 库隐藏了建立网络连接和通过连接发送数据的复杂过程。实际上, java.net 包提供的编程接口与操作文件时所使用的接口基本相同

本书所介绍的内容仅覆盖了 TCP (传输控制协议)网络协议。Java 平台另外还支持 UDP (用户数据报协议)协议,该协议可以用于发送数据包(也称为数据报),它所需付出的开销要比 TCP 少得多。UDP 有一个重要的缺点:数据包无需按照顺序传递到接收应用程序,它们甚至可能在传输过程中全部丢失。UDP 让数据包的接收者自己负责对它们进行排序,并请求发送者重新发送那些丢失的数据包。UDP 比较适合于那些可以忍受数据包丢失的应用,例如用于音频流和视频流的传输,或者用于连续测量的应用领域

java.net.Socket 方法名称 方法声明 描述
构造器 public Socket(String host, int port) throws UnknownHostException, IOException 构建一个套接字,用来连接给定的主机和端口
getInputStream
getOutputStream
public InputStream getInputStream() throws IOException
public OutputStream getOutputStream() throws IOException
获取可以从套接字中读取数据的流,以及可以向套接字写出数据的流

套接字超时

从套接字读取信息时,在有数据可供访问之前,读操作将会被阻塞。如果此时主机不可达,那么应用将要等待很长的时间,并且因为受底层操作系统的限制而最终会导致超时

对于不同的应用,应该确定合理的超时值。然后调用 setSoTimeout 方法设置这个超时值(单位:毫秒)。如果已经为套接字设置了超时值,并且之后的读操作和写操作在没有完成之前就超过了时间限制,那么这些操作就会抛出 SocketTimeoutException 异常。你可以捕获这个异常,并对超时做出反应

public Socket(String host, int port) throws UnknownHostException, IOException

构造器会一直无限期地阻塞下去,直到建立了到达主机的初始连接为止。可以通过先构建一个无连接的套接字,然后再使用一个超时来进行连接的方式解决这个问题

Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), timeout);
java.net.Socket 方法名称 方法声明 描述
构造器 public Socket() 创建一个还未被连接的套接字
connect public void connect(SocketAddress endpoint) throws IOException 将该套接字连接到给定的地址
connect public void connect(SocketAddress endpoint, int timeout) throws IOException 将套接字连接到给定的地址 如果在给定的时间内没有响应,则返回
setSoTimeout public synchronized void setSoTimeout(int timeout) throws SocketException 设置该套接字上读请求的阻塞时间 如果超出给定时间,则抛出 SocketTimeoutException 异常
isConnected public boolean isConnected() 如果该套接字已被连接,则返回 true
isClosed public boolean isClosed() 如果套接宇已经被关闭,则返回 true

因特网地址( InetAddress

通常,不用过多考虑因特网地址的问题,它们是用一串数字表示的主机地址,一个因特网地址由 4 个字节组成(在 IPv6 中是 16 个字节),比如 129.6.15.28 。但是,如果需要在主机名和因特网地址之间进行转换,那么就可以使用 InetAddress

只要主机操作系统支持 IPv6 格式的因特网地址, java.net 包也将支持它

静态的 getByName 方法可以返回代表某个主机的 InetAddress 对象,将返回一个 InetAddress 对象,该对象封装了一个 字节的序列

String host = "qq.com";
InetAddress address = InetAddress.getByName(host);
System.out.println(address);    // qq.com/123.151.137.18

byte[] bytes = address.getAddress();
for (int i = 0; i < bytes.length; i++) {
    System.out.println(bytes[i]);   // 123、-105、-119、18
}

一些访问量较大的主机名通常会对应于多个因特网地址,以实现负载均衡。可以通过调用 getAllByName 方法来获得所有主机。

最后需要说明的是,有时我们可能需要本地主机的地址。如果只是要求得到 localhost 的地址,那总会得到本地回环地址 127.0.0.1 ,但是其他程序无法用这个地址来连接到这台机器上。此时,可以使用静态的 getlocalHost 方法来得到本地主机的地址:

String host = "qq.com";

if (host.length() > 0) {
    InetAddress[] addresses = InetAddress.getAllByName(host);
   for (InetAddress a : addresses) {
      System.out.println(a);    // qq.com/123.151.137.18、qq.com/183.3.226.35、qq.com/61.129.7.47
   }
} else {
    InetAddress localHostAddress = InetAddress.getLocalHost();	// user名称/ipv4地址
    System.out.println(localHostAddress);
}
java.net.InetAddress 方法名称 方法声明 描述
getByName
getAllByName
public static InetAddress getByName(String host) throws UnknownHostException
public static InetAddress[] getAllByName(String host) throws UnknownHostException
为给定的主机名创建一个 InetAddress 对象,或者一个包含了该主机名所对应的所有因特网地址的数组
getLocalHost public static InetAddress getLocalHost() throws UnknownHostException 为本地主机创建 InetAddress 对象
getAddress public byte[] getAddress() 返回一个包含数字型地址的字节数组
getHostAddress public String getHostAddress() 返回一个由十进制数组成的字符串,各数字间用圆点符号隔开,例如,129.6.15.28
getHostName public String getHostName() 返回主机名

实现服务器

服务器套接字( ServerSocket

ServerSocket 类用于建立套接字,accept 用于告诉程序不停地等待,直到有客户端连接到这个端口。一旦有人通过网络发送了正

确的连接请求,并以此连接到了端口上,该方法就会返回一个表示连接已经建立的 Socket 对象。你可以使用这个对象来得到输入流和输出流。

服务器发送给服务器输出流的所有信息都会成为客户端程序的输入,同时来自客户端程序的所有输出都会被包含在服务器输入流中

每一个服务器程序,比如一个 HTTP Web 服务器,都会不间断地执行下面这个循环:

  1. 通过输入数据流从客户端接收一个命令
  2. 解码这个客户端命令
  3. 收集客户端所请求的信息
  4. 通过输出数据流发送信息给客户端
// establish server socket
try (ServerSocket s = new ServerSocket(8189)) {
    log.info("wait incoming");

    // wait for client connection
    try (Socket incoming = s.accept()) {
        log.info("accept incoming");

        InputStream inStream = incoming.getInputStream();
        OutputStream outStream = incoming.getOutputStream();

        try (Scanner in = new Scanner(inStream, "UTF-8")) {
            PrintWriter out = new PrintWriter(
                    new OutputStreamWriter(outStream, StandardCharsets.UTF_8),
                    true);

            out.println("Hello! Enter BYE to exit.");

            // echo client input
            boolean done = false;
            while (!done && in.hasNextLine()) {
                String line = in.nextLine();
                log.info("line :: {}", line);

                out.println("Echo: " + line);
                if (line.trim().equals("BYE")) {
                    done = true;
                }
            }
            log.info("end while");
        }
    }
}
java.net.ServerSocket 方法名称 方法声明 描述
构造器 public ServerSocket(int port) throws IOException 创建一个监听端口的服务器套接字
accept public Socket accept() throws IOException 等待连接。该方法阻塞(即,使之空闲)当前线程直到建立连接为止。该方法返回一个Socket 对象,程序可以通过这个对象与连接中的客户端进行通信
close public void close() throws IOException 关闭服务器套接字

为多个客户端服务

为了支持多客户端连接,每当程序建立一个新的套接字连接,也就是说当调用 accept 时,可以启动一个新的线程来处理服务器和该客户端之间的连接,而主线程将立即返回并等待下一个连接

@Slf4j
public class ThreadedEchoServer {
    public static void main(String[] args) {
        try (ServerSocket s = new ServerSocket(8189)) {
            log.info("wait incoming");
            
            int i = 1;

            while (true) {
                Socket incoming = s.accept();
                log.info("Spawning " + i);
                Runnable r = new ThreadedEchoHandler(incoming);
                Thread t = new Thread(r);
                t.start();
                i++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

@Slf4j
class ThreadedEchoHandler implements Runnable {
    private Socket incoming;

    public ThreadedEchoHandler(Socket incomingSocket) {
        incoming = incomingSocket;
    }

    public void run() {
        try (InputStream inStream = incoming.getInputStream();
             OutputStream outStream = incoming.getOutputStream()) {
            Scanner in = new Scanner(inStream, "UTF-8");
            PrintWriter out = new PrintWriter(
                    new OutputStreamWriter(outStream, "UTF-8"),
                    true /* autoFlush */);

            out.println("Hello! Enter BYE to exit.");

            // echo client input
            boolean done = false;
            while (!done && in.hasNextLine()) {
                String line = in.nextLine();
                log.info("line :: {}", line);

                out.println("Echo: " + line);
                if (line.trim().equals("BYE")) {
                    done = true;
                }
            }
            log.info("end while");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

半关闭

半关闭( half-close )提供了这样 种能力:套接字连接的一端可以终止其输出,同时仍旧可以接收来自另一端的数据

这是一种很典型的情况,例如我们在向服务器传输数据,但是一开始并不知道要传输多少数据。在向文件写数据时,我们只需在数据写入后关闭文件即可。但是,如果关闭一个套接字,那么与服务器的连接将立刻断开,因而也就无法读取服务器的响应了

使用半关闭的方法就可以解决上述问题,可以通过关闭一个套接字的输出流来表示发送给服务器的请求数据已经结束,但是必须保持输入流处于打开状态

try (Socket socket = new Socket(host, port)) {
    Scanner in = new Scanner(socket.getInputStream(), "UTF-8");
    PrintWriter writer = new PrintWriter(socket.getOutputStream());
    // send request data
    writer.print("...");
    writer.flush();
    socket.shutdownOutput();
    // now socket is half-closed
    // read response data
    while (in.hasNextLine()) {
        String line = in.nextLine();
        // do something
        System.out.println(line);
    }
}

服务器端将读取输入信息,直至到达输入流的结尾,然后它再发送响应

当然,该协议只适用于一站式( one-shot )的服务,例如 HTTP 服务,在这种服务中,客户端连接服务器,发送一个请求,捕获响应信息,然后断开连接

java.net.Socket 方法名称 方法声明 描述
shutdownOutput public void shutdownOutput() throws IOException 将输出流设为 “流结束”
shutdownInput public void shutdownInput() throws IOException 将输入流设为 “流结束”
isOutputShutdown public boolean isOutputShutdown() 如果输出已被关闭,则返回 true
isInputShutdown public boolean isInputShutdown() 如果输入已被关闭,则返回 true

可中断套接字

当连接到一个套接宇时,当前线程将会被阻塞直到建立连接或产生超时为止。同样地,当通过套接字读写数据时,当前线程也会被阻塞直到操作成功或产生超时为止

当线程因套接宇无法响应而发生阻塞时,无法通过调用 interrupt 来解除阻塞

为了中断套接字操作,可以使用 java.nio 包提供的一个特性 —— SocketChannel 类。通道( channel )并没有与之相关联的流 实际上,它所拥有的 readwrite 方法都是通过使用 Buffer 对象来实现的。ReadableByteChannel 接口和 WritableByteChannel 接口都声明了这两个方法。

打开 SocketChannel

SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port));

如果不想处理缓冲区,可以使用 Scanner 类从 SocketChannel 中读取信息,因为 Scanner 有一个带 ReadableByteChannel 参数的构造器:

Scanner in = new Scanner(channel, "UTF-8");

通过调用静态方法 Channels.newOutputStream ,可以将通道转换成输出流

OutputStream outStream = Channels.newOutputStream(channel);

当线程正在执行打开、读取或写入操作时,如果线程发生中断,那么这些操作将不会陷入阻塞,而是以抛出异常的方式结束。

java.net.InetSocketAddress 方法名称 方法声明 描述
构造器 public InetSocketAddress(String hostname, int port) 用给定的主机和端口参数创建一个地址对象,并在创建过程中解析主机名。如果主机名不能被解析,那么该地址对象的 unresolved 属性将被设为 true
isUnresolved public final boolean isUnresolved() 如果不能解析该地址对象,则返回 true
java.nio.channels.SocketChannel 方法名称 方法声明 描述
open public static SocketChannel open(SocketAddress remote) throws IOException 打开一个套接字通道,并将其连接到远程地址
java.nio.channels.Channels 方法名称 方法声明 描述
newInputStream public static InputStream newInputStream(ReadableByteChannel ch) 创建一个输入流,用以从指定的通道读取数据
newOutputStream public static OutputStream newOutputStream(final WritableByteChannel ch) 创建一个输出流,用以向指定的通道写人数据

获取 Web 数据

为了在 Java 程序中访问 Web 服务器,你可能希望在更高的级别上进行处理,而不只是创建套接字连接和发送 HTTP 请求

URL 和 URI

URLURLConnection 类封装了大量复杂的实现细节,这些细节涉及如何从远程站点获取信息

可以自一个字符串构建一个 URL 对象:

URL url = new URL(urlString);

如果只是想获得该资源的内容,可以使用 URL 类中的 openStream 方法。该方法将产生一个 InputStream 对象,然后就可以按照一般的用法来使用这个对象了,比如用它构建一个 Scanner 对象:

InputStream inputStream = url.openStream();
Scanner scanner = new Scanner(inputStream, "UTF-8");

java.net 包对 统一资源定位符( Uniform Resource Locator , URL )统一资源标识符( Uniform Resource Identifier , URI )作了非常有用的区分。URI 是个纯粹的语法结构,包含用来指定 Web 资源的字符串的各种组成部分。URL 是 URI 的一个特例,它包含了用于定位 Web 资源的足够信息。其他 URI,例如 mailto:cay@horstmann.com 不属于 URL ,因为根据该标识符我们无法定位任何数据。这样的 URI 我们称之为 统一资源名称( Uniform Resource Name ,URN )

在 Java 类库中, URI 类并不包含任何用于访问资源的方法,它的唯一作用就是解析。 URL 类可以打开一个到达资源的流。因此, URL 类只能作用于那些 Java 类库知道该如何处理的模式,例如 http:https:ftp: 、本地文件系统( file: )和 JAR 文件( jar:

URI 规范给出了标记这些标识符的规则。一个 URI 具有以下句法:

[scheme:]schemeSpecficPart[#fragment]

[...] 表示可选部分,并且 :# 可以被包含在标识符内。包含 scheme: 部分的 URI 称为 绝对 URI 。否则,称为 相对 URI

如果绝对 URI 的 schemeSpecficPart 不是以 / 开头的,我们就称它是 不透明 的。例如 mailto:cay@horstmann.com

所有绝对的透明 URI 和所有相对 URI 都是 分层的( hierarchical )。例如:

http://horstmann.com/index.html 
../../java/net/Socket.html#Socket()

一个分层 URI schemeSpecificPart 具有以下结构:

[//authority][path][?query]

[...] 表示可选部分。对于那些基于服务器的 URI , authority 部分具有以下形式:

[user-info@]host[:port]

URI 类的作用之一是解析标识符并将它分解成各种不同的组成部分。可以通过 getXxx 方法获取相关信息

URI 类的另一个作用是处理绝对标识符和相对标识符。如果存在一个绝对 URIhttp://docs.mycompany.com/api/java/net/ServerSocket.html 和一个相对 URI../../java/net/Socket.html#Socket() ,那么可以用它们组合出一个绝对 URIhttp://docs.mycompany.com/api/java/net/Socket.html#Socket() 。这个过程称为 解析相对 URL

与此相反的过程称为 相对化( relativization )。例如,假设有一个基本 URI ,即:http://docs.mycompany.com/api 和另一个 URIhttp://docs.mycompany.com/api/java/lang/String.html ,那么相对化之后的 URI 就是:java/lang/String.html

URI 类同时支持以下两个操作:

String baseUri = "http://docs.mycompany.com/api/java/net/ServerSocket.html";
String combinedUri = "../../java/net/Socket.html#Socket()";

URI base = new URI(baseUri);
URI combined = new URI(combinedUri);

URI resolve = base.resolve(combined);
System.out.println(resolve);    // http://docs.mycompany.com/api/java/net/Socket.html#Socket()

String baseUri2 = "http://docs.mycompany.com/api";
URI base2 = new URI(baseUri2);
URI relativize = base2.relativize(resolve);
System.out.println(relativize);     // java/net/Socket.html#Socket()

使用 URLConnection 获取信息

如果想从某个 Web 资源获取更多信息,那么应该使用 URLConnection 类,通过它能够得到比基本的 URL 类更多的控制功能

当操作 URLConnection 对象时,必须像下面这样非常小心地安排操作步骤:

  1. 调用 URL 类中的 openConnection 方法获得 URLConnection 对象

    URLConnection connection = url.openConnection();
    
  2. 使用以下 setXxx 方法来设置任意的请求属性

    setDoInput 
    setDoOutput 
    setIfModifiedSince 
    setUseCaches 
    setAllowUseInteraction
    setRequestProperty
    setConnectTimeout 
    setReadTimeout
    
  3. 调用 connect 方法连接远程资源

    connection.connect();
    
  4. 与服务器建立连接后,你可以查询头信息 getHeaderFieldKeygetHeaderField 两个方法枚举了消息头的所有字段。 getHeaderFields 方法返回一个包含了消息头中所有字段的标准阴 Map 对象。为了方便使用,以下方法可以查询各标准字段:

    getContentType 
    getContentLength
    getContentEncoding 
    getDate
    getExpiration 
    getLastModified
    
  5. 最后,访问资源数据。使用 getInputStream 方法获取一个输入流用以读取信息( 这个输入流与 URL 类中的 openStream 方法所返回的流相同 )。另一个方法 getContent 实际操作中并不是很有用。由标准内容类型( 比如 text/plain 和 image/gif ) 所返回的对象需要使用 com.sun 层次结构中的类来进行处理。也可以注册自己的内容处理器

警告:一些程序员在使用 URLConnection 类的过程中形成了错误的观念,他们认为 URLConnection 中的 getInputStreamgetOutputStream 方法与 Socket 类中的这些方法相似,但是这种想法并不十分正确。URLConnection 类具有很多表象之下的神奇功能,尤其在处理请求和响应消息头时。正因 为如此,严格遵循建立连接的每个步骤显得非常重要

URLConnection 类有几个方法可以在与服务器建立连接之前设置连接属性,其中最重要的是 setDoInputsetDoOutput 在默认情况下,建立的连接只产生从服务器读取信息的输入流,并不产生任何执行写操作的输出流。如果想获得输出流(例如,用于向一个 Web 服务器提交数据),那么你需要调用

connection.setDoOutput(true)

接下来,也许想设置某些请求头( request header )。请求头是与请求命令一起被发送到服务器的。例如:

GET www.server.com/index.html HTTP/1.0 
Referer: http://www.somewhe.com/links.html 
Proxy-Connection: Keep-Alive 
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.4)
Host: www.server.com 
Accept: text/html, image/gif, image/jpeg, image/png, */* 
Accept-Language: en 
Accept-Charset: iso-8859-1, *, utf-8 
Cookie: orangemilano=192218887821987

setIfModifiedSince 方法用于告诉连接你只对自某个特定日期以来被修改过的数据感兴趣; setUseCachessetAllowUserInteraction 这两个方法只作用于 Applet ; setUseCaches 方法用于命令浏览器首先检查它的缓存; setAllowUserInteraction 方法则用于在访问有密码保护的资源时弹出对话框,以便查询用户名和口令

再介绍一个总览全局的方法 setRequestProperty ,它可以用来设置对特定协议起作用的任何 “名-值( name/value )对” 。关于 HTTP 请求头的格式,请参见 RFC 2616 ,其中的某些参数没有很好地建档,它们通常在程序员之间口头传授。RequestProperty 只能在 调用 connect 方法前 getset

一旦调用了 connect 方法,就可以查询响应头信息了。可以使用 getHeaderFieldsgetHeaderField 方法。

下面是一组 HTTP 请求的响应头字段:

Date: Wed, 27 Aug 2008 00:15:48 GMT 
Server: Apache/2.2.2 (Unix) 
Last-Modified: Sun, 22 Jun 2008 20:53:38 GMT 
Accept-Ranges: bytes 
Content-Length: 4813 
Connection: close 
Content-Type: text/html

为了简便起见, Java 提供了 6 个方法用以访问最常用的消息头类型的值,并在需要的时候将它们转换成数字类型。返回类型为 long 的方法返回的是从格林尼治时间 1970 年 1 月 1 日开始计算的秒数

键名 方法名 返回类型
Date getDate long
Expires getExpiration long
Last-Modified geLastModified long
Content-Length getContentLength int
Content-Type getContentType String
Content-Encoding getContentEncoding String
try {
    String urlName;
    if (args.length > 0) {
        urlName = args[0];
    } else {
        urlName = "http://horstmann.com";
    }

    URL url = new URL(urlName);
    URLConnection connection = url.openConnection();

    // set username, password if specified on command line

    if (args.length > 2) {
        String username = args[1];
        String password = args[2];
        String input = username + ":" + password;
        Base64.Encoder encoder = Base64.getEncoder();
        String encoding = encoder.encodeToString(input.getBytes(StandardCharsets.UTF_8));
        connection.setRequestProperty("Authorization", "Basic " + encoding);
    }

    System.out.println("----------getRequestProperties");
    connection.setRequestProperty("user", "hwj");

    Map<String, List<String>> requestProperties = connection.getRequestProperties();
    for (Map.Entry<String, List<String>> entry : requestProperties.entrySet()) {
        String key = entry.getKey();
        for (String value : entry.getValue()) {
            System.out.println(key + ": " + value);
        }
    }



    connection.connect();

    // print header fields
    System.out.println("----------getHeaderFields");
    Map<String, List<String>> headers = connection.getHeaderFields();
    for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
        String key = entry.getKey();
        for (String value : entry.getValue()) {
            System.out.println(key + ": " + value);
        }
    }



    // print convenience functions

    System.out.println("----------");
    System.out.println("getContentType: " + connection.getContentType());
    System.out.println("getContentLength: " + connection.getContentLength());
    System.out.println("getContentEncoding: " + connection.getContentEncoding());
    System.out.println("getDate: " + connection.getDate());
    System.out.println("getExpiration: " + connection.getExpiration());
    System.out.println("getLastModifed: " + connection.getLastModified());
    System.out.println("----------");

    String encoding = connection.getContentEncoding();
    if (encoding == null) {
        encoding = "UTF-8";
    }
    try (Scanner in = new Scanner(connection.getInputStream(), encoding)) {
        // print first ten lines of contents

        for (int n = 1; in.hasNextLine() && n <= 10; n++) {
            System.out.println(in.nextLine());
        }
        if (in.hasNextLine()) {
            System.out.println(". . .");
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}
java.net.URL 方法名称 方法声明 描述
openStream public final InputStream openStream() throws java.io.IOException 打开一个用于读取资源数据的输入流
openConnection public URLConnection openConnection() throws java.io.IOException 返回一个 URLConnection 对象,该对象负责管理与资源之间的连接
java.net.URLConnection 方法名称 方法声明 描述
setDoInput
getDoInput
public void setDoInput(boolean doinput)
public boolean getDoInput()
如果 doInputtrue ,那么用户可以接收来自该 URLConnection 的输入
setDoOutput
getDoOutput
public void setDoOutput(boolean dooutput)
public boolean getDoOutput()
如果 doOutputtrue ,那么用户可以将输出发送到该 URLConnection
setIfModifiedSince
getIfModifiedSince
public void setIfModifiedSince(long ifmodifiedsince)
public long getIfModifiedSince()
属性 ifModifiedSince 用于配置该 URLConnection 对象,使它只获取那些自从某个给定时间以来被修改过的数据。调用方法时需要传人的 time 参数指的是从格林尼治时间 1970 年 1 月 1 日午夜开始计算的秒数
setUseCaches
getUseCaches
public void setUseCaches(boolean usecaches)
public boolean getUseCaches()
如果 useCachestrue ,那么数据可以从本地缓存中得到。请注意, URLConnection 本身并不维护这样一个缓存,缓存必须由浏览器之类的外部程序提供
setAllowUserInteraction
getAllowUserInteraction
public void setAllowUserInteraction(boolean allowuserinteraction)
public boolean getAllowUserInteraction()
如果 allowUserInteractiontrue ,那么可以查询用户的口令。请注意, URLConnection 本身并不提供这种查询功能。查询必须由浏览器或浏览器插件之类的外部程序实现
setConnectTimeout
getConnectTimeout
public void setConnectTimeout(int timeout)
public int getConnectTimeout()
设置或得到连接超时时限(单位:毫秒) 。如果在连接建立之前就已经达到了超时的时限,那么相关联的输入流的 connect 方法就会抛出一个 SocketTimeoutException 异常
setReadTimeout
getReadTimeout
public void setReadTimeout(int timeout)
public int getReadTimeout()
设置读取数据的超时时限(单位:毫秒) 。如果在一个读操作成功之前就已经达到了超时的时限,那么 read 方法就会抛出一个 SocketTimeoutException 异常
setRequestproperty public void setRequestProperty(String key, String value) 设置请求头的一个字段
getRequestproperties public Map<String,List<String>> getRequestProperties() 返回请求头属性的一个映射表。相同的键对应的所有值被放置在同一个列表中
connect abstract public void connect() throws IOException; 连接远程资源并获取响应头信息
getHeaderFields public Map<String,List<String>> getHeaderFields() 返回响应的一个映射表。相同的键对应的所有值被放置在同一个列表中
getHeaderFieldKey public String getHeaderFieldKey(int n) 得到响应头第 n 个字段的键。如果 n 小于等于 0 或大于响应头字段的总数, 该方法返回 null
getHeaderField public String getHeaderField(int n) 得到响应头第 n 个字段的值。如果 n 小于等于 0 或大于响应头字段的总数, 该方法返回 null
getContentLength public int getContentLength() 如果内容长度可获得, 返回该长度值,否则返回 -1
getContentType public String getContentType() 获取内容的类型,比如 text/plainimage/gif
getContentEncoding public String getContentEncoding() 获取内容的编码机制, 比如 gzip 。这个值不太常用,因为默认的 identity 编码机制并不是用 Content-Encoding 头来设定的
getDate
getExpiration
getLastModified
public long getDate()
public long getExpiration()
public long getLastModified()
获取创建日期、过期日以及最后一次被修改的日期。这些日期指的是从格林尼治时间1970 年 1 月 1 日午夜开始计算的秒数
getInputStream
getOutputStream
public InputStream getInputStream() throws IOException
public OutputStream getOutputStream() throws IOException
返回从资源读取信息或向资源写入信息的流
getContent public Object getContent() throws IOException 选择适当的内容处理器,以便读取资源数据并将它转换成对象。该方法对于读取诸如text/plain 或 image/gif 之类的标准内容类型并没有什么用处,除非你安装了自己的内容处理器

提交表单数据

在上一节中,我们介绍了如何从 Web 服务器读取数据。现在,我们将介绍如何让程序再将数据反馈回 Web 服务器和那些被 Web 服务器调用的程序

为了将信息从 Web 浏览器发送到 Web 服务器,需要用到表单。当用户点击提交按钮时,文本框中的文本以及复选框和单选按钮的设定值都被发送到了 Web 服务器 此时, Web 务器调用程序对用户的输入进行处理

有许多技术可以让 Web 服务器实现对程序的调用 其中最广人所知的是 Java Servlet 、JavaServer Face 、微软的 ASP ( Active Server Pages ,动态服务器主页)以及 CGI (Common Gateway Interface ,通用网关接口)脚本

服务器端程序用于处理表单数据并生成另一个 HTML 页,该页会被 We 服务器发回给浏览器

img

当表单数据被发送到 Web 服务器时,数据到底由谁来解释并不重要,可能是 Servlet、CGI 脚本,也可能是其他服务器端技术。客户端以标准格式将数据发送给 Web 服务器,而 Web 服务器则负责将数据传递给具体的程序以产生响应

在向 Web 服务器发送信息时,通常有两个命令会被用到: GET 和 POST

在使用 GET 命令时,只需将参数附在 URL 的结尾处即可

http://host/path?query

其中,每个参数都具有 “名字=值” 的形式,而这些参数之间用 & 字符分隔开。参数的值将遵循下面的规则,使用 URL 编码模式进行编码:

  • 保留字符 A 到 Z 、a 到 z 、0 到 9 ,以及 . - ~ _
  • + 字符替换所有的空格
  • 将其他所有字符编码为 UTF-8 ,并将每个字节都编码为 % 后面紧跟一个两位的十六进制数字

例如,若要发送街道名 San Francisco, CA ,可以使用 San+Francisco%2c+CA ,因为十六进制数 2c (即十进制数 44 )是 , 的 UTF-8 码值

在浏览器中出现很长的查询字符串很让人郁闷,而且老式的浏览器和代理对在 GET 请求中能够包含的字符数量做出了限制。正因为此, POST 请求经常用来处理具有大量数据的表单。在 POST 请求中,我们不会在 URL 上附着参数,而是从 URLConnection 中获得输出流,并将键值对写入到该输出流中。我们仍旧需要对这些值进行 URL 编码,并用 & 字符将它们隔开

下面,我们将详细介绍这个过程 在提交数据给服务器端程序之前,首先需要创建一个 URLConnection 对象

URL url = new URL("http://host/path");
URLConnection connection = url.openConnection();

然后,调用 setDoOutput 方法建立一个用于输出的连接

connection.setDoOutput(true);

接着,调用 getOutputStream 方法获得一个流,可以通过这个流向服务器发送数据。如果要向服务器发送文本信息,那么可以非常方便地将流包装在 PrintWriter 对象中

PrintWriter out = new PrintWriter(connection.getOutputStream());

现在,可以向服务器发送数据了

out.print(name1 + "=" + URLEncoder.encode(value1, "UTF-8") + "&");
out.print(name2 + "=" + URLEncoder.encode(value2, "UTF-8"));

之后,关闭输出流

out.close();

最后,调用 getInputStream 方法读取服务器的响应

java.net.HttpURLConnection 方法名称 方法声明 描述
getErrorStream public InputStream getErrorStream() 返回一个流,通过这个流可以读取 Web 服务器的错误信息
java.net.URLEncoder 方法名称 方法声明 描述
encode public static String encode(String s, String enc) throws UnsupportedEncodingException 采用指定的字符编码模式(推荐使用 UTF-8 )对字符串 s 进行编码,并返回它的 URL 编码形式。在 URL 编码中,A-Z,a-z ,0-9 ,-_.* 等字符保持不变,空格被编码成 + ,所有其他字符被编码成 %XY 形式的字节序列,其中 0xXY 为该字节十六进制数
java.net.URLDecoder 方法名称 方法声明 描述
decode public static String decode(String s, String enc) throws UnsupportedEncodingException 采用指定编码模式对已编码字符串 s 进行解码,并返回结果

可以使用 Java 库的类来与网页交互,但是用起来并非特别方便。可以考虑使用其他的库,例如 Apache HttpClient

发送 E-mail

posted @ 2022-04-24 21:18  流星<。)#)))≦  阅读(25)  评论(0编辑  收藏  举报