11

我有这个该死的结构:

public void run() {

        try {
            if (!portField.getText().equals("")) {              
                String p = portField.getText();
                CharSequence numbers = "0123456789";

            btnRun.setEnabled(false);

            if (p.contains(numbers)) {
                ServerSocket listener = new ServerSocket(Integer.parseInt(p));

                while (true) {
                    Socket socket = listener.accept();
                    try {
                        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                        out.println("Hi there, human.");    
                    } finally {
                        socket.close(); 
                    }
                }} else {
                    JOptionPane.showMessageDialog(null, "Only numbers are allowed.");
                }
            }

        } catch (NumberFormatException | HeadlessException | IOException e) {
            e.printStackTrace();
        } 
    }

如您所见,我需要完全关闭侦听器,就像我对套接字所做的那样。问题是如果我在循环之后尝试这样做,代码将“无法访问”,并且如果我尝试在任何地方为 ServerSocket 声明一个字段,我会得到一个 NullPointerException。我不想与套接字/客户端一起关闭 ServerSocket,因为我想建立新的连接。

所以这就是我的问题:

在这种情况下真的有必要关闭 ServerSocket 吗?软件关闭时ServerSocket自行关闭?(System.exit(0))。如果 ServerSocket 在我关闭软件时继续运行只是因为我没有关闭它,那么我就有问题了,因为我无法访问那段该死的代码来关闭它:)。

4

3 回答 3

11

是的。虽然销毁对套接字的引用可能会导致垃圾收集器完成它,但这并没有指定它将被关闭。在许多情况下,这是特定于实现的,并且有时会由于性能甚至难以跟踪的微小错误而与设计脱轨。

可以肯定的是,即使使用 Wea​​kReferences,也不会在任何地方保留引用。

现在操作系统可以提供的套接字数量有限(由于其设计)。随着您打开越来越多的套接字,操作系统会承受压力并最终耗尽套接字,从而导致 Java 或其他程序的故障。此外,根据您或默认设置的套接字选项,此套接字可能会发送保活,也会耗尽另一个端点上的资源。

在退出时,它被套接字在其构造函数中注册关闭操作以关闭它,和/或操作系统的自动清理。

永远不应该依赖 OS/JVM 行为或终结来关闭你的套接字,尤其是当你有多个套接字时,即使你不打算同时使用它们。

于 2013-06-29T22:59:28.337 回答
8

是的,有必要释放任何有限的资源,否则您的应用程序将使主机上的其他进程因资源匮乏而无法长期维持

这是我执行此类任务的方法:

public void run() {
    ServerSocket serverSocket = null;
    try {
        serverSocket = ... // init here
    } catch (...) {

    } finally {
        if (serverSocket != null) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                // log error just in case
            }
        }
    }
}

此外,将 GUI 代码移动到其他类将是一个好主意,以保持事情清洁。

于 2013-06-29T23:04:36.837 回答
3

一个答案是你真的应该close()资源......即使它不是绝对必要的。

为什么?

因为,(假设)意味着它不是绝对必要的情况可能会改变!

而且......让我们面对现实......编写代码来自动关闭服务器套接字很容易。

但是请继续阅读...


在这种情况下真的有必要关闭 ServerSocket 吗?

这取决于。

  • 如果您确定run()只会运行一次,然后应用程序总是会完全关闭(下面的模数警告),那么不关闭ServerSocket不会造成任何实际伤害。

  • 否则,关闭()失败可能会造成伤害。

“伤害”可能有几种形式:

  • 服务器套接字可以在端口上保持“锁定”,防止应用程序的另一个实例侦听该端口(取决于操作系统)。

  • 服务器套接字可能是有限的资源,如果run()多次调用该方法(没有close()-ing),应用程序可以“抓取”该资源的所有可用实例,从而阻止其他应用程序获取实例。

run()如果多次调用,也可能对应用程序实例本身造成损害。


软件关闭时ServerSocket自行关闭?

ServerSocket 不会“自行关闭”,除非它被垃圾收集并最终确定。而且你不能依赖 JVM 退出时发生的任何一种情况。但在更广泛的意义上,底层服务器套接字资源通常会在 JVM 退出时自动关闭(释放)。

但完整的答案实际上取决于您所说的“软件已关闭”,以及平台的性质。例如:

  • 如果关闭意味着JVM完全退出,并且“拥有”底层服务器套接字(从操作系统的角度)的相应进程退出,那么操作系统将关闭该套接字......至少在现代Unix / Linux / Windows平台上. (请注意,Android 在底层是 Linux)

  • 如果“软件”类似于在 web 容器中运行的 webapp,则“关闭”可能意味着与 JVM 退出不同的东西,并且 ServerSocket 可以在关闭后继续存在(而不是关闭)。

  • 如果 JVM 嵌入在其他东西中(例如,在大型 C / C++ 应用程序中)并且其他东西没有退出,那么操作系统将不知道释放底层服务器套接字。

  • 如果您在操作系统不提供与 Unix / Linux /(现代)Windows 上相同级别的进程分离/资源管理的平台上运行,则操作系统可能不会在进程退出时关闭底层服务器套接字。(这种情况可能适用于嵌入式系统上的 Java 实现......)


无论如何,做正确的事并不难。在 Java 7 及更高版本中:

public void run() {
    String p = portField.getText().trim();
    if (p.isEmpty()) {
        return;
    }
    btnRun.setEnabled(false);
    try (ServerSocket listener = new ServerSocket(Integer.parseInt(p))) {
        while (true) {
            try (Socket socket = listener.accept();
                 PrintWriter out = new PrintWriter(
                         socket.getOutputStream(), true)) {
                out.println("Hi there, human.");    
            }
        }
    } catch (NumberFormatException e) {
        JOptionPane.showMessageDialog(null, "Only numbers are allowed.");
    } catch (HeadlessException | IOException e) {
        e.printStackTrace();
    } finally {
        btnRun.setEnabled(true); // ... possibly
    }
}

(不过,我看不出捕捉HeadlessException按钮侦听器调用的动作的意义。)

于 2013-06-29T23:20:47.847 回答