📜  Java的传统套接字 API

📅  最后修改于: 2022-05-13 01:55:10.403000             🧑  作者: Mango

Java的传统套接字 API

Java Socket API 已经存在了二十多年。在此期间,它一直在维护和更新,但即使是保存最完好的代码最终也必须升级以跟上当代技术的步伐。在Java 13 中处理 Socket 交互的基本类已经重新实现,以利用Java的当前状态,同时也为未来的发展做准备。

什么是 Socket API,它是如何工作的?

Java套接字 API 中的对象(Java.net.ServerSocket 和Java.net.Socket)使您可以直接控制服务器侦听的套接字和传递数据的套接字。

ServerSocket可用于等待端口上的连接请求,如果接受,则返回可用于读取和写入数据的 Socket 对象。这两个类都采用基于 SOCKS 的实现,而繁重的工作由 SocketImpl 的内部实现完成。这段代码很容易学习和使用,但从Java 1.0 开始就已经存在了。较旧的Java和 C 代码的这种组合从一开始就需要增加默认线程堆栈大小,并且随着时间的推移出现稳定性和并发性困难。我们已经到了唯一可行的选择是彻底重建的阶段。

实现旧式套接字 API

该 JEP 提议用新的 NioSocketImpl 实现替换 SocketImpl 实现。该解决方案使用与 NIO(新 I/O)实现相同的架构,并与当前的缓冲区缓存方法连接,消除了对线程堆栈的需要。这些修改中还包含一些其他有益的修改,例如Java.lang.ref。如果 SocketImpl 实现在尚未关闭的套接字上被垃圾收集,并且在轮询时在非阻塞模式下使用 Socket 发生超时操作,则将使用更清洁的方法来关闭套接字。

已创建系统属性以利用原始实现,以减少在重新实现已使用 20 多年的技术时出现困难的机会。



如果设置了usePlainSocketImpl=true,则将使用之前的实现。

需要注意的是, SocketImpl是一种历史悠久的 SPI 技术,在过去没有得到充分说明;当前版本力求在可能的情况下模拟未定义的行为。然而,在使用新实现时,有一些边缘情况可能会失败,可以在这里看到。除了两个之外的所有这些都可以通过使用上面提到的系统属性来修复。 FileInputStreamFileOutputStream的先前实现返回输入和输出流,用于扩展它们。当前的实现并非如此。

使用自定义或平台 SocketImpl 的 ServerSockets 不能接受返回带有其他(自定义或平台)类型 SocketImpl 的 Sockets 的连接。

图 1. 传统套接字概述

那么,这有什么好处呢?

之前的实现难以维护和增强;但是,由于已经进行了这些改进, Java Socket API 将更容易维护。由于更好的维护,套接字代码的可靠性应该提高。 NIO 实现也是在较低级别完成的,这允许 Socket 和 ServerSocket 类保持不变。

这些修改不仅使维护此代码更容易,而且还使实现面向未来。一个名为 Project Loom 的项目目前正在 JDK 中运行。这个项目介绍了纤维的概念,这是一种看待线程的新颖方式(在我们之前的 Project Loom 文章中了解更多信息)。当 Project Loom 发布时,NIO 实现将能够利用它,而先前的实现将不适合用途。因为 NIO 实现使用Java.util.concurrent 锁而不是同步方法,所以这是可以想象的。

可以通过运行实例化 Socket 和 ServerSocket 的类来查看此调试输出。这是默认(新)的样子:

Java
java -XX:+TraceClassLoading JEP353  | grep Socket
[0.033s][info   ][class,load] java.net.Socket source: jrt:/java.base
[0.035s][info   ][class,load] java.net.SocketOptions source: jrt:/java.base
[0.035s][info   ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.039s][info   ][class,load] java.net.SocketImpl$$Lambda$1/0x0000000800b50840 source: java.net.SocketImpl
[0.042s][info   ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base
[0.042s][info   ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base
[0.043s][info   ][class,load] sun.nio.ch.SocketDispatcher source: jrt:/java.base
[0.044s][info   ][class,load] java.net.DelegatingSocketImpl source: jrt:/java.base
[0.044s][info   ][class,load] java.net.SocksSocketImpl source: jrt:/java.base
[0.044s][info   ][class,load] java.net.ServerSocket source: jrt:/java.base
[0.045s][info   ][class,load] jdk.internal.access.JavaNetSocketAccess source: jrt:/java.base
[0.045s][info   ][class,load] java.net.ServerSocket$1 source: jrt:/java.base


Java
System.out.println(switch (args[0]) {
    // a simple switch case
        case "1" -> 1;
    case "2" -> 2;
    default -> args[0].length();
});


Java
System.out.println(switch (args[0]) {
case "1":
    yield 1;
case "2":
    yield 2;
default: {
    int len = args[0].length();
    yield len;
}
});


使用 Socket 的 Switch 表达式

示例#1

Java

System.out.println(switch (args[0]) {
    // a simple switch case
        case "1" -> 1;
    case "2" -> 2;
    default -> args[0].length();
});

输出:

//This Switch case prints accordingly
//User entered 1
->1

示例#2

Java

System.out.println(switch (args[0]) {
case "1":
    yield 1;
case "2":
    yield 2;
default: {
    int len = args[0].length();
    yield len;
}
});

输出:

//This Switch case prints accordingly
//User entered 1
-> yield 1