Ошибка связывания сокета

У меня есть тестовое приложение, которое открывает сокет, отправляет что-то через этот сокет, а затем закрывает его. Это делается в цикле 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 ()), не определено.

Попробуйте переместить вызов куда-нибудь, прежде чем связать () или подключиться ().

Я думаю, что это то же самое, что и этот вопрос (и я привел ссылку на мой ответ, который, я думаю, может помочь.)

Java Bind Exception

Через некоторое время после использования socket.close() не будет немедленно закрывать сокет, и цикл будет выполняться (в цикле он будет пытаться установить соединение через сокет с тем же ip и портом) намного быстрее, поэтому, пожалуйста, обнулите сокет.

socket_server.close ();

socket_server = null;

Спасибо Сунил Кумар Саху

Другие вопросы по тегам