c# - socket - tcp visual studio
La forma correcta de detener TcpListener (8)
Actualmente estoy usando TcpListener para abordar las conexiones entrantes, cada una de las cuales tiene un hilo para manejar la comunicación y luego cerrar esa única conexión. El código se ve de la siguiente manera:
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();
}
La variable de listen
es un booleano que es un campo en la clase. Ahora, cuando el programa se apaga, quiero que deje de escuchar a los clientes. Si se establece listen to false
evitará que tome más conexiones, pero como AcceptTcpClient
es una llamada de bloqueo, como mínimo tomará el próximo cliente y luego THEN saldrá. ¿Hay alguna forma de forzarlo a que simplemente explote y pare en ese mismo momento? ¿Qué efecto tiene la llamada listener.Stop () mientras se está ejecutando la otra llamada de bloqueo?
Algunos cambios para hacer que Peter Oehlert sea perfecto. Porque antes de los 500 milisegundos, el oyente está bloqueando nuevamente. Para corregir esto:
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();
}
}
Hay dos sugerencias que haré dado el código y supongo que es su diseño. Sin embargo, me gustaría señalar primero que realmente debería utilizar devoluciones de E / S sin bloqueo cuando se trabaja con E / S como red o sistemas de archivos. Es mucho más eficiente y tu aplicación funcionará mucho mejor, aunque son más difíciles de programar. Voy a cubrir brevemente una modificación de diseño sugerida al final.
- Use Using () {} para TcpClient
- Thread.Abort ()
- TcpListener.Pending ()
- Reescritura asincrónica
Use Using () {} para TcpClient
*** Tenga en cuenta que realmente debe adjuntar su llamada a TcpClient en un bloque using () {} para asegurarse de que se invocan los métodos TcpClient.Dispose () o TcpClient.Close () incluso en el caso de una excepción. Alternativamente, puedes poner esto en el bloque finally de un bloque try {} finally {}.
Thread.Abort ()
Hay 2 cosas que veo que podrías hacer. 1 es que si ha iniciado este hilo TcpListener desde otro, simplemente puede llamar al método Thread.Abort instance en el hilo, lo que provocará que se ejecute una amenaza threadaborte dentro de la llamada de bloqueo y suba la pila.
TcpListener.Pending ()
El segundo arreglo de bajo costo sería usar el método listener () para implementar un modelo de sondeo. Luego usaría un Thread.Sleep para "esperar" antes de ver si hay una nueva conexión pendiente. Una vez que tenga una conexión pendiente, llamaría a AcceptTcpClient y eso liberaría la conexión pendiente. El código se vería algo como esto.
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();
}
Reescritura asincrónica
Finalmente, recomendaría que realmente cambie a una metodología sin bloqueo para su aplicación. Bajo las cubiertas, el marco utilizará I / O superpuestos y puertos de finalización de E / S para implementar E / S no bloqueantes de sus llamadas asíncronas. Tampoco es terriblemente difícil, solo requiere pensar en tu código de forma un poco diferente.
Básicamente, iniciaría su código con el método BeginAcceptTcpClient y realizar un seguimiento del IAsyncResult que le devolvieron. Señala eso en un método que es responsable de obtener el TcpClient y pasarlo NO a un nuevo hilo, sino a un hilo fuera del ThreadPool.QueueUserWorkerItem, por lo que no está girando y cerrando un nuevo hilo para cada solicitud del cliente (Tenga en cuenta que puede necesitar usar su propio grupo de subprocesos si tiene solicitudes de larga duración porque el grupo de subprocesos se comparte y si monopoliza todos los subprocesos, otras partes de la aplicación implementadas por el sistema pueden quedar anonadas). Una vez que el método de oyente ha iniciado su nuevo TcpClient para su propia solicitud de ThreadPool llama a BeginAcceptTcpClient nuevamente y apunta al delegado de nuevo hacia sí mismo.
De hecho, estás dividiendo tu método actual en 3 métodos diferentes que recibirán llamadas de las distintas partes. 1. para arrancar todo, 2. ser el objetivo para llamar a EndAcceptTcpClient, iniciar el TcpClient en su propio hilo y luego volver a llamarse a sí mismo, 3. para procesar la solicitud del cliente y cerrarla cuando haya terminado.
Los zócalos proporcionan poderosas capacidades asíncronas. Eche un vistazo al uso de un socket de servidor asíncrono
Aquí hay un par de notas sobre el código.
El uso de subprocesos creados manualmente en este caso puede ser una sobrecarga.
El código siguiente está sujeto a las condiciones de la carrera: TcpClient.Close () cierra la transmisión de red que recibes a través de TcpClient.GetStream (). Considere cerrar al cliente donde definitivamente puede decir que ya no es necesario.
clientThread.Start(client.GetStream());
client.Close();
TcpClient.Stop () cierra el socket subyacente. TcpCliet.AcceptTcpClient () usa el método Socket.Accept () en el socket subyacente que arrojará SocketException una vez que se cierre. Puedes llamarlo desde un hilo diferente.
De todos modos recomiendo conectores asíncronos.
No use un bucle. En su lugar, llame a BeginAcceptTcpClient () sin un bucle. En la devolución de llamada, solo emita otra llamada a BeginAcceptTcpClient (), si su bandera de escucha todavía está configurada.
Para detener al oyente, ya que no ha bloqueado, su código puede simplemente llamar a Close () en él.
Probablemente sea mejor usar la función asincrónica BeginAcceptTcpClient . Luego puede simplemente llamar a Stop () en el oyente ya que no estará bloqueando.
Solo para agregar más razones para usar el enfoque asincrónico, estoy bastante seguro de que Thread.Abort no funcionará porque la llamada está bloqueada en la pila TCP del nivel del sistema operativo.
Además ... si está llamando a BeginAcceptTCPClient en la devolución de llamada para escuchar todas las conexiones, excepto la primera, asegúrese de que el subproceso que ejecutó el BeginAccept inicial no finalice o de lo contrario el oyente será eliminado automáticamente por el marco. Supongo que es una característica, pero en la práctica es muy molesto. En las aplicaciones de escritorio, generalmente no es un problema, pero en la web es posible que desee utilizar el grupo de subprocesos, ya que esos subprocesos nunca terminan realmente.
Ver mi respuesta aquí https://.com/a/17816763/2548170 TcpListener.Pending()
no es una buena solución
listener.Server.Close()
de otro hilo rompe la llamada de bloqueo.
A blocking operation was interrupted by a call to WSACancelBlockingCall