14

我正在寻找使用 Java 1.7 ( try-with-resources ) 的适当特性来实现客户端/服务器套接字通信的最佳模式。必须确保所有线程和资源都完全关闭和释放。应考虑有效的 Java / Clean Code项以及简单的可调试性(每行中只有一个语句)。

任何反馈或讨论将不胜感激。

第一种方法

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使用 try-with-resources 所需的第二个t 实例
  • 由于UnknownHostExceptionfrom的深层嵌套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 格式化程序不支持
  • 一些违反规则“每行只有一个语句”(简单可调试性)
4

1 回答 1

8
  • 正如规范所写,final 修饰符在 try-with-resources 中是多余的:

    如果在 ResourceSpecification 中声明的资源未显式声明为 final,则它被隐式声明为 final(第 4.12.4 节)。

  • 关闭阅读器也将关闭底层流,与关闭编写器相同。因此没有必要将中间流声明为资源。
  • 您的第二种方法错误地忽略了关闭资源时引发的异常。关闭流会刷新它,如果没有成功,则不会发送所有数据,这显然需要引发异常。相反,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 回答