#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.Исключение NullPointerExceptionat 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 ().