2016-11-24 3 views
10

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

То, что я узнал, что при использовании 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()

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

EDIT

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

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

+6

«не должен ли GC также ссылаться на метод Dispose() объекта?» GC * never * вызывает 'Dispose'. Он вызывает только финализатор класса, если он есть. Обычно 'Dispose()' вызывается из финализатора, но это повторяется: 'Dispose' предназначен только для' use'. GC вообще не заботится об этом. –

+0

Это отличное объяснение участия GC в этом, THX! – cogumel0

+1

Обратите внимание, что ваш код не дает времени GC для фактического сбора сокетов, поскольку у них есть финализаторы. Вам нужно сделать 'GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); 'заставить это. Ваш вызов 'GC.Collect();' просто помещает сокеты в очередь финализатора (если это) - и финализаторы сокетов могут занимать много времени (часто убивая ваш процесс). Сокеты не находятся в TIME_WAIT, скорее всего, потому, что они еще не были закрыты *. То же самое происходит, когда вы убиваете процесс - на самом деле нетривиально отключить сокет TCP, даже для ОС. Перечислите все порты, а не только TIME_WAIT. – Luaan

ответ

1

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

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

+0

Это объясняет сторону GC, но, учитывая, что сокет переходит в TIME_WAIT или нет, управляется самой ОС (а не .NET), почему бы назвать деструктор vs '.Dispose()' иметь какие-либо влияние на то, что ОС делает с сокетом? Мое понимание TIME_WAIT заключается в том, что * все сокеты * должны проходить через это состояние для обнаружения повторной передачи и т. Д. Все еще не понимаю, почему есть способ обойти это (и будет ли это «хорошей практикой» использовать его). – cogumel0

7

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

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

По ссылочной ссылке:

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

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

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

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

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

Причина, по которой для этого требуется время выключение происходит из-за того, что код lingers by default предоставляет приложению некоторое время для обработки любых сообщений в очереди. Согласно методу TcpClient.Close doc на MSDN:

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

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

Это значение тайм-аута может быть уменьшено или полностью устранено following code:

// 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 в нуль, рекомендуемый подход заключается в вызове метода Close , Когда для ссылки задано значение null, GC заканчивает вызов метода finalize. Метод finalize в конечном итоге вызывает метод Dispose для консолидации кода для очистки неуправляемого ресурса. Таким образом, он будет работать, чтобы закрыть сокет - его просто не рекомендуется.

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

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

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

What is the proper way of closing and cleaning up a Socket connection?

Are there any cases when TcpClient.Close or Socket.Close(0) could block my code?

+0

Хороший ответ, но я до сих пор не понимаю, почему код метода 'Close()' должен отличаться от кода 'Dispose()'. Я имею в виду, * почему * сокет не входит в 'TIME_WAIT', если он удаляется без закрытия? – cogumel0

+0

@ cogumel0 Вы сказали в своем вопросе: «Я выяснил, что при использовании TCPClient (или Socket, если на то пошло), если я вызываю методы Close() или Dispose(), состояние TCP сокета изменяется на TIME_WAIT и будет соблюдайте период ожидания до полного закрытия ». Таким образом, когда объект имеет значение null, он не переходит в состояние TIME_WAIT. Я подозреваю, что это связано с тем, что финализатор понимает, что уже не действительный объект для обработки сообщений в очереди. Таким образом, не было бы смысла задерживаться. Он просто закрывает сокет в этой точке. –

+0

@ cogumel0 Вы пытались использовать LingerOption, чтобы заставить метод Close не задерживаться (или перейти в состояние TIME_WAIT)? Если по какой-то причине это не работает, тогда у вас может не быть другого выбора, кроме как установить объект в null. –

1

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

Я хочу обратиться к большому ответу https://stackoverflow.com/a/13088864/2138959 о SO_LINGER на вопрос TCP option SO_LINGER (zero) - when it's required, которые могли бы разъяснить вещи еще к вам и так, что вы можете сделать ваше решение в каждом конкретном случае, какой подход для закрытия сокета для использования.

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

0

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

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

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

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