读后笔记 -- Java核心技术(第11版 卷 II) Chapter4 网络
4.1 连接到服务器
4.1.2 Java 连接到服务器
var s = new Socket("time-a.nist.gov", 13); InputStream inStream = s.getInputStream();
4.1.2 socket timeout
// 方式一: var s = new Socket(...); s.setSoTimeout(10000); // timeout 10s try { InputStream in = s.getInputStream(); ... } catch (SocketTimeoutException e) { e.printStackTrace(); } // 方式二: var s = new Socket(); s.connect(new InetSocketAddress(host, port), timeout);
4.1.4 因特网地址
// 获取主机的 InetAddress 对象 InetAddress address = InetAddress.getByName("time-a.nist.gov"); // 获取地址 byte[] addressBytes = address.getAddress(); // 如果一个主机对应多个IP,则可使用 InetAddress[] addresses = InetAddress.getAllByName(host); // 获取本地主机的 InetAddress 对象 InetAddress address = InetAddress.getLocalHost();
4.2 实现服务器
4.2.1 服务器套接字
// 创建服务器 socket var s = new ServerSocket(8189); // 监听 Socket incoming = s.accept(); // 获取输入流、输出流 InputStream inStream = incoming.getInputStream(); OutputStream outStream = incoming.getOutputStream(); // 关闭连接进来的套接字 incoming.close();
4.2.2 为多个客户端服务
// 服务器上循环处理请求 while (true) { Socket incoming = s.accept(); Runnable r = new ThreadedEchoHandler(incoming); var t = new Thread(r); t.start(); } // 实现 ThreadedEchoHandler 方法 class ThreadedEchoHandler implements Runnable { private Socket incoming; public ThreadedEchoHandler(Socket incoming) { this.incoming = incoming; } @Override public void run() { try (InputStream inStream = incoming.getInputStream(); OutputStream outStream = incoming.getOutputStream()) {
var in = new Scanner(inStream, StandardCharsets.UTF_8); var out = new PrintWriter(new OutputStreamWriter(outStream, StandardCharsets.UTF_8), true); out.println("Hello! Enter BYE to exit."); // echo client input var done = false; while (!done && in.hasNextLine()) { String line = in.nextLine(); out.println("Echo: " + line); if (line.trim().equalsIgnoreCase("BYE")) done = true; } } catch (IOException e) { e.printStackTrace(); } } }
4.2.3 半关闭
半关闭:套接字连接的一端可以终止其输出,同时仍旧可以接收来自另一端的数据。
该协议只适用于一站式(one-shot)的服务,例如 HTTP 服务。
try (var socket = new Socket(host, port)) { var in = new Scanner(socket.getInputStream(), StandardCharsets.UTF_8); var 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() != null) { String line= in.nextLine(); ... } }
4.2.4 可中断套接字
// 中断套接字操作,需要打开 SocketChannel SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port)); // 从 channel 读取信息 var in = new Scanner(channel, StandardCharsets.UTF_8); // 将通道信息转成输出流 OutputStream outStream = Channels.newOutputStream(channel);
4.3 获取 Web 数据
4.3.1 URL 和 URI
// 构建一个 URL 对象 var url = new URL(urlString); // 获取资源内容 InputStream inStream = url.openStream(); // 使用对象 var in = new Scanner(inStream, StandardCharsets.UTF_8);
URI:统一资源标识符。纯粹的语法结构,包含用了指定 Web 资源的字符串的各种组成部分。
- URL:统一资源定位符。是 URI 的一个特例,包含了用于定位 Web 资源的足够信息。
- URN:统一资源名称。不能定位
Java 中 URI 类的作用:
- 解析标识符并分解成各种不同的组成部分;
- 处理绝对标识符和相对标识符;
绝对URI:http://docs.mycompany.com/api/java/net/ServerSocket.html
相对URI:../../java/net/Socket.html#Socket()
URI baseURI = new URI("http://docs.mycompany.com/api/"); URI relativeURI = new URI("java/lang/String.html"); URI combinedURI = baseURI.resolve(relativeURI); // 解析相对 URL,即拼接成完整 URL System.out.println("combinedURI: " + combinedURI); URI relative = baseURI.relativize(combinedURI); // URL 的相对化 System.out.println("relative: " + relative);
4.3.2 使用 URLConnection 获取信息
// step1: 获取 URLConnection 对象 URLConnection connection = url.openConnection(); // step2:调用方法设置属性 setDoInput ... setReadTimeout // step3:调用 connect() 连接远程资源: connection.connect(); // step4: 可查询头信息及其他标准字段: getHeaderFileKey ... getLastModified // step5: 访问资源数据 var in = new Scanner(connection.getInputStream(), encoding);
获取响应头字段信息
// print head fields 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); } }
访问有密码保护的页面时,需要如下操作:
String input = username + ":" + password; Base64.Encoder encoder = Base64.getEncoder(); String encoding = encoder.encodeToString(input.getBytes(StandardCharsets.UTF_8)); connection.setRequestProperty("Authorization", "Basic " + encoding);
4.3.3 提交表单数据
许多技术可以让 web 服务器实现对程序的调用:
- Java Servlet
- JavaServer Face
- ASP
- CGI
// step1: 创建一个 URLConnection 对象 var url = new URL("http://host/path"); URLConnection connection = url.openConnection(); // step2: 调用 setDoOutput() 建立一个用于输出的连接 connection.setDoOutput(true); // step3: 调用 getOutputStream() 获得一个流,通过该流向服务器发送数据 var out = new PrinterWriter(connection.getOutputStream(), StandardCharsets.UTF_8); // step4: 向服务器发送数据 out.print(name1 + "=" + URLEncoder.encode(value1, StandardCharsets.UTF_8) + "&"); out.print(name2 + "=" + URLEncoder.encode(value2, StandardCharsets.UTF_8)); // step5: 关闭输出流 out.close(); // step6: 调用 getInputStream() 读取服务器的响应 try (Scanner in = new Scanner(connection.getInputStream(), encoding))
读取 properties 文件:
// post.properties url=https://tools.usps.com/go/ZipLookupAction.action User-Agent=HTTPie/0.9.2 redirects=10
var props = new Properties(); try (InputStream in = Files.newInputStream(Paths.get(propsFilename))) { // propsFilename 是个 String 类型 props.load(in); } String urlString = props.remove("url").toString(); // https://tools.usps.com/go/ZipLookupAction.action Object userAgent = props.remove("User-Agent"); // HTTPie/0.9.2 Object redirects = props.remove("redirects"); // 10
4.4 HTTP Client
URLConnection 类是早期设计,对 HTTP 的支持比较笨重;
HttpClient:提供更便捷的 API 和对 HTTP/2 的支持;
1. 获取客户端:
HttpClient client = HttpClient.newHttpClient();
或,配置客户端:
HttpClient client = HttpClient.newBuilder() // 1)获取一个构建器
.followRedirects(HttpClient.Redirect.ALWAYS) // 2)调用其方法定制需要待构建的项
.build(); // 3)build() 终结构建过程。
// 这是构建不可修改对象的常见模式
2. 通过构建器定制 Get 请求
HttpRequest request = HttpRequest.newBuilder() .uri(new URI("http://horstmann.com")) .GET() .build();
POST 请求
HttpRequest request = HttpRequest.newBuilder() .uri(new URI(url)) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(jsonString)) .build();
3. 在发送请求时,必须告诉客户端如何处理响应。
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String bodyString = response.body();
可以异步地处理响应。在构建客户端时,可提供一个执行器:
ExecutorService executor = Executors.newCachedThreadPool();
HttpClient client = HttpClient.newBuilder().executor(executor).build();
构建一个请求,如何在该客户端上调用 sendAsync(),就会收到一个 CompletableFuture<HttpResponse<T>> 对象,其中 T 是具体处理器的类型。
HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
thenAccept(repsonse -> ...);
4.5 发送 E-mail
通用代码参照:https://www.cnblogs.com/bruce-he/p/17401838.html