Какова современная технология обработки сокетов с помощью Java 1.7? - программирование
Подтвердить что ты не робот

Какова современная технология обработки сокетов с помощью Java 1.7?

Я ищу наилучший образец для реализации взаимодействия со связью клиент/сервер с использованием соответствующих возможностей Java 1.7 (try-with-resources). Необходимо убедиться, что все потоки и ресурсы закрыты и полностью выпущены. Эффективная Java/Чистый код должны рассматриваться как простая и отладочная (только одна заявка в каждой строке).

Любые отзывы или обсуждения будут оценены.

Первый подход

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)
  • Второй экземпляр Socke t, необходимый для использования try-with-resources
  • Глубокое вложение из-за UnknownHostException из InetAddress#getByName(null)

Второй подход

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 может быть забыто (с ошибкой)

Есть ли у вас отзывы или дополнения к плюсам и минусам? Есть ли еще слабые стороны?

Третий подход

Это результат обсуждения ниже:

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 не поддерживается
  • Некоторые нарушения правила "только одна инструкция в каждой строке" (простая отладка)
4b9b3361

Ответ 1

  • Последний модификатор избыточен в try-with-resources, поскольку спецификация пишет:

    Ресурс, объявленный в ResourceSpecification, неявно объявляется окончательным (§4.12.4), если он явно не объявлен окончательным.

  • Закрытие считывателя также закроет базовый поток, то же самое для закрытия записи. Поэтому нет необходимости объявлять промежуточные потоки в качестве ресурсов.
  • ваш второй подход неправильно игнорирует исключения, возникающие при закрытии ресурсов. Закрытие потока сбрасывает его, и если это не удастся, не все данные будут отправлены, что явно гарантирует исключение. В constrast оператор try-with-resources распространяет исключения, которые были закрыты при закрытии (возможно, как исключенные исключения, см. Спецификацию для details).

Поэтому я рекомендую:

try (
    Socket socket = this.socket;
    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()))
) {