Правильный способ остановить TcpListener - PullRequest
57 голосов
/ 13 декабря 2008

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

Ответы [ 9 ]

57 голосов
/ 13 декабря 2008

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

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

Использовать использование () {} для 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 и передачу его NOT в новый поток, но в поток из ThreadPool.QueueUserWorkerItem, чтобы вы не раскручивали и не закрывали новый поток для каждый клиентский запрос (обратите внимание, что вам может понадобиться использовать свой собственный пул потоков, если у вас особенно долгоживущие запросы, потому что пул потоков используется совместно, и если вы монополизируете все потоки, другие части вашего приложения, реализованные системой, могут оказаться неэффективными). Как только метод слушателя запускает ваш новый TcpClient для его собственного запроса ThreadPool, он снова вызывает BeginAcceptTcpClient и направляет делегата обратно на себя.

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

47 голосов
/ 27 октября 2009

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

A blocking operation was interrupted by a call to WSACancelBlockingCall
3 голосов
/ 13 декабря 2008

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

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

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

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

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

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

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

2 голосов
/ 23 июля 2013

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

2 голосов
/ 13 декабря 2008

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

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

1 голос
/ 05 марта 2009

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

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

0 голосов
/ 18 октября 2018

Как уже упоминалось выше, используйте вместо него 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 голосов
/ 12 июня 2009

Некоторые изменения, которые сделают Питера Олерта идеальным. Потому что раньше, чем за 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();
       }
   }
0 голосов
/ 13 декабря 2008

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

...