Ошибка связывания сокета
У меня есть тестовое приложение, которое открывает сокет, отправляет что-то через этот сокет, а затем закрывает его. Это делается в цикле 5-10 000 раз. Дело в том, что после 3400 итераций я получаю ошибку такого типа:
java.net.BindException: Address already in use: connect
Я даже установил сокет для немедленного использования, но ошибка сохраняется
try
{
out_server.write(m.ToByteArray());
socket_server.setReuseAddress(true);
socket_server.close();
}
catch(Exception e)
{
e.printStackTrace();
System.out.println(i+" unable to register with the server");
}
Что я мог сделать, чтобы это исправить?
7 ответов
Я думаю, что вы можете идти слишком быстро.
У большинства операционных систем есть ограничение на количество сокетов, которые они могут открывать одновременно, но на самом деле это еще хуже.
Когда сокет закрыт, он переводится в особое время ожидания на определенное время. Обычно это вдвое больше времени жизни пакета, и это гарантирует, что в сети еще нет пакетов, которые находятся на пути к вашему сокету.
По истечении этого времени вы можете быть уверены, что все пакеты в сети уже умерли. Сокет находится в этом особом состоянии, так что пакеты, которые были в сети, когда вы его закрыли, могут быть перехвачены и выброшены, если они прибывают до того, как они умирают.
Я думаю, что это то, что происходит в вашем случае, сокеты освобождаются не так быстро, как вы думаете.
У нас была похожая проблема с кодом, который открывал множество недолговечных сессий. Некоторое время он работал нормально, но затем аппаратная часть работала быстрее, что позволяло открывать еще больше за определенный период времени. Это проявилось в невозможности открыть больше сессий.
Один из способов проверить это сделать netstat -a
из командной строки и посмотреть, сколько сессий на самом деле в состоянии ожидания.
Если это действительно так, есть несколько способов справиться с этим.
- повторно использовать ваши сеансы, либо вручную, либо поддерживая пул соединений.
- введите задержку в каждом соединении, чтобы попытаться прекратить достижение точки насыщения.
- идите ровно до тех пор, пока не достигнете насыщения, а затем измените свое поведение, например, запустив логику соединения внутри оператора while, который повторяется до 60 раз с задержкой в две секунды каждый раз, прежде чем полностью сдаться. Это позволяет вам работать на полной скорости, замедляя работу только в случае возникновения проблем.
Этот последний пункт заслуживает некоторого расширения. Мы фактически использовали стратегию отсрочки в нашем вышеупомянутом приложении, которое постепенно уменьшило бы нагрузку на поставщика ресурсов, если бы оно жаловалось, поэтому вместо 30 двухсекундных задержек мы выбрали одну секунду, а затем две секунды. затем четыре и так далее.
Общий процесс для стратегии отсрочки заключается в следующем, и его можно использовать в любом случае, когда может быть временная нехватка ресурса. Действие, упомянутое в псевдокоде ниже, будет открытием сокета в вашем случае.
set maxdelay to 16 # maximum time period between attempts
set maxtries to 10 # maximum attempts
set delay to 0
set tries to 0
while more actions needed:
if delay is not 0:
sleep delay
attempt action
if action failed:
add 1 to tries
if tries is greater than maxtries:
exit with permanent error
if delay is 0:
set delay to 1
else:
double delay
if delay is greater than maxdelay:
set delay to maxdelay
else:
set delay to 0
set tries to 0
Это позволяет процессу работать на полной скорости в подавляющем большинстве случаев, но отключается, когда начинают возникать ошибки, надеясь, что у поставщика ресурсов есть время для восстановления. Постепенное увеличение задержек позволяет восстанавливать более серьезные ограничения ресурсов, и максимальное количество попыток улавливает то, что вы бы назвали постоянными ошибками (или ошибками, восстановление которых занимает слишком много времени).
Мои предложения:
- промойте розетку после записи
- добавить крошечный сон (~50 мс?) в конце вышеуказанного метода
У @Pax есть хорошая точка зрения о состоянии сокета. Попробуйте свой код, дайте ему потерпеть неудачу, а затем сделайте netstat
и проанализировать его (или опубликовать здесь)
Какая операционная система? Если вы используете Windows, и я предполагаю, что вы используете, то есть ограничение на количество клиентских подключений, которые вы можете иметь (это настраивается MaxUserPort
запись в реестре, которая по умолчанию равна 4000; см. http://technet.microsoft.com/en-us/library/aa995661.aspx и http://www.tech-archive.net/Archive/Windows/microsoft.public.windows.server.general/2008-09/msg00611.html для получения подробной информации об его изменении). Это в сочетании с тем фактом, что вы инициируете сокет близко от вашего клиента и таким образом накапливаете сокеты в TIME_WAIT
Состояние вашего клиента, скорее всего, является причиной вашей проблемы.
Обратите внимание, что решение TIME_WAIT
проблема накопления не в том, чтобы возиться с параметрами стека TCP, чтобы проблема исчезла. TIME_WAIT
существует по очень веской причине, и ее удаление или сокращение может вызвать новые проблемы!
Итак, если вы находитесь на компьютере с Windows, первый шаг заключается в настройке вашего MaxUserPort
значение, чтобы у вас было больше динамических портов, доступных для ваших исходящих соединений. Затем, если это не решит проблему, вы можете подумать о том, какая сторона соединения должна заканчиваться TIME_WAIT
(при условии, что вы можете контролировать протокол, используемый в ваших соединениях...) Узел, который выдает "активное закрытие", является тем, который заканчивается TIME_WAIT
так что если вы можете что-то изменить, чтобы ваши серверы выдавали активное закрытие, то TIME_WAIT
сокеты будут накапливаться на сервере, а не на клиенте, и это МОЖЕТ быть лучше для вас...
Я согласен с другими, что у вас заканчиваются конечные точки сокетов. Однако это не на 100% кристально ясно из вашего примера, так как, вероятно, исключение исходит от вызова connect() или bind(), который может лежать в основе какого-то другого высокоуровневого метода Java.
Следует также подчеркнуть, что нехватка конечных точек - это не какое-то странное ограничение библиотеки сокетов, а довольно фундаментальная часть любой реализации TCP/IP. Вам необходимо некоторое время хранить информацию о старых соединениях, чтобы опоздавшие IP-пакеты для старого соединения отбрасывались.
setReuseAddress () соответствует низкоуровневой опции сокета SO_REUSEADDR и применяется к серверу, только когда он выполняет listen().
Если пример кода на самом деле соответствует тому, как вы выполняете цикл, у вас могут быть вещи в неправильном порядке.
Документы Java для setReuseAddress говорят: поведение, когда SO_REUSEADDR включен или отключен после привязки сокета (см. IsBound ()), не определено.
Попробуйте переместить вызов куда-нибудь, прежде чем связать () или подключиться ().
Я думаю, что это то же самое, что и этот вопрос (и я привел ссылку на мой ответ, который, я думаю, может помочь.)
Через некоторое время после использования socket.close() не будет немедленно закрывать сокет, и цикл будет выполняться (в цикле он будет пытаться установить соединение через сокет с тем же ip и портом) намного быстрее, поэтому, пожалуйста, обнулите сокет.
socket_server.close ();
socket_server = null;
Спасибо Сунил Кумар Саху