读后笔记 -- 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

 

posted on 2023-05-11 11:23  bruce_he  阅读(12)  评论(0编辑  收藏  举报