为什么 DatagramSocket#receive(DatagramPacket) 不阻止 macOS?

Why doesn't DatagramSocket#receive(DatagramPacket) block with macOS?

使用以下代码,

import java.io.*;
import java.net.*;

class DatagramServer {

    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(null);
        socket.bind(new InetSocketAddress("127.0.0.1", 0));
        System.out.println(socket.getLocalSocketAddress());
        while (true) {
            // I just need client's address to send back some data!
            DatagramPacket packet = new DatagramPacket(new byte[0], 0);
            socket.receive(packet);
            System.out.println(packet.getSocketAddress());
        }
    }
}

在 Windows 中,它按预期阻塞。

> java --version
java 11.0.8 2020-07-14 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.8+10-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.8+10-LTS, mixed mode)

> java DatagramServer

在 macOS 中它不会打印一堆接收到的套接字地址。

$ java -version
openjdk version "11.0.14" 2022-01-18
OpenJDK Runtime Environment Temurin-11.0.14+9 (build 11.0.14+9)
OpenJDK 64-Bit Server VM Temurin-11.0.14+9 (build 11.0.14+9, mixed mode)

$ java DatagramServer 
/127.0.0.1:61206
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:0
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
/0.0.0.0:15922
...

这正常吗?

我认为@user207421 是正确的,它可能与您的本地配置有关。在我的 macbook 上,第一个打印语句是唯一被执行的语句 - 之后,服务器阻塞。

再想一想,由于您绑定到 127.0.0.1,服务器应该只侦听从您的本地计算机发送的数据,而不是整个本地网络。快速 google 搜索无助于找到此行为的潜在原因,但也许您可以尝试使用如下命令自行分析流量:

while true; do lsof -lnP +M -i4 | grep '<server-port>'; sleep 1; done

来源:https://security.stackexchange.com/a/17204

我们在 MacOS 而不是 Windows 中看到这些问题的原因是 DatagramSocketDatagramPacket class 取决于操作 System-specific 套接字连接的 JDK 实现。由于底层实现取决于 OS,我们在 Windows、macOS 或任何 Linux-based 环境中看到不同的问题。

在上述问题的上下文中,DatagramSocket 对象正在等待 DatagramPacket。该代码创建了一个带有空字节数组的 DatagramPacket。当字节数组为空时,数据报包只是向服务器请求信息。当服务器读取完所有数据包后,while循环结束。

为了从服务器获得响应,客户端创建一个“接收”数据包并使用DatagramSocket 接收方法接收来自服务器的响应。 receive 方法一直等到发往客户端的数据报包通过套接字。

请注意,如果服务器的回复不知何故丢失,由于数据报模型的 no-guarantee 策略,客户端将永远等待。通常,客户端会设置一个计时器,这样它就不会永远等待回复,如果没有回复到达,计时器就会关闭,客户端会重新传输。

因此,这里客户端可能会超时,然后重新绑定可能会失败,因此套接字处于未指定状态。然后,这进一步绑定到由 OS 选择的通配符地址,并且它无限期地重复循环而不被阻塞。

MacOS 中存在一个已知问题。 JDK (15+).
的更高版本中存在此问题的修复 第 1 期JDK-8235674 - Rebinding fail issue Oracle Bug

link 中的信息(JDK-15 的 JEP):

On macOS and Linux, invoking disconnect on the new implementation might require rebinding the underlying socket. This introduces the possibility that the rebinding might fail and leave the underlying socket in an unspecified state. Whereas the legacy implementation might have silently left the socket in an unspecified state.

示例中提供的Java版本为OpenJDK版本11.0.1411.0.8 。 在 JDK-15 之前,如果重新绑定失败,则套接字将处于未指定状态。 DatagramSocket 在断开连接时保持绑定状态,close() 方法会解除绑定。构造函数的默认行为是立即绑定套接字。

但是这里我们通过 DatagramSocket(null) 创建了一个未绑定的 DatagramSocket 作为构造函数参数。当重新绑定失败时,套接字将移动到一个未指定的状态,有可能套接字将保持由 OS 分配,直到 JVM 进程退出。

问题 2: JDK-8231259- DatagramChannel::disconnect re-binds socket to the wildcard address (macOS) and   Oracle Bug

以上问题已在 JDK-14 中修复。 DatagramChannel 绑定到特定的本地地址,连接,然后调用 disconnect 来解除关联。在 macOS 上,disconnect 将套接字重新绑定到 wildcard 地址。如果对象是使用 no-arg 构造函数创建的,您将获得一个未绑定的套接字,因此它 returns 通配符 IP 地址 0.0.0.0。如果 IP 地址为 0.0.0.0,则套接字将绑定到通配符地址,即内核选择的 IP 地址。 0.0.0.0non-routable meta-address 用于指定无效的、未知的或 non-applicable 目标(“无特定地址”占位符)

使用link: JDK 17 : DatagramSocketDatagram Client ServerUDP loop
计算器溢出:DataGramSocket questionsLocaladdress 0.0.0.0
修复:JDK-15 fixRelease notes