Try-with-resources closes sockets of spawned child

2019-08-14 17:08发布

问题:

I want to write a simple server that listens on a port and spawns new threads for handling new connections. I attempted to use try-with-resources for accepting new connections but failed because sockets in child threads seem to be closed immediately and I don't understand why.

Here are 2 simplified examples.
a) The working example of the server (without try-with-resources):

package MyTest;

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

public class MyServerA implements Runnable  {
    private int port;
    private ServerSocket serverSocket;

    public MyServerA(Integer port)  {
        this.port = port;
    }

    @Override
    public void run() {
        try     {   
            serverSocket = new ServerSocket(port);
        } catch(IOException ioe) {
            System.err.println("error opening socket. " + ioe.getStackTrace());
        }

        while (true) {
            Socket clientSocket = null;
            try {
                clientSocket = serverSocket.accept();
                ClientServiceThread cliThread = new ClientServiceThread(clientSocket);
                cliThread.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class ClientServiceThread extends Thread {
        private Socket s;
        boolean goOn = true;

        ClientServiceThread(Socket s) {
            this.s = s;
        }

        public void run() {
            BufferedReader in = null;
            PrintWriter out = null;

            try {
                in = new BufferedReader(new InputStreamReader(this.s.getInputStream()));
                out = new PrintWriter(new OutputStreamWriter(this.s.getOutputStream()));

                while (goOn) {
                    final String req = in.readLine();
                    if (req != null) {
                        System.out.println("got: " + req);
                        out.println("you said: " + req);
                        out.flush();

                        if (req.contains("bye")) {
                            System.out.println("closing thread");
                            goOn = false;
                        }
                    }
                }
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyServerA a = new MyServerA(30000);
        a.run();
    }
}

b) Exactly the same, but with try-with-resources (doesn't work):

package MyTest;

import java.io.BufferedReader;

public class MyServerB implements Runnable  {
    private int port;
    private ServerSocket serverSocket;

    public MyServerB(Integer port)  {
        this.port = port;
    }

    @Override
    public void run() {
        try {   
            serverSocket = new ServerSocket(port);
        } catch(IOException ioe) {
            System.err.println("error opening socket. " + ioe.getStackTrace());
        }

        while (true) {
            try (Socket clientSocket = serverSocket.accept();) {
                ClientServiceThread cliThread = new ClientServiceThread(clientSocket);
                cliThread.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class ClientServiceThread extends Thread {
        private Socket s;
        boolean goOn = true;

        ClientServiceThread(Socket s) {
            this.s = s;
        }

        public void run() {
            BufferedReader in = null;
            PrintWriter out = null;

            try {
                in = new BufferedReader(new InputStreamReader(this.s.getInputStream()));
                out = new PrintWriter(new OutputStreamWriter(this.s.getOutputStream()));

                while (goOn) {
                    final String req = in.readLine();
                    if (req != null) {
                        System.out.println("got: " + req);
                        out.println("you said: " + req);
                        out.flush();

                        if (req.contains("bye")) {
                            System.out.println("closing thread");
                            goOn = false;
                        }
                    }
                }
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyServerB b = new MyServerB(30000);
        b.run();
    }
}

The example in a) works like expected. The example in b) accepts a connection but closes it immediately. Can someone explain to me why and tell me how I do this properly?

回答1:

The structure

try (resource = ...) {
} 

is equivalent to

resource = null;
try {
   resource = ...;
}  finally {
    if (resource != null) {
        resource.close();
    }
}

That's it. It is just a syntactic sugar, just shorter way to write the same. So, when you put statement Socket clientSocket = serverSocket.accept(); into try-with-resource block you actually close it once you leave the block.

This structure is good when processing of stream is done synchronously, i.e. when you open stream, read or write and close it.

In your case you get the stream and process it in separate thread and therefore cannot close it immediately. Client should decide to close the stream itself. For example when user press button "disconnect" or when server sends special application level command "close connection" or if IOException is thrown.