Pregunta

¿Cuál es la forma normal en que las personas que escriben código de red en Delphi utilizan la E/S de socket asíncrona superpuesta al estilo de Windows?

Aquí está mi investigación previa sobre esta pregunta:

El India Los componentes parecen completamente sincrónicos.Por otro lado, aunque la unidad ScktComp usa WSAAsyncSelect, básicamente solo asincroniza una aplicación de socket multiplexada estilo BSD.Lo arrojan en una devolución de llamada de un solo evento, como si acabara de regresar de select() en un bucle, y tuviera que realizar toda la navegación de la máquina de estado usted mismo.

La situación de .NET es considerablemente mejor, con Socket.BeginRead/Socket.EndRead, donde la continuación se pasa directamente a Socket.BeginRead, y ahí es donde se retoma.Una continuación codificada como cierre obviamente tiene todo el contexto que necesita y más.

¿Fue útil?

Solución

Otros consejos

Descubrí que Indy, si bien es un concepto más simple al principio, es incómodo de administrar debido a la necesidad de eliminar sockets para liberar subprocesos al finalizar la aplicación.Además, la biblioteca Indy dejó de funcionar después de una actualización del parche del sistema operativo.ScktComp funciona bien para mi aplicación.

@Roddy: los enchufes síncronos no son lo que busco.Grabar un hilo completo en aras de una conexión posiblemente de larga duración significa que limita la cantidad de conexiones simultáneas a la cantidad de hilos que su proceso puede contener.Dado que los subprocesos utilizan muchos recursos (espacio de direcciones de pila reservado, memoria de pila comprometida y transiciones del núcleo para cambios de contexto), no se escalan cuando se necesita admitir cientos de conexiones, y mucho menos miles o más.

¿Cuál es la forma normal en que las personas que escriben el código de red en Delphi usan la E/S de enchufe asíncrono de estilo Windows?

Bueno, Indy ha sido la biblioteca "estándar" para E/S de sockets desde hace mucho tiempo, y se basa en el bloqueo de sockets.Esto significa que si desea un comportamiento asincrónico, utilice subprocesos adicionales para conectar/leer/escribir datos.En mi opinión, esto es en realidad una gran ventaja, ya que no hay necesidad de administrar ningún tipo de navegación de la máquina de estado, ni preocuparse por los procesos de devolución de llamada o cosas similares.Encuentro que la lógica de mi hilo de 'lectura' está menos abarrotada y mucho más portátil de lo que permitirían los sockets sin bloqueo.

India 9 ha sido en su mayoría a prueba de bombas, rápido y confiable para nosotros.Sin embargo, el paso a Indy 10 para Tiburon me está causando un poco de preocupación.

@Miguel:"...la necesidad de eliminar sockets para liberar hilos...".

Esto hizo ir "¿eh?" Hasta que recordé que nuestra biblioteca de subprocesos utiliza una técnica basada en excepciones para matar hilos de 'espera' de manera segura.Llamamos Usuario de colaAPC para poner en cola una función que genera una excepción de C++ (NO derivada de la clase Exception) que solo debe ser detectada por nuestro procedimiento contenedor de subprocesos.Se llama a todos los destructores para que todos los subprocesos terminen limpiamente y ordenados al salir.

"Los enchufes sincrónicos no son lo que busco."

Entendido, pero creo que en ese caso la respuesta a su pregunta original es que simplemente no existe un Delphi. modismo para IO de socket asíncrono porque en realidad es un requisito altamente especializado y poco común.

Como tema adicional, estos enlaces pueden resultarle interesantes.Ambos son un poco viejos y más *nxy que Windows.La segunda implica que, en el entorno adecuado, los subprocesos podrían no ser tan malos como cree.

El problema del C10K

Por qué los eventos son una mala idea (para servidores de alta concurrencia)

@Chris Miller: lo que ha dicho en su respuesta es objetivamente inexacto.

El estilo de mensaje asíncrono de Windows, disponible a través de WSAAsyncSelect, es en gran medida una solución alternativa a la falta de un modelo de subprocesamiento adecuado en los días de Win 3.x.

.NET Begin/End, sin embargo, es no usando hilos adicionales.En su lugar, utiliza E/S superpuestas, utilizando el argumento adicional en WSASend/WSARecv, específicamente la rutina de finalización superpuesta, para especificar la continuación.

Esto significa que el estilo .NET aprovecha el soporte de E/S asíncronas del sistema operativo Windows para evitar quemar un hilo bloqueándolo en un enchufe.

Dado que los subprocesos son generalmente costosos (a menos que especifique un tamaño de pila muy pequeño para CreateThread), tener subprocesos bloqueados en sockets le impedirá escalar a 10,000 conexiones simultáneas.

Por eso es importante utilizar E/S asíncronas si desea escalar, y también por qué .NET es no, repito, es no, simplemente "usando subprocesos, [...] simplemente administrados por el Framework".

@Roddy: ya leí los enlaces que señala, a ambos se hace referencia en la presentación de Paul Tyma "Miles de subprocesos y bloqueo de E/S: la antigua forma de escribir servidores Java es nueva otra vez".

Sin embargo, algunas de las cosas que no necesariamente destacan de la presentación de Paul son que especificó -Xss:48k a la JVM al inicio y que asume que la implementación NIO de la JVM es eficiente para que sea válida. comparación.

Indy lo hace no especifique un tamaño de pila igualmente reducido y estrictamente restringido.No hay llamadas a BeginThread (la rutina de creación de subprocesos RTL de Delphi, que debe usar para tales situaciones) o CreateThread (la llamada sin formato de WinAPI) en el código base de Indy.

El tamaño de pila predeterminado se almacena en el PE, y para el compilador Delphi, el valor predeterminado es 1 MB de espacio de direcciones reservado (el sistema operativo asigna el espacio página por página en fragmentos de 4K;de hecho, el compilador necesita generar código para tocar páginas si hay >4K de locales en una función, porque la extensión está controlada por fallas de página, pero solo para la página más baja (guardia) de la pila).Eso significa que se quedará sin espacio de direcciones después de un máximo de 2000 subprocesos simultáneos manejando conexiones.

Ahora, puede cambiar el tamaño de pila predeterminado en el PE usando la directiva {$M minStackSize [,maxStackSize]}, pero eso afectará todo hilos, incluido el hilo principal.Espero que no hagas mucha recursividad, porque 48K o (similar) no es mucho espacio.

Ahora bien, no estoy 100% seguro de si Paul tiene razón acerca del mal rendimiento de la E/S asíncrona para Windows en particular; tendría que medirlo para estar seguro.Lo que sí sé, sin embargo, es que los argumentos acerca de que la programación por subprocesos es más fácil que la programación asíncrona basada en eventos presentan un problema. falsa dicotomía.

El código asíncrono no necesidad estar basado en eventos;puede estar basado en continuación, como en .NET, y si especifica un cierre como continuación, obtendrá el estado mantenido de forma gratuita.Además, un compilador puede hacer mecánica la conversión de código de estilo de subproceso lineal a código asíncrono de estilo de paso continuo (la transformación CPS es mecánica), por lo que tampoco tiene por qué haber ningún costo en la claridad del código.

Hay componentes de socket IOCP (puertos de finalización) gratuitos: http://www.torry.net/authorsmore.php?id=7131 (código fuente incluido)

"Por Naberegnyh Sergey N..Servidor de socket de alto rendimiento basado en el puerto de finalización de Windows y con el uso de extensiones de socket de Windows.IPv6 admitido."

Lo encontré mientras buscaba mejores componentes/biblioteca para rediseñar mi pequeño servidor de mensajería instantánea.Aún no lo he probado pero parece bien codificado como primera impresión.

Indy utiliza sockets sincrónicos porque es una forma más sencilla de programación.El bloqueo de socket asíncrono fue algo agregado a la pila de winsock en los días de Windows 3.x.Windows 3.x no admitía subprocesos y allí no se podía realizar E/S de socket sin subprocesos.Para obtener información adicional sobre por qué Indy usa el modelo de bloqueo, consulte Este artículo.

Las llamadas .NET Socket.BeginRead/EndRead utilizan subprocesos, simplemente las administra Framework en lugar de usted.

@Roddy, Indy 10 se incluye con Delphi desde Delphi 2006.Descubrí que migrar de Indy 9 a Indy 10 es una tarea sencilla.

Con las clases ScktComp, necesita utilizar un servidor ThreadBlocking en lugar de un tipo de servidor NonBlocking.Utilice el evento OnGetThread para transferir el parámetro ClientSocket a un nuevo hilo de su diseño.Una vez que haya creado una instancia heredada de TServerClientThread, creará una instancia de TWinSocketStream (dentro del hilo) que podrá usar para leer y escribir en el socket.Este método le evita intentar procesar datos en el controlador de eventos.Estos subprocesos podrían existir solo durante el breve período necesario para leer o escribir, o permanecer durante el tiempo necesario para ser reutilizados.

El tema de escribir un servidor de socket es bastante amplio.Hay muchas técnicas y prácticas que puede optar por implementar.El método de lectura y escritura en el mismo socket en TServerClientThread es sencillo y adecuado para aplicaciones simples.Si necesita un modelo para alta disponibilidad y alta concurrencia, entonces debe buscar patrones como el patrón Proactor.

¡Buena suerte!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top