14

Java 1.7 の適切な機能 ( try-with-resources )を使用して、クライアント/サーバー ソケット通信を実装するための最適なパターンを探しています。すべてのスレッドとリソースが閉じられ、完全に解放されていることを確認する必要があります。効果的な Java /クリーン コード項目と、単純なデバッグ可能性 (各行に 1 つのステートメントのみ) を考慮する必要があります。

フィードバックや議論をいただければ幸いです。

最初のアプローチ

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

import org.junit.Test;

public final class ThreadedServerTest1 {
    private static final class ThreadedServer implements Runnable {
        private final Socket socket;

        ThreadedServer(final Socket socket) {
            this.socket = socket;
        }

        public static void main(final String arguments[]) {
            final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
            try (final ServerSocket serverSocket = new ServerSocket(port)) {
                while (true) {
                    final Socket socket = serverSocket.accept();
                    final ThreadedServer server = new ThreadedServer(socket);
                    final Thread thread = new Thread(server);
                    thread.start();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            // @formatter:off
            try (
                // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7027552 and
                // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7013420:
                final Socket socket = this.socket;
                final InputStream inputStream = socket.getInputStream();
                final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                final OutputStream outputStream = socket.getOutputStream();
                final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
                final PrintWriter printWriter = new PrintWriter(outputStreamWriter);
            ) {
                // @formatter:on
                final String request = bufferedReader.readLine();
                System.out.println(String.format("Received from client: %s", request));
                printWriter.println("Hello client!");
                printWriter.flush();
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static final class Client {
        public static void main(final String arguments[]) {
            final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
            try {
                final InetAddress loopbackAddress = InetAddress.getByName(null);
                // @formatter:off
                try (
                    final Socket socket = new Socket(loopbackAddress, port);
                    final OutputStream outputStream = socket.getOutputStream();
                    final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
                    final PrintWriter printWriter = new PrintWriter(outputStreamWriter);
                    final InputStream inputStream = socket.getInputStream();
                    final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                    final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)
                ) {
                    // @formatter:on
                    printWriter.println("Hello server!");
                    printWriter.flush();
                    final String answer = bufferedReader.readLine();
                    System.out.println(String.format("Answer from server: %s", answer));
                } catch (final IOException e) {
                    e.printStackTrace();
                }
            } catch (final UnknownHostException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test() {
        final String[] arguments = new String[] { "9999" };
        final Thread tread = new Thread() {
            @Override
            public void run() {
                ThreadedServer.main(arguments);
            };
        };
        tread.start();
        Client.main(arguments);
    }
}

長所:

  • コード行の削減

短所:

  • 大規模な try-with-resources ブロック (Eclipse フォーマッターではサポートされていません)
  • try-with-resources を使用するために必要な2 番目Sockeの t インスタンス
  • UnknownHostExceptionfromによる深いネスティングInetAddress#getByName(null)

2 番目のアプローチ

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

import org.junit.Test;

public final class ThreadedServerTest2 {
    private static final class Connection implements AutoCloseable {
        final Socket socket;
        final InputStream inputStream;
        final InputStreamReader inputStreamReader;
        final BufferedReader bufferedReader;
        final OutputStream outputStream;
        final OutputStreamWriter outputStreamWriter;
        final PrintWriter printWriter;

        private Connection(final Socket socket) throws IOException {
            this.socket = socket;
            inputStream = socket.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(inputStreamReader);
            outputStream = socket.getOutputStream();
            outputStreamWriter = new OutputStreamWriter(outputStream);
            printWriter = new PrintWriter(outputStreamWriter);
        }

        static Connection serverConnection(final Socket socket) throws IOException {
            return new Connection(socket);
        }

        static Connection clientConnection(final String hostname, final int port) throws IOException {
            final InetAddress inetAddress = InetAddress.getByName(hostname);
            final Socket socket = new Socket(inetAddress, port);
            return new Connection(socket);
        }

        static Connection localhostCientConnection(final int port) throws IOException {
            return clientConnection(null, port);
        }

        @Override
        public void close() {
            closeAndIgnoreException(printWriter, outputStreamWriter, outputStream, bufferedReader, inputStreamReader, inputStream, socket);
        }

        private void closeAndIgnoreException(final AutoCloseable... closeables) {
            for (final AutoCloseable closeable : closeables) {
                try {
                    closeable.close();
                } catch (final Exception ignore) {
                }
            }
        }
    }

    private static final class ThreadedServer implements Runnable {
        private final Socket socket;

        private ThreadedServer(final Socket socket) {
            this.socket = socket;
        }

        public static void main(final String arguments[]) {
            final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
            try (final ServerSocket serverSocket = new ServerSocket(port)) {
                while (true) {
                    final Socket socket = serverSocket.accept();
                    final ThreadedServer server = new ThreadedServer(socket);
                    final Thread thread = new Thread(server);
                    thread.start();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            try (Connection connection = Connection.serverConnection(socket)) {
                final String request = connection.bufferedReader.readLine();
                System.out.println(String.format("Received from client: %s", request));
                connection.printWriter.println("Hello client!");
                connection.printWriter.flush();
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static final class Client {
        public static void main(final String arguments[]) {
            final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
            try (Connection connection = Connection.localhostCientConnection(port)) {
                connection.printWriter.println("Hello server!");
                connection.printWriter.flush();
                final String answer = connection.bufferedReader.readLine();
                System.out.println(String.format("Answer from server: %s", answer));
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test() {
        final String[] arguments = new String[] { "9999" };
        final Thread tread = new Thread() {
            @Override
            public void run() {
                ThreadedServer.main(arguments);
            };
        };
        tread.start();
        Client.main(arguments);
    }
}

長所:

  • きれいで読みやすい try-with-resources ブロック
  • Eclipse フォーマッターでのサポート

短所:

  • より多くのコード行
  • 独自のConnectionクラスが必要
  • AutoCloseableクラス内のインスタンスを閉じるConnectionことを忘れる可能性があります (エラーが発生しやすい)

長所と短所へのフィードバックや追加はありますか? これ以上の弱点はありますか?

3 番目のアプローチ

それが以下の議論の結果です。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

import org.junit.Test;

public final class ThreadedServerTest3 {
    private static final class ThreadedServer implements Runnable {
        private final Socket socket;

        private ThreadedServer(final Socket socket) {
            this.socket = socket;
        }

        public static void main(final String arguments[]) {
            final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
            try (final ServerSocket serverSocket = new ServerSocket(port)) {
                while (true) {
                    final Socket socket = serverSocket.accept();
                    final ThreadedServer server = new ThreadedServer(socket);
                    final Thread thread = new Thread(server);
                    thread.start();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            // @formatter:off
            try (
                // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7027552 and
                // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7013420:
                Socket socket = this.socket;
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
            ) {
                // @formatter:on
                final String request = bufferedReader.readLine();
                System.out.println(String.format("Received from client: %s", request));
                printWriter.println("Hello client!");
                printWriter.flush();
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static final class Client {
        public static void main(final String arguments[]) {
            final int port = arguments.length > 0 ? Integer.parseInt(arguments[0]) : 9999;
            // @formatter:off
            try (
                Socket socket = new Socket(InetAddress.getByName(null), port);
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()))
            ) {
                // @formatter:on
                printWriter.println("Hello server!");
                printWriter.flush();
                final String answer = bufferedReader.readLine();
                System.out.println(String.format("Answer from server: %s", answer));
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test() {
        final String[] arguments = new String[] { "9999" };
        final Thread tread = new Thread() {
            @Override
            public void run() {
                ThreadedServer.main(arguments);
            };
        };
        tread.start();
        Client.main(arguments);
    }
}

長所:

  • コード行の削減
  • 複雑で読みやすい try-with-resources ブロックにはノー
  • 無視された例外なし

短所:

  • Eclipse フォーマッターでのサポートなし
  • 「各行に 1 つのステートメントのみ」というルールのいくつかの違反 (単純なデバッグ可能性)
4

1 に答える 1

8
  • 仕様にあるように、最後の修飾子は、try-with-resourcesでは冗長です

    ResourceSpecificationで宣言されたリソースは、明示的にfinalとして宣言されていない場合、暗黙的にfinal(§4.12.4)として宣言されます。

  • リーダーを閉じると、ライターを閉じる場合と同じように、基になるストリームも閉じます。したがって、中間ストリームをリソースとして宣言する必要はありません。
  • 2番目のアプローチでは、リソースを閉じるときにスローされた例外が誤って無視されます。ストリームを閉じるとフラッシュされます。それでも成功しない場合は、すべてのデータが送信されていないため、例外をスローする必要があります。対照的に、try-with-resourcesステートメントは、閉じるときにスローされた例外を伝播します(おそらく、抑制された例外として、詳細については仕様を参照してください)。

したがって、私はお勧めします:

try (
    Socket socket = this.socket;
    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()))
) {
于 2013-02-23T15:29:04.777 に答える