Ваша проблема в ClientThread:
private static Socket socket = null;
Это означает, что все потоки совместно используют один и тот же экземпляр сокета для всех экземпляров ClientThread.Это означает, что ваши сокеты будут синхронизированы с состоянием разговора.Ваш разговор:
Состояния клиента:
- Клиент подключается
- Клиент отправляет запрос
- Клиент ожидает ответа
- Клиентполучает ответ
- Клиентский сокет закрытия (должен делать каждый).
Состояния сервера:
- Сервер ожидает клиента
- Сервер читает запрос
- Сервер обрабатывает команду
- Сервер отправляет ответ
- Сервер закрывает сокеты
Тем не менее, у вас есть 30 разговоров, все пытаются войти вразные состояния одновременно, и сервер и клиент не могут их отслеживать.Первый поток создает сокет, отправляет запрос на перемещение в состояние 2, в то время как он ожидает, что другой поток создает другой сокет, записывает его в перемещение в состояние 2, когда первый поток активизируется, а затем начинает говорить по новому сокету, который был создан потоком 2который не закончил обработку своей команды.Теперь третий запускает и перезаписывает эту ссылку снова, и так далее, и так далее.Первая команда никогда не будет прочитана, потому что она потеряла ссылку на исходный сокет, когда поток 2 перезаписал ее, и начинает читать команду потока 2 и выплевывает ее.Как только поток 1 обращается к операторам close, он закрывает сокет, в то время как другие потоки пытаются читать / писать в него, и отбрасывается исключение.
По существу каждый ClientThread должен создать свой собственный экземпляр сокета, итогда каждый разговор может происходить независимо от других продолжающихся разговоров.Подумайте, написали ли вы ваш клиент однопоточным.Затем запустили два отдельных процесса (дважды запустите приложение Java).Каждый процесс будет создавать свой собственный сокет, и каждый диалог будет работать независимо.Теперь у вас есть один сокет с 30 потоками, выкрикивающими команды через один мегафон на сервер.Работа не может продолжаться упорядоченным образом, когда все кричат о том же мегафоне.
Таким образом, в результате краткого изменения удалите статический модификатор из элемента сокета в ClientThread, и он должен начать работать довольно хорошо.
Так же, как в стороне, никогда не выпускайте этот код в мир.У него серьезные проблемы с безопасностью, потому что клиенты могут выполнять любую команду на уровне безопасности, выполняемом вашим серверным процессом.Так что очень легко любой может владеть вашей машиной или сравнительно легко захватить вашу учетную запись.Выполнение таких команд с клиента означает, что они могут отправлять:
sudo cat /etc/passwd
и, например, захватывать хеши паролей.Я думаю, что вы только учитесь, но я чувствовал, что вы должны быть предупреждены о том, что вы делаете, чтобы быть в безопасности.
Другое дело, что сервер закрывает сокеты, только если разговор прошел, как ожидалось,Вы действительно должны переместить вызовы close () в блок finally того блока try, который у вас есть.В противном случае, если клиент закрывает свой сокет преждевременно (это происходит), то на вашем сервере будут протекать сокеты, и в конечном итоге в ОС останутся сокеты.
public void run() {
try {
} catch( SomeException ex ) {
logger.error( "Something bad happened", ex );
} finally {
out.close(); <<<< not a bad idea to try {} finally {} these statements too.
in.close(); <<<< not a bad idea to try {} finally {} these statements too.
socket.close();
}
}
Другая вещь, которую вы, возможно, захотите изучить, - это использование пулов потоков на сервере вместо обновления потока для каждого нового соединения, которое вы получаете.В вашем простом примере с этим легко поиграть, и это работает.Однако, если вы строили реальный сервер, пул потоков имеет два основных вклада.1. создание потоков связано с накладными расходами, так что вы можете получить некоторое время отклика производительности, если поток будет сидеть, ожидая обработки входящих запросов.Вы можете сэкономить время, чтобы отвечать клиентам.2. Что еще более важно, физический компьютер не может бесконечно создавать потоки.Если бы у вас было много клиентов, скажем, 1000+, ваша машина не могла бы ответить на все те, которые используют 1000 потоков.Пул потоков означает, что вы создаете максимальное количество потоков, скажем, 50 потоков, и каждый поток будет использоваться несколько раз для обработки каждого запроса.По мере появления новых соединений они ожидают освобождения потока перед обработкой.Если вы получаете слишком много подключений, поступающих в клиентах, тайм-аут, в отличие от уничтожения вашей машины, требующей перезагрузки.Это позволяет быстрее выполнять больше запросов, не давая вам умирать, если слишком много подключений происходит одновременно.
Наконец, исключение закрытия сокета может произойти по многим уважительным причинам.Часто, если клиент просто выключается в середине разговора, сервер получает это исключение.Лучше всего правильно отключить и очистить себя, когда это произойдет.Вы никогда не сможете предотвратить это моя точка зрения.Вы просто должны ответить на это.