web-dev-qa-db-ja.com

ServerSocket-それをclose()する必要が本当にありますか?

私はこのひどい構造を持っています:

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が引き続き実行される場合は、問題が発生しています。その血まみれのコードに到達して閉じることができないためです:)。

11

はい。ソケットへの参照を破棄するとmay、ガベージコレクターがソケットをファイナライズしますが、それはソケットが閉じられることを指定していません。これは多くの場合実装固有であり、パフォーマンスや追跡が難しい小さなバグのために、設計に比べて脱線することがあります。

参照をどこにでも保持することは確実ではないことを賭けます、WeakReferencesを使用しても。

現在、OSには(その設計のために)提供できるソケットの数が制限されています。ますます多くのソケットを開くと、OSに圧力がかかり、最終的にソケットが不足し、Javaまたは別のプログラムのいずれかが失敗します。さらに、ソケットオプションに依存すると、ユーザーまたはデフォルトが発生する可能性があります。セットすると、このソケットはキープアライブを送信し、他のエンドポイントのリソースも使い果たす可能性があります。

終了時に、コンストラクターにシャットダウンアクションを登録して閉じるソケット、および/またはOSの自動クリーンアップによって閉じられます。

ソケットを閉じるために決してOS/JVMの動作またはファイナライズに依存しないでください特に複数のソケットがある場合は、それらすべてを同時に使用する予定はありません。

11
nanofarad

はい、有限のリソースを解放する必要があります。そうしないと、アプリがホスト上の他のプロセスのリソースを枯渇させ、長期的に維持できなくなるためです。

これが私がそのようなタスクを行う方法です:

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コードを他のクラスに移動することは、物事をクリーンに保つための良いアイデアです。

8
Victor Sorokin

1つの答えは、厳密に必要でなくても、リソースをclose()する必要があるということです。

どうして?

なぜなら、(仮想的に)それが厳密に必要ではないことを意味する状況変わる可能性があります!

そして...それに直面しましょう...サーバーソケットを自動的に閉じるコードを書くのは簡単です。

しかし、読んでください...


この場合、ServerSocketを閉じる必要がありますか?

場合によります。

  • run()が一度だけ実行され、アプリケーションが常に完全にシャットダウンすることを確実に知っている場合(以下のモジュロ警告)、その後ServerSocketを閉じなくても、実際に害を及ぼすことはありません。

  • そうしないと、close()に失敗すると、害を及ぼす可能性があります

「害」にはいくつかの形態があります。

  • サーバーソケットはポートで「ロック」を保持し、アプリケーションの別のインスタンスがそのポートでリッスンするのを防ぐことができます(OSによって異なります)。

  • サーバーソケットは限られたリソースである可能性があり、run()メソッドが複数回呼び出された場合(close()- ingなし)、アプリケーションはそのリソースの使用可能なすべてのインスタンスを「取得」して、他のアプリケーションがインスタンスを取得することから。

run()が複数回呼び出された場合、アプリケーションインスタンス自体にも害が及ぶ可能性があります。


ソフトウェアがシャットダウンされると、ServerSocketは自動的に閉じますか?

ServerSocketは、ガベージコレクションされてファイナライズされない限り、「それ自体を閉じ」ません。また、JVMの終了時に発生することにも依存できません。しかし、より広い意味では、基盤となるサーバーソケットリソースは、JVMが終了すると、通常自動的に閉じられます(解放されます)。

しかし、完全な答えは、実際には「ソフトウェアがシャットダウンされている」という意味と、プラットフォームの性質によって異なります。例えば:

  • シャットダウンとは、JVMが完全に終了し、(OSの観点から)基盤となるサーバーソケットを「所有」する対応するプロセスも終了することを意味する場合終了します。 OSはそのソケットを閉じます...少なくとも最新のUnix/Linux/Windowsプラットフォームでは。 (Androidは内部のLinuxであることに注意してください)

  • 「ソフトウェア」がWebコンテナで実行されているWebアプリのようなものである場合、「シャットダウン」はJVM出口とは異なる何かを意味する可能性があり、ServerSocketはシャットダウン後も存在し続ける(閉じられない)可能性があります。

  • JVMが他の何か(たとえば、大規模なC/C++アプリケーション)に埋め込まれていて、他の何かが終了しない場合、OSは基盤となるサーバーソケットを解放することを認識しません。

  • OSがUnix/Linux /(最新の)Windowsと同じレベルのプロセス分離/リソース管理を提供しないプラットフォームで実行している場合、基盤となるサーバーソケットはそうではない可能性がありますプロセス終了時にOSによって閉じられます。 (このシナリオは、組み込みシステムでの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をキャッチする意味はわかりません。)

3
Stephen C