Search code examples
javamultithreadingsocketssocketexceptioneofexception

EOFException / SocketException when working with sockets and threads


This socket application works perfectly fine until I add support for multiple client connections to the server. Then I get a EOFException from the client, and a SocketException: Socket closed from the server.

Server.java:

public class Server {

    static final int PORT = 8005;
    static final int QUEUE = 50;

    public Server() {
        while (true) {
            try (ServerSocket serverSocket = new ServerSocket(PORT, QUEUE);
                 Socket socket = serverSocket.accept();
                 DataInputStream input = new DataInputStream(socket.getInputStream());
                 DataOutputStream output = new DataOutputStream(socket.getOutputStream())) {

                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            output.writeUTF("Hey, this is the server!");
                            output.flush();
                            System.out.println(input.readUTF());
                        } catch (IOException e) {
                            System.out.println();
                            e.printStackTrace();
                        }
                    }
                });
                thread.start();

            } catch (IOException e) {
                System.out.println(e.getMessage());
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Server();
    }

}

Client.java:

public class Client {

    static final String HOST = "localhost";
    static final int PORT = 8005;

    public Client() {
        try (Socket socket = new Socket(HOST, PORT);
             DataInputStream input = new DataInputStream(socket.getInputStream());
             DataOutputStream output = new DataOutputStream(socket.getOutputStream())
        ) {
            System.out.println(input.readUTF());
            output.writeUTF("Hey, this is the client!");
            output.flush();
        } catch (IOException e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Client();
    }

}

Solution

  • A couple problems here:

    1. You're creating a new ServerSocket for each pass through the loop. For a multi-client server you should instead be opening one ServerSocket and calling accept() on it for each client that connects.
    2. Try-with-resources closes all resources it's provided with as soon as the try block is exited. You're creating a Thread that uses output but executes independently of the try block, so the execution flow is leaving the try block before thread finishes executing, resulting in socket (and output) being closed before the thread is able to use them. This is one of those situations where your resources need to be used outside the scope of the try block (in the thread you create to use them), so try-with-resources can't do all your resource handling for you.

    I would rearrange your server code to something like:

    public class Server {
    
        static final int PORT = 8005;
        static final int QUEUE = 50;
    
        public Server() {
            // create serverSocket once for all connections
            try (ServerSocket serverSocket = new ServerSocket(PORT, QUEUE)) {
                while (true) {
                    // accept a client connection, not in a try-with-resources so this will have to be explicitly closed
                    final Socket socket = serverSocket.accept();
    
                    Thread thread = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            // limit scope of input/output to where they're actually used
                            try (DataInputStream input = new DataInputStream(socket.getInputStream());
                                    DataOutputStream output = new DataOutputStream(socket.getOutputStream())) {
                                output.writeUTF("Hey, this is the server!");
                                output.flush();
                                System.out.println(input.readUTF());
                            } catch (IOException e) {
                                System.out.println();
                                e.printStackTrace();
                            }
    
                            // implicitly close socket when done with it
                            try {
                                socket.close();
                            } catch (IOException e) {
                                System.out.println();
                                e.printStackTrace();
                            }
                        }
                    });
                    thread.start();
                }
            } catch (IOException e) {
                System.out.println(e.getMessage());
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            new Server();
        }
    }
    

    Code is commented somewhat to explain some of the moves. Also note that the socket.close() call is in its own try-catch block to ensure that it's called even if the I/O streams throw an exception. It could equivalently (or perhaps more correctly now that I think about it) been placed in a finally block on the I/O stream try-catch block.