Понимание причины исключения NullPointerException в канале сокета Java NIO

#java #nio #nonblocking

#java #nio #неблокирующее

Вопрос:

Я собрал простое серверное приложение типа echo, используя java nio. Хотя он способен записывать данные, он всегда выдает следующее исключение. Кто-нибудь может объяснить, почему это происходит? Мой код приведен ниже. Перед созданием потока, содержащего метод run, я гарантирую, что key.isAcceptable() значение true.

Исключение в потоке «пул-1-поток-2» java.lang.Исключение NullPointerException

 ExecutorService pool = Executors.newFixedPool(5);
...
try {
    pool.execute(                                           
        new Thread() {
            @Override public void run() {
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = null;
                try {
                    sc = ssc.accept();
                    String response = "Server thread ("   Thread.currentThread().getId()   ") Current time: "   new Date();
                    ByteBuffer bytes = encoder.encode(CharBuffer.wrap(response));
                    sc.write(bytes);
                } catch (java.io.IOException e) {
                    System.out.println("Exception: "   e.getMessage());                                                                              
                } finally {
                    try {
                        if (sc.isOpen()) sc.close(); // This is the line causing the exception to be thrown. If the check is changed to (sc != null) no exceptions are thrown.
                    } catch (java.io.IOException e) {
                        System.out.println("Exception: "   e.getMessage());
                    }
                }
            }
        }
    );
} catch (java.lang.Exception e) {
    System.out.println("Interrupted!");
}               
  

Исключение в потоке «пул-1-поток-2»
60java.lang.Исключение NullPointerException

     at EchoServer$1.run(EchoServer.java:61)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExec
  

utor.java:886)
в java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor
.java: 908)
на java.lang.Thread.run(поток.java:619)

Комментарии:

1. @EJP: Добавлена трассировка стека. ПРИМЕЧАНИЕ: Запись в канал сокета не вызывает исключения, и клиент получает ВСЕ переданные данные. Если оператор if изменен на if (sc != null) sc.close(); , исключения не генерируются. Я не понимаю, почему канал не равен нулю, когда я пишу в него, но когда я пытаюсь его закрыть, это так.

Ответ №1:

Я бы предположил, что SocketChannel, возвращаемый ssc.accept, равен null, что, в свою очередь, подразумевает, что ServerSocketChannel находится в неблокирующем режиме и ожидающее соединение отсутствует.

Комментарии:

1. @lainM: Я дважды проверил и ssc.accept() действительно возвращает SocketChannel объект; проблема, по-видимому, связана с ExecutorService пулом, в частности, там, где код пытается закрыть канал сокета — sc.close()

Ответ №2:

Javadoc для ServerSocketChannel говорит:

 If this channel is in non-blocking mode then this method will immediately 
return null if there are no pending connections. Otherwise it will block 
indefinitely until a new connection is available or an I/O error occurs.
  

Вы уверены, что ssc.accept() не возвращается null именно по этой причине?

Комментарии:

1. Я уверен, что ssc.accept() не возвращается null . Однако, основываясь на вашей цитате выше, я считаю, что основной причиной является ExecutorService пул (см. OP).

Ответ №3:

Как было предложено @IainM и @QuantumMechanic, вы не проверяете ‘sc’ на значение null. Если NPE возникает в sc.close(), ‘sc’ было null, я бы сказал, что вы получили NPE, вызывающий sc.write(), и еще один в блоке finally{}, вызывающем sc.close ().

Вы также не проверяете результат write (), поэтому вы можете писать частичные сообщения или вообще ничего не писать. У вас также есть совершенно бессмысленный тест для sc.isOpen(). В этом коде либо null, либо open, другой возможности нет.

И я не знаю, как вы можете называть эту штуку эхо-сервером, когда она не считывает никаких входных данных.

Это не тот способ использования NIO. Вся идея в том, что вам не нужны дополнительные потоки. Что-то серьезно не так со всем вашим дизайном.

Вы также лжете себе, печатая «Прервано!» для любого исключения и скрывая фактическое исключение, не печатая его сообщение или трассировку стека. Это также не способ использования исключений.

Комментарии:

1. ‘sc’ имеет значение null ТОЛЬКО при попытке закрыть его. Вы правы, я не проверял активно результаты write (); Я просто просматривал выходные данные клиента, который отображал ВСЕ содержимое, которое должен был отправить сервер. Что касается того, что это «эхо-сервер», изначально он выполнял как чтение, так и запись, но как только я столкнулся с ошибкой NPE, я сократил это только до компонентов ‘w rite’.

2. Насколько я понимаю, NIO является альтернативой парадигме 1 соединения и 1 потока. В моем игрушечном приложении много подключений к нескольким потокам; поэтому оно все еще находится в рамках NIO. Если вы не согласны, пожалуйста, поясните, почему. Наконец, вывод «Прервано!», очевидно, не лучшая практика; с этой частью кода проблем не было, поэтому я не видел смысла добавлять printStackTrace() .

3. @dansten Ничто из этого не меняет того факта, что вы должны проверить результат write () и отреагировать соответствующим образом, или что вы должны напечатать фактическое исключение, а не предполагать, что это было прерывание. Или что вам не нужен отдельный поток для обработки неблокирующего ввода-вывода. В этом случае, возможно, OP_ACCEPT запускается дважды из-за вашего дизайна потока, поэтому второй поток получает значение null из accept(), в то время как первый принимает и записывает. Я бы переместил, по крайней мере, обработку accept обратно в цикл выбора, возможно, также обработку write ().