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

Правильный способ остановки 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 - это логическое поле, которое является полем класса. Теперь, когда программа отключается, я хочу, чтобы она перестала слушать клиентов. Настройка прослушивания false не позволит ему подключаться к большему количеству подключений, но поскольку AcceptTcpClient является блокирующим вызовом, он, как минимум, должен сделать следующий клиент и выйти THEN. Есть ли способ заставить его просто вырваться и остановиться, прямо тут и там? Какой эффект вызывает вызов listener.Stop() во время выполнения другого блокирующего вызова?

4b9b3361

Ответ 1

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

  • Использование использования() {} для TcpClient
  • Thread.Abort()
  • TcpListener.Pending()
  • Асинхронная перезапись

Использовать с помощью() {} для TcpClient

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

Thread.Abort()

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

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. обработать клиентский запрос и закрыть его по завершении.

Ответ 2

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

A blocking operation was interrupted by a call to WSACancelBlockingCall

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

Смотрите мой ответ здесь fooobar.com/questions/105184/... TcpListener.Pending() не является хорошим решением

Ответ 7

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

Ответ 8

Некоторые изменения, чтобы сделать поклонника Петра Охлерта идеальным. Потому что до 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();
       }
   }