Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the state of the art in socket handling with Java 1.7?

I'm searching for the best pattern to implement client/server socket communication using appropriate features of Java 1.7 (try-with-resources). It must be assured all threads and resources are closed and released completely. Effective Java/Clean Code items should be considered as well as simple debugability (only one statement in each line).

Any feedback or discussion would be appreciated.

First approach

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);
    }
}

Pros:

  • Less lines of code

Cons:

  • Large try-with-resources blocks (no support in Eclipse formatter)
  • Second Socket instance needed to use try-with-resources
  • Deep nesting because of the UnknownHostException from InetAddress#getByName(null)

Second approach

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);
    }
}

Pros:

  • Clean and readable try-with-resources blocks
  • Support in Eclipse formatter

Cons:

  • More lines of code
  • Own Connection class needed
  • Closing the AutoCloseable instances in the Connection class may be forgotten (error prone)

Do you have feedback or additions to the pros and cons? Are there more weaknesses?

Third approach

That's the result of the discussion below:

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);
    }
}

Pros:

  • Less lines of code
  • No to complex and mostly readable try-with-resources blocks
  • No ingored exceptions

Cons:

  • No support in Eclipse formatter
  • Some violations of the rule "only one statements in each line" (simple debugability)
like image 424
Jonathan Avatar asked Feb 23 '13 14:02

Jonathan


People also ask

What is a socket in Java?

A socket is one endpoint of a two-way communication link between two programs running on the network. A socket is bound to a port number so that the TCP layer can identify the application that data is destined to be sent to. An endpoint is a combination of an IP address and a port number.

What is socket in Java with example?

Sockets provide the communication mechanism between two computers using TCP. A client program creates a socket on its end of the communication and attempts to connect that socket to a server. When the connection is made, the server creates a socket object on its end of the communication.

What is TCP IP socket in Java?

TCP/IP Client Sockets. TCP/IP sockets are used to implement reliable two-way, persistent, point-to-point streaming connections between hosts on the Internet. The Java I/O system can use sockets to connect to other programs on the local system or on other systems on the Internet.


1 Answers

  • The final modifier is redundant in a try-with-resources, as the spec writes:

    A resource declared in a ResourceSpecification is implicitly declared final (§4.12.4) if it is not explicitly declared final.

  • closing a reader will also close the underlying stream, same for closing a writer. It is therefore not necessary to declare the intermediary streams as resources.
  • your second approach incorrectly ignores exceptions thrown when closing the resources. Closing a stream flushes it, and if that doesn't suceed not all data will have been sent, which clearly warrants throwing an exception. In constrast, the try-with-resources statement does propagate exceptions thrown when closing (possibly as suppressed exceptions, see the spec for details).

Therefore, I recommend:

try (
    Socket socket = this.socket;
    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()))
) {
like image 177
meriton Avatar answered Sep 20 '22 01:09

meriton