Try-with-resources закрывает сокеты порожденных дочерних элементов

Я хочу написать простой сервер, который прослушивает порт и создает новые потоки для обработки новых подключений. Я попытался использовать try-with-resources для приема новых подключений. но не удалось, потому что сокеты в дочерних потоках кажутся немедленно закрытыми, и я не понимаю, почему.

Вот 2 упрощенных примера.
а) Рабочий пример сервера (без 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();
    }
}

б) Точно так же, но с try-with-resources (не работает):

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

Пример в а) работает так, как ожидалось. Пример в b) принимает соединение, но немедленно закрывает его. Может кто-нибудь объяснить мне, почему и сказать мне, как мне это сделать правильно?


person pitseeker    schedule 01.12.2014    source источник
comment
Это ожидаемо, поскольку именно так работает try-with-resources... После блока try все ресурсы, объявленные в открывающих скобках, закрываются.   -  person fge    schedule 01.12.2014
comment
Спасибо за ответ. Можете ли вы сказать, что использование try-with-resources здесь неправильный подход? Можете ли вы порекомендовать более элегантный вариант?   -  person pitseeker    schedule 01.12.2014
comment
Что ж, просто передайте сокет как таковой и используйте оператор try-with-resources в потоках, чтобы закрыть его (вы можете final Socket socket = s; там, где s уже существует)   -  person fge    schedule 01.12.2014
comment
Кроме того, вам следует рассмотреть возможность использования ExecutorService вместо необработанных потоков.   -  person fge    schedule 01.12.2014


Ответы (1)


Структура

try (resource = ...) {
} 

эквивалентно

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

Вот и все. Это просто синтаксический сахар, просто более короткий способ написать то же самое. Итак, когда вы помещаете оператор Socket clientSocket = serverSocket.accept(); в блок try-with-resource, вы фактически закрываете его, как только покидаете блок.

Эта структура хороша, когда обработка потока выполняется синхронно, т.е. когда вы открываете поток, читаете или записываете и закрываете его.

В вашем случае вы получаете поток и обрабатываете его в отдельном потоке и поэтому не можете сразу его закрыть. Клиент должен решить сам закрыть поток. Например, когда пользователь нажимает кнопку «отключиться», или когда сервер отправляет специальную команду уровня приложения «закрыть соединение», или если выдается IOException.

person AlexR    schedule 01.12.2014
comment
Нет, это не эквивалентно. Ресурсы в блоке try-with-resources закрываются перед блоком catch, если он есть. - person fge; 01.12.2014
comment
Спасибо AlexR и fge. - person pitseeker; 02.12.2014