В Java, почему readLine () блокирует на закрытом соединении сокета?

У меня есть простое приложение клиент / сервер. Сервер настроен так, что если данные не поступают в течение N секунд, происходит тайм-аут и соединение сокета закрывается. Я делаю это через сокет.setSoTimeout(). Все работает нормально, если клиент зависает. Если клиенты умирают (например, Я убиваю его с помощью Ctrl-C), то readLine() никогда не истекает.

вот код сервера, если это имеет значение:

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

    try {
        sock.setSoTimeout(10000);

        out = new PrintWriter(sock.getOutputStream(), true);
        in = new BufferedReader(new InputStreamReader(sock.getInputStream()));

        String input;
        while ((input = in.readLine()) != null) {

Я попытался ввести обработчик сигнала в клиент для отправки сообщение ABEND на сервер, но это не работает (я подозреваю, что сокет закрывается до отправки ABEND, но я действительно не потратил никакого времени, пытаясь понять это).

есть ли способ периодически просыпаться и проверять состояние сокета, чтобы увидеть, закрыто ли оно? Или (еще лучше) не имеют readLine () повесить, если сокет закрывается? Должен ли я использовать какой-то unbuffered reader? Существует ли unbuffered reader, поддерживающий механизм, подобный readLine?

I я использую Java 6 в Linux.

EDIT: я сам убиваю клиента во время простоя; все данные были отправлены и получены в этот момент. Я проверил (через ps), что клиентская программа больше не работает; процесс Java действительно был убит. Используя netstat, я вижу, что сокет закрыт с обоих концов, но readLine() все еще висит.

3 ответов


Я думаю, что вы неправильно диагностировали проблему.

если входная сторона сокета была закрыта, то readLine() вызов должны видим, что рядом. После того, как все буферизованные данные были использованы, вызов должен вернуть null. (Вы не получите исключение IOException, если сокет закрыт нормально, но вы можете получить его, если время соединения истекло ... например.)

наиболее очевидным объяснением является то, что сокет еще не был закрыт. Взгляните на обстоятельства, при которых другой конец закроет розетку, и выяснить, почему этого еще не произошло.


ни один из них не является решением, но я отвечу за полноту.

есть ли способ периодически просыпаться и проверять состояние сокета, чтобы увидеть, закрыто ли оно?

другой поток может сделать это, но не этот.

или (еще лучше) не имеют readLine () повесить, если сокет закрывается?

это то, что должно произойти.

должен ли я использовать какой-то unbuffered reader?

Это не поможет. В конечном итоге вам придется реализовать readLine() сами, и ваш код будет блокировать в той же точке. Поведение блокировки происходит ниже слои буферизации и декодирования символов цепочки считывателя.

делает unbuffered читатель поддерживая readLine-как механизм существует?

нет, это не так. См. выше.


разве вы не ненавидите, когда вы проводите часы, преследуя проблему, которой нет? В моей попытке предоставить SSCCE для Stephen C я обнаружил, что не могу предоставить такой пример, потому что это is работает. Мой код отладки создавал проблему...

для записи, вот рабочий код (в случае, если кто-то еще есть похожие проблема):

sscceServer.java

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;


public class sscceServer
{
    public static class EchoHandlerThread implements Runnable
    {
        private final Socket sock;
        private final int id;

        public EchoHandlerThread(Socket s, int i)
        {
            sock = s;
            id = i;
        }

        public void run()
        {
            try {
                //----- set socket read timeout to 10 seconds -----
                sock.setSoTimeout(10000);

                PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
                BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));

                String input;
                while ((input = in.readLine()) != null) {
                    System.out.println("[" + id + "] <" + input + ">");
                }

                out.close();
                in.close();
                sock.close();

                System.out.println("\tConnection #" + id + " closed");
            }
            catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    public static class Listener
    {
        private final InetAddress bindip;
        private final int portnum;

        public Listener(String ipstr, int pn)
        {
            try {
                bindip = InetAddress.getByName(ipstr);
                portnum = pn;
            }
            catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }

        public void start()
        {
            try {
                ServerSocket srvsock = new ServerSocket(portnum, 0, bindip);
                System.out.println("Listening on " + bindip.getHostAddress() + ":" + portnum);

                int connum = 0;
                while (true) {
                    Socket sock = srvsock.accept();

                    connum++;
                    InetAddress ipaddr = sock.getInetAddress();
                    System.out.println("Connection #" + connum + " from: " + ipaddr.getHostAddress());

                    new Thread(new EchoHandlerThread(sock, connum)).start();
                }
            }
            catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
    }


    public static void main(String[] args)
    {
        Listener lsnr = new Listener("localhost", 8988);
        lsnr.start();
    }
}

sscceClient.java

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public final class sscceClient
{
    public static void main(String[] args)
    {
        try {
            Socket sock = new Socket("localhost", 8909);

            PrintWriter out = new PrintWriter(sock.getOutputStream(), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));

            out.println("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
            out.println("Ut sem ante, mollis ut porttitor at, aliquet vel nulla.");
            out.println("Suspendisse quis sapien ante, a cursus nunc.");

            //---- sleep for 20 seconds -----
            Thread.sleep(20000);

            out.close();
            in.close();
            sock.close();
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

Спасибо за помощь!


по словам Javadoc, the readLine() метод выдаст исключение IOException. И в соответствии с IOException Javadoc, ожидается, что исключение будет брошено на прерывание. Без написания кода я бы сказал, что вы можете обрабатывать случай преждевременного закрытия потока, поймав IOException.