Подтвердить что ты не робот

Как предотвратить утечку Socket/Port?

Я пытаюсь выполнить тест производительности сайта, нажав на него с запросами в нескольких потоках. Каждый поток выполняет n раз. (в петле цикла)

Однако у меня проблемы. В частности, WebException ( "Не удается подключиться к удаленному серверу" ) с внутренним исключением:

Операция сокета не может быть выполнена, поскольку система не было достаточного пространства для буфера или потому, что очередь была заполнена 127.0.0.1:52395

Я пытаюсь запустить 100 потоков на 500 итераций на поток.

Изначально я использовал HttpWebRequest в System.Net, чтобы сделать запрос GET на сервер. В настоящее время я использую WebClient, поскольку я предположил, что каждая итерация использует новый сокет (так что со 100 * 500 сокетов за короткий промежуток времени). Я предположил, что WebClient (который создается один раз в потоке) будет использовать только один сокет.

Мне не нужно открывать сразу 50 000 сокетов, так как я хочу отправить запрос GET, получить ответ и закрыть сокет, освободив его для использования в следующей итерации цикла. Я понимаю, что было бы проблемой

Однако даже с помощью WebClient запрашивается куча сокетов, в результате чего создается набор сокетов в режиме TIME_WAIT (проверяется с помощью netstat). Это заставляет другие приложения (например, интернет-браузеры) висеть и перестать функционировать.

Я могу управлять своим тестом с меньшим количеством итераций и/или меньше потоков, так как кажется, что сокеты в конечном итоге выходят из этого состояния TIME_WAIT. Однако это не решение, так как оно недостаточно проверяет возможности веб-сервера.

Вопрос:

Как явным образом закрыть сокет (с клиентской стороны) после каждой итерации потока, чтобы предотвратить состояние TIME_WAIT и исчерпание сокетов?

Код:

Класс, который обертывает HttpRequest

Edit: Wrapped WebClient в использовании, поэтому новый экземпляр, используется и удаляется для каждой итерации. Проблема все еще сохраняется.

  public sealed class HttpGetTest : ITest {
    private readonly string m_url;

    public HttpGetTest( string url ) {          
        m_url = url;
    }

    void ITest.Execute() {
        using (WebClient webClient = new WebClient()){
            using( Stream stream = webClient.OpenRead( m_url ) ) {          
            }
        }
    }
}

Часть моего ThreadWrapperClass, создающая новый поток:

public void Execute() {
    Action Hammer = () => {
        for( int i = 1; i <= m_iterations; i++ ) {
            //Where m_test is an ITest injected through constructor
            m_test.Execute();
        }       
    };
    ThreadStart work = delegate {
        Hammer();
    };
    Thread thread = new Thread( work );
    thread.Start();
}
4b9b3361

Ответ 1

Вы понимаете цель TIME_WAIT? Это период, в течение которого было бы небезопасно повторно использовать порт, потому что потерянные пакеты (которые были успешно ретранслированы) из предыдущей транзакции еще могут быть доставлены в течение этого периода времени.

Вероятно, вы можете настроить его в реестре, но я сомневаюсь, если это разумный следующий шаг.

Мой опыт создания реалистичной нагрузки в тестовой среде оказался очень расстраивающим. Разумеется, запуск вашего тестера нагрузки из localhost ни в коем случае не является реалистичным, и большинство сетевых тестов, которые я сделал с использованием .net http apis, похоже, требуют больше хрюкать на клиенте, чем сам сервер.

Таким образом, лучше перейти на вторую машину для генерации нагрузки на ваш сервер... однако внутреннее оборудование для маршрутизации редко подходит для поддержки где угодно рядом с количеством подключений, которые могут вызвать какую-либо нагрузку на хорошо написанное серверное приложение, поэтому теперь вам нужно обновить свое оборудование для маршрутизации и коммутации!

Наконец, у меня были некоторые действительно странные и неожиданные проблемы с производительностью в API-интерфейсе .net Http. В конце дня все они используют HttpWebRequest для тяжелого подъема. ИМО он нигде не близок, насколько это было возможно. DNS является синхронным, даже если вы вызываете API асинхронно (хотя, если вы запрашиваете только один хост, это не проблема), и после продолжительного использования загрузка ЦП закрадывается, пока клиент не станет ограниченным процессором, а не ограничивается IO. Если вы хотите создать устойчивую и тяжелую нагрузку, любое приложение, зависящее от запросов, зависящее от HttpWebRequest, является IMO фиктивными инвестициями.

В целом, довольно сложная работа и, в конечном счете, то, что может быть доказано только в дикой природе, если только у вас нет наличных денег, чтобы тратить на армаду лучшего оборудования.

[Подсказка: я получил гораздо лучшее исполнение от моего собственного клиента, написанного с использованием async Socket apis и сторонней клиентской библиотеки DNS]

Ответ 2

Q: Как явным образом закрываю сокет..., чтобы предотвратить Состояние TIME_WAIT?

A: Чувак, TIME_WAIT является неотъемлемым и важным! - часть самого TCP/IP!

Вы можете настроить ОС, чтобы уменьшить TIME_WAIT (что может иметь отрицательные последствия).

И вы можете настроить ОС на увеличение #/эфемерных портов:

Здесь ссылка о том, почему существует TIME_WAIT... и почему это хорошая вещь:

Ответ 3

Похоже, вы не заставляете свой WebClient избавляться от ресурсов, которые он выделил. Вы выполняете операцию "Использовать" в возвращаемом потоке, но ваш WebClient все еще имеет ресурсы.

Либо оберните экземпляр WebClient в используемом блоке, либо вручную вызовите его, как только вы закончите чтение из URL.

Попробуйте следующее:

public sealed class HttpGetTest : ITest {
    private readonly string m_url;

    public HttpGetTest( string url ) {
        m_url = url;        
    }

    public void ITest.Execute() {
        using( var m_webClient = new WebClient())
        {
            using( Stream stream = m_webClient.OpenRead( m_url ) ) 
            {

            }
        }
    }
}

Ответ 4

Это не проблема закрытия сокетов или освобождения ресурсов в вашем приложении. TIME _WAIT является таймером TCP-стека в выпущенных сокетах, чтобы предотвратить их повторное использование до тех пор, пока практически невозможно, чтобы все оставшиеся пакеты из предыдущего соединения с этим сокетом не истекли.

В целях тестирования вы можете уменьшить время ожидания от значения по умолчанию (несколько минут, AFAIK) до меньшего значения. Когда серверы проверки нагрузки устанавливаются на шесть секунд,

Это где-то в реестре - вы найдете его, если вы Google.

Найдено:

Изменить задержку TIME_WAIT

Ответ 5

Вам не нужно возиться с TIME_WAIT, чтобы выполнить то, что вы хотите.

Проблема в том, что вы удаляете WebClient каждый раз, когда вы вызываете Execute(). Когда вы это сделаете, вы закрываете соединение сокета с сервером, а порт TCP продолжает работать в течение периода TIME_WAIT.

Лучшим подходом является создание WebClient в конструкторе вашего класса HttpGetTest и повторное использование одного и того же объекта во время теста.

WebClient использует сохранение по умолчанию и повторно использует одно и то же соединение для всех своих запросов, поэтому в вашем случае для этого будет только 100 открытых соединений.