Чтобы закрыть сокет, не закрывайте() гнездо. Uhmm?

Я знаю, что TIME_WAIT является неотъемлемой частью TCP/IP, но есть много вопросов о SO (и других местах), где в секунду создается несколько сокетов, а сервер заканчивается из эфемерных портов.

То, что я узнал, заключается в том, что при использовании TCPClient (или Socket, если на то пошло), если я вызываю либо методы Close(), либо Dispose(), состояние TCP-сокета изменяется на TIME_WAIT и будет уважать таймаут период до полного закрытия.

Однако, если он просто установил переменную в null, сокет будет полностью закрыт при следующем запуске GC, который, конечно же, может быть принудительно, без какого-либо состояния TIME_WAIT.

Это не имеет большого значения для меня, так как это объект IDisposable не должен GC также вызывать метод Dispose() объекта?

Здесь приведен код PowerShell, который демонстрирует, что (на этой машине нет VS). Я использовал TCPView от Sysinternals для проверки состояния сокетов в реальном времени:

$sockets = @()
0..100 | % {
    $sockets += New-Object System.Net.Sockets.TcpClient
    $sockets[$_].Connect('localhost', 80)
}

Start-Sleep -Seconds 10

$sockets = $null

[GC]::Collect()

Используя этот метод, сокеты никогда не входят в состояние TIME_WAIT. То же самое, если я просто закрою приложение перед ручным вызовом Close() или Dispose()

Может кто-то пролить свет и объяснить, будет ли это хорошей практикой (что, я думаю, люди скажут это не так).

ИЗМЕНИТЬ

Вопрос GC в этом вопросе уже был дан, но мне все еще интересно узнать, почему это повлияет на состояние сокета, поскольку это должно контролироваться ОС, а не .NET.

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

Ответ 1

Это не имеет большого значения для меня, так как это объект IDisposable, не должен ли GC также ссылаться на метод Dispose() объекта?

Dispose pattern, также известный как IDisposable, предоставляет два способа очистки неуправляемого объекта. Метод Dispose обеспечивает прямой и быстрый способ очистки ресурса. Метод finalize, который вызывается сборщиком мусора, является отказоустойчивым способом удостовериться, что неуправляемый ресурс очищается, если другой разработчик, использующий код, забывает вызвать метод Dispose. Это несколько похоже на то, что разработчики С++ забывают вызывать Delete в памяти, выделенной кучей, что приводит к утечкам памяти.

Согласно ссылочной ссылке:

"Хотя финализаторы эффективны в некоторых сценариях очистки, у них есть два существенных недостатка:

  • Финализатор вызывается, когда GC обнаруживает, что объект имеет право на сбор. Это происходит в некоторый неопределенный период времени после того, как ресурс больше не нужен. Задержка между тем, когда разработчик может или хочет освободить ресурс, и время, когда ресурс фактически выпущен финализатором, может быть неприемлемым в программах, которые приобретают множество скудных ресурсов (ресурсы, которые можно легко истощать) или в случаях, когда ресурсы (например, большие неуправляемые буферы памяти).

  • Когда CLR нужно вызвать финализатор, он должен отложить сбор памяти объектов до следующего раунда сборки мусора (финализаторы выполняются между коллекциями). Это означает, что память объектов (и все объекты, на которые она ссылается) не будет выпущена в течение более длительного периода времени.

Используя этот метод, сокеты никогда не входят в состояние TIME_WAIT. То же самое, если я просто закрою приложение, прежде чем вручную вызывать Close() или Dispose()

Может кто-то пролить свет и объяснить, будет ли это хорошей практикой (что, я думаю, люди скажут это не так).

Причина, по которой он задерживается, заключается в том, что код задерживается по умолчанию, чтобы дать приложению некоторое время для обработки любые сообщения в очереди. В соответствии с TcpClient.Close метод doc в MSDN:

"Метод Close помещает экземпляр как расположенный и запрашивает, чтобы связанный Socket закрывал TCP-соединение. Основываясь на свойстве LingerState, TCP-соединение может оставаться открытым в течение некоторого времени после вызова метода Close, когда данные остаются отправленными. Нет уведомления, предоставленного, когда базовое соединение завершило закрытие.

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

Это значение таймаута может быть уменьшено или полностью устранено с помощью следующего кода:

// Allow 1 second to process queued msgs before closing the socket.
LingerOption lingerOption = new LingerOption (true, 1);
tcpClient.LingerState = lingerOption;
tcpClient.Close();

// Close the socket right away without lingering.
LingerOption lingerOption = new LingerOption (true, 0);
tcpClient.LingerState = lingerOption;
tcpClient.Close();

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

Что касается установки ссылки на объект TcpClient на null, рекомендуется использовать метод Close. Когда для ссылки задано значение null, GC заканчивает вызов метода finalize. Метод finalize в конечном итоге вызывает метод Dispose для консолидации кода для очистки неуправляемого ресурса. Таким образом, он будет работать, чтобы закрыть сокет - его просто не рекомендуется.

На мой взгляд, это зависит от того, нужно ли время задержки на время приложения обрабатывать сообщения в очереди. Если бы я был уверен, что мое клиентское приложение обработало все необходимые сообщения, я бы, вероятно, либо дал ему время ожидания 0 секунд или, возможно, 1 секунду, если бы я думал, что это может измениться в будущем.

Для очень занятого клиента и/или слабого оборудования - тогда я могу дать ему больше времени. Для сервера мне пришлось бы сравнивать разные значения под нагрузкой.

Другие полезные ссылки:

Каков правильный способ закрытия и очистки соединения Socket?

Есть ли случаи, когда TcpClient.Close или Socket.Close(0) могут блокировать мой код?

Ответ 2

Класс Socket имеет довольно длинный метод protected virtual void Dispose(bool disposing), который вызывается с true как параметр из .Dispose() и false как параметр из деструктора, который вызывается сборщиком мусора.

Скорее всего, ваш ответ на любые различия в обработке удаления сокетов будет найден в этом методе. Фактически, он ничего не делает на false от деструктора, поэтому у вас есть свое объяснение.

Ответ 3

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

Я хочу сослаться на отличный ответ fooobar.com/questions/16439/... о SO_LINGER на вопрос параметр TCP SO_LINGER (ноль) - когда это необходимо, что может прояснить вам еще больше, и вы сможете принять решение в каждом конкретном случае, который подходит для закрытия используемого сокета.

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

Ответ 4

Я ранил вверх кучу этих ссылок и, наконец, разобрал свои проблемы. Это было действительно полезно.

На стороне сервера я ничего не делаю. получать, отправлять ответ и выходить из обработчика. Я добавил LingerState из 1, но я не думаю, что он что-то делает.

На стороне клиента я использую тот же LingerState, но после получения (я знаю, что все данные есть, поскольку я получаю на основе длины UInt32 в начале пакета), я Close() клиентский сокет, затем установите для объекта Socket значение NULL.

Запуск как клиента, так и сервера настойчиво на том же компьютере, он немедленно очищает все сокеты; Раньше я оставлял тысячи в TIME_WAIT.