Правильный способ остановки 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() во время выполнения другого блокирующего вызова?
Ответы
Ответ 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();
}
}