Правильный способ остановить TcpListener

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

TcpListener listener = new TcpListener(IPAddress.Any, Port);
System.Console.WriteLine("Server Initialized, listening for incoming connections");
listener.Start();
while (listen)
{
     // Step 0: Client connection
     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

listenПеременная логическая переменная , которая является полем в классе. Теперь, когда программа закрывается, я хочу, чтобы она перестала слушать клиентов. Настройка listen falseбудет препятствовать тому, чтобы он принимал больше соединений, но, поскольку AcceptTcpClientэто блокирующий вызов, он как минимум примет следующий клиент и затем выйдет. Есть ли способ заставить его просто вспыхнуть и остановиться, прямо тогда и там? Какой эффект имеет вызов listener.Stop (), когда выполняется другой блокирующий вызов?

13.12.2008 16:12:27
9 ОТВЕТОВ
РЕШЕНИЕ

Есть 2 предложения, которые я бы сделал, учитывая код, и я предполагаю, что это ваш дизайн. Однако сначала я хотел бы отметить, что вы должны действительно использовать неблокирующие обратные вызовы ввода / вывода при работе с сетями ввода / вывода, такими как файловые системы или системы. Это далеко FAR более эффективный и ваше приложение будет работать намного лучше , хотя их труднее программу. Я кратко расскажу о предлагаемой модификации дизайна в конце.

  1. Используйте Using () {} для TcpClient
  2. Thread.Abort ()
  3. TcpListener.Pending ()
  4. Асинхронное переписывание

Используйте Using () {} для TcpClient

*** Обратите внимание, что вы действительно должны заключить свой вызов TcpClient в блок using () {}, чтобы гарантировать, что методы TcpClient.Dispose () или TcpClient.Close () вызываются даже в случае исключения. Вы также можете поместить это в блок finally блока try {} finally {}.

Thread.Abort ()

Есть 2 вещи, которые я вижу, ты можешь сделать. Во-первых, если вы запустили этот поток TcpListener из другого потока, вы можете просто вызвать метод потока Thread.Abort в потоке, который вызовет исключение threadabortexception в вызове блокировки и поднимется по стеку.

TcpListener.Pending ()

Вторым недорогим решением будет использование метода listener.Pending () для реализации модели опроса. Затем вы должны использовать Thread.Sleep, чтобы «подождать», прежде чем посмотреть, ожидает ли новое соединение. Если у вас есть ожидающее соединение, вы вызываете AcceptTcpClient, и это освобождает ожидающее соединение. Код будет выглядеть примерно так.

while (listen){
     // Step 0: Client connection
     if (!listener.Pending())
     {
          Thread.Sleep(500); // choose a number (in milliseconds) that makes sense
          continue; // skip to next iteration of loop
     }

     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

Асинхронный переписать

Наконец, я бы порекомендовал вам действительно перейти на неблокирующую методологию для вашего приложения. Под прикрытием платформа будет использовать перекрывающиеся порты ввода-вывода и завершения ввода-вывода для реализации неблокирующего ввода-вывода из ваших асинхронных вызовов. Это тоже не очень сложно, просто нужно немного подумать о вашем коде.

Обычно вы начинаете свой код с метода BeginAcceptTcpClient и отслеживает возвращаемый вами IAsyncResult. Вы указываете на метод, который отвечает за получение TcpClient и передачу его НЕ в новый поток, а в поток из ThreadPool.QueueUserWorkerItem, чтобы вы не раскручивали и не закрывали новый поток для каждого клиентского запроса (обратите внимание, что может потребоваться использовать ваш собственный пул потоков, если у вас есть особенно долгоживущие запросы, потому что пул потоков используется совместно, и если вы монополизируете все потоки, другие части вашего приложения, реализованные системой, могут оказаться голодными). Как только метод слушателя запускает ваш новый TcpClient для его собственного запроса ThreadPool, он снова вызывает BeginAcceptTcpClient и направляет делегата обратно на себя.

Фактически вы просто разбиваете свой текущий метод на 3 различных метода, которые затем будут вызываться различными частями. 1. загрузить все, 2. стать целью для вызова EndAcceptTcpClient, запустить TcpClient в свой собственный поток и затем вызвать себя снова, 3. обработать запрос клиента и закрыть его, когда закончите.

60
30.05.2017 13:35:13
Я просто прочитал ваш ответ, но для меня не ясно, как правильно реализовать то, что вы описали. Можете ли вы предоставить код, чтобы прояснить это. Спасибо
Jehof 21.02.2011 13:38:38
В моих тестах казалось, что Thread.Abort не работает должным образом, когда поток заблокирован в ожидании соединения, см. Мой вопрос здесь: stackoverflow.com/questions/5778267/…
Ralph Shillington 25.04.2011 12:21:49
Вы уверены, что можно закрыть клиент, пока clientThread использует client.GetStream ()? Если я уйду в вызове client.Close (), я не смогу создать StreamReader для NetworkStream в методе HandleConnection.
comecme 17.05.2011 20:21:38
Почему это помечено как решение? Первые 3 пункта кажутся неправильными по той или иной причине, а последний может использовать некоторый код.
IvanP 19.05.2013 15:52:29
TcpClient не реализует IDisposable. Этот ответ имеет несколько плохих моментов и не должен быть принят.
xxbbcc 20.11.2014 17:19:40

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

-1
13.12.2008 16:28:50

Не используйте петлю. Вместо этого вызовите BeginAcceptTcpClient () без цикла. В обратном вызове просто выполните другой вызов BeginAcceptTcpClient (), если ваш флаг прослушивания все еще установлен.

Чтобы остановить слушателя, поскольку вы не заблокированы, ваш код может просто вызвать Close () для него.

3
13.12.2008 18:00:04

Сокеты предоставляют мощные асинхронные возможности. Взгляните на использование асинхронного сокета сервера

Вот пара замечаний по коду.

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

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

 clientThread.Start(client.GetStream());
 client.Close();

TcpClient.Stop () закрывает основной сокет. TcpCliet.AcceptTcpClient () использует метод Socket.Accept () для нижележащего сокета, который вызывает закрытие SocketException после его закрытия. Вы можете позвонить из другой ветки.

В любом случае я рекомендую асинхронные розетки.

3
13.12.2008 20:19:39
В документации конкретно говорится, что закрытие TcpClient НЕ закрывает основной поток.
Christian P. 13.12.2008 21:20:46
Да, в документации так сказано, но реализация закрывает поток ... Проверьте это TcpClient tcpClient = new TcpClient (); tcpClient.Connect ("www.google.com", 80); NetworkStream networkStream = tcpClient.GetStream (); TcpClient.Close (); byte [] bytes = новый байт [1024]; networkStream.Read (байты, 0, 1024);
Dzmitry Huba 15.12.2008 21:41:27
Там нет TcpClient.Stop()метода.
Qwertie 26.04.2011 21:12:33

Просто чтобы добавить еще больше причин для использования асинхронного подхода, я уверен, что Thread.Abort не будет работать, потому что вызов заблокирован в стеке TCP уровня ОС.

Также ... если вы вызываете BeginAcceptTCPClient в обратном вызове для прослушивания каждого соединения, кроме первого, будьте осторожны, чтобы убедиться, что поток, выполнивший начальный BeginAccept, не завершится, иначе слушатель будет автоматически удален платформой. Я полагаю, что это особенность, но на практике это очень раздражает. В настольных приложениях это обычно не проблема, но в Интернете вы можете использовать пул потоков, поскольку эти потоки никогда не завершаются.

1
5.03.2009 18:51:23

Некоторые изменения, чтобы сделать Питера Олерта идеальным. Потому что раньше, чем за 500 миллисекунд, слушатель снова хлопнул. Чтобы исправить это:

    while (listen)     
    {
       // Step 0: Client connection     
       if (!listener.Pending())     
       {
           Thread.Sleep(500); // choose a number (in milliseconds) that makes sense
           continue; // skip to next iteration of loop
       }
       else // Enter here only if have pending clients
       {
          TcpClient client = listener.AcceptTcpClient();
          Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
          clientThread.Start(client.GetStream());
          client.Close();
       }
   }
-2
12.06.2009 12:22:54
Элимар, единственное изменение, которое я вижу между этим кодом и моим, - это блок else. Поскольку continue в основном будет переходить к началу цикла while, остальное не обязательно. Можно утверждать о достоинствах чистоты или стиля кодирования, но это не обязательно. Я что-то упустил?
Peter Oehlert 23.07.2010 01:25:23

listener.Server.Close() из другого потока прерывает блокирующий вызов.

A blocking operation was interrupted by a call to WSACancelBlockingCall
49
9.11.2011 18:45:44
Хотя мне нравится ответ Питера Элерта за полноту и лучшие практики, у меня есть простое задание, которое не оправдывает сложность полной асинхронной перезаписи, и я обнаружил, что Thread.Abort () не работает для меня. Это, однако, помогло мне быстро и эффективно.
Matt Connolly 27.05.2011 05:23:58
Я предпочитаю этот ответ, так как если вы собираетесь переписать его, вы, вероятно, должны использовать WCF, он решает так много проблем, которые у меня были с сокетами TCP / IP в .net
NibblyPig 12.12.2011 14:50:57
Вы могли бы даже добавить несколько строк. В вашем обработчике ловушки SocketException. if ((e.SocketErrorCode == SocketError.Interrupted)) Console.WriteLine("A blocking listen has been cancelled");Таким образом, вы уверены, что сделали завершение, пока он ожидал подключения клиента.
WagoL 9.12.2015 08:57:54
Почему бы не делать listener.Stop (); ?
John 22.08.2016 10:02:44
Я согласен с @John; зачем звонить listener.Server.Close()а не listener.Stop()? Если вы посмотрите на Ссылочный источник дляTcpListener.Stop() , делает ли это эквивалент listener.Server.Close()для вас (среди прочего, как очистка старых запросов на подключение).
jrh 25.10.2017 14:23:03

Смотрите мой ответ здесь https://stackoverflow.com/a/17816763/2548170 TcpListener.Pending() не является хорошим решением

2
23.05.2017 12:17:33
Отметьте вопрос как дубликат или предоставьте полный ответ.
Denise Skidmore 19.12.2014 19:03:52

Как уже упоминалось выше, используйте вместо этого BeginAcceptTcpClient, асинхронным управлением намного проще.

Вот пример кода:

        ServerSocket = new TcpListener(endpoint);
        try
        {
            ServerSocket.Start();
            ServerSocket.BeginAcceptTcpClient(OnClientConnect, null);
            ServerStarted = true;

            Console.WriteLine("Server has successfully started.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Server was unable to start : {ex.Message}");
            return false;
        }
0
18.10.2018 16:57:28