Colabora |
Emulación Multicast en TCPCómo crear un servidor de envíos multicast
Fecha: 03/Mar/2008 (03-03-08)
|
IntroducciónEn este articulo se propone una pequeña solución al problema del envío simultaneo a muchos clientes por tcp, es por ello que implementaremos unas funciones para solventar de alguna forma dicho problema. El articulo se dividirá en tres apartados: La idea con la clase que cree es la de un servidor que pudiera enviar datos multicast para una aplicación que tenía requisito de Tiempo Real. Como anexo también pondré la funciones necesaria para recibir un fichero del servidor, esta función la tendrá que tener implementada el cliente si quiere poder recibir fichero del servidor. El cliente no esta puesto porque se implementa como cualquier cliente normal exceptuando la función de recibir fichero, dicha función es la típica que se asocia a el proceso de revivir_datos. Contenido 11-Explicación de la solución propuesta: Todos no hemos encontrado con el problema del envío multicast en tcp, y por tanto hemos estado obligados a utilizar el protocolo udp para nuestra tareas, pero algunas veces utilizar el protocolo udp no es solución debido a que puede ser que nuestro sistema tenga que respetar cierto requisitos , como tiempo real o seguridad en la transferencia de datos. Es por ello que se me ocurrió utilizar procesos para los envíos multicast, en un principio esta solución conlleva dos preguntas:¿cuanto de grandes son los datos a enviar?¿y cuanto se debe de respetar el Tiempo Real? Hay dos soluciones: Por un lado podemos crear un proceso de envío para cada cliente que tengamos conectado y que dicho proceso se encargue de enviar los datos, pero claro esta que si los datos son muy largos y se va a tardar mucho tiempo en enviar (como queremos respetar el Tiempo Real, mucho tiempo es un par de segundos) deberemos de crear nuestro planificador que divida el tiempo de uso de CPU o el tiempo de ejecución entre todos los procesos, para que de esta forma un proceso no utilice mucho tiempo de ejecución de forma continua. Para ello podemos implementar planificadores que ya existen como Round-Robbin o EDF. Así que, como esta forma sería mas complicada de implementar sugiero que dividamos los datos en paquetes mas pequeños para de esta forma no tener que implementar un Planificador. Por ello yo implemente la segunda opción, esta opción trata de enviar siempre mensajes de tamaño pequeño, con ello ya no tengo que crear un planificador, sino que solo tengo que crear una tarea encargada de enviar el dato a todos los clientes, esta tarea se llamara ENVIO_MULTICAST. Dicho hilo o tarea se creará cada vez que queramos enviar un datos multicast y se destruirá cada vez que finalice de enviar dichos datos a todos los clientes, es por ello que lo mas importante de el proceso es que bloquee el acceso a la variable que hace referencia al socket para que no se produzcan problemas de concurrencia. Un problema de concurrencia podría producirse si por ejemplo estamos enviando datos en multicast y el cliente decide también transferir cierta información a un cliente de forma independiente, en este caso podría ocurrir que el proceso principal del programa y el proceso de envio_multicast quisieran acceder al mismo tiempo al socket, es por ello que se bloqueará el acceso al socket cada vez que se haga uso de el, esto lo haremos haciendo uso de semáforos o de synclock. Tras explicar el problema de la concurrencia solo queda solucionar el problema de las prioridades de los procesos que tenemos, por ahora solo tenemos el Procesos Principal, Proceso Escucha de conexiones Entrantes en el caso de ser un servidor, Proceso de recepción de datos para cada conexión establecida, y el Proceso de Envio Multicast. Puesto que nuestro requisito es que no perdamos Tiempo Real, le daremos prioridad Normal o Por encima de lo Normal al Proceso de Enviomulticast, y los demás tendrán por debajo de lo normal. De todas formas el tema de las prioridades es independiente para los requisitos de cada sistema, por ejemplo en el caso de un chat en plan IRC lo mejor es que el proceso multicast tuviera por encima de lo normal y que luego los procesos de recepción de datos de cada uno de los canales tuviera prioridad normal, pero claro, esto es olo una idea. Como pequeña aclaración diré que cuando creamos un hilo podemos asignarle la prioridad con el propiedad "priority", de todas formas en otros artículos viene mucho mejor explicado la utilización de hilos o Thread. También hay que tener en cuenta que si nuestra aplicación es un servidor el cual solo realiza tareas de Intercomunicar mensajes entre los clientes, las prioridades del proceso de multicast puede ser por encima de lo normal, pero tened cuidad con el proceso de escucha de datos de los clientes, si le ponéis por encima de lo normal y dicho proceso recibe datos continuamente el acceso a la interfaz grafica de programa será complicada, puesto que un formulario principal tiene prioridad normal, y por tanto si un proceso con prioridad mayor que ella se quiere ejecutar, el formulario principal no tendrá tiempo de ejecución y por tanto no podremos interactuar mucho con el. Tened en cuenta que un formulario se ejecuta con eventos que van activándose, por lo que si un proceso de prioridad superior a el esta ejecutándose siempre, será muy complicado de el formulario atienda los eventos que se produzcan. Una vez comentando los aspectos más importantes a la hora de implementar un servidor multicast solo me queda comentar las estructuras de datos utilizadas para mi clase. Cree una estructura de datos que contiene la información de cada cliente:Ip y puerto, Socket por el que envío y recibo datos, Un socket auxiliar para conexiones independientes, y los procesos encargados de controlar la recepción de datos. Lo mas importante a explicar en referencia a lo anteriormente comentado es la cuestión de tener otro socket en escucha para cada cliente, la razón es poder realizar envíos punto-a -punto de archivos o datos mas grandes sin que interrumpa en el envío de datos en Tiempo Real. En mi caso en particular como el envío de fichero o de datos mas grandes por parte del servidor no se realizaba muy a menudo pues no me supuso un problema. Sin embargo la idea es muy bueno para un gran problema que surge en algunos sistemas. ¿que ocurre si un cliente se conecta a un sistema que continuamente envía datos en Tiempo Real, pero además al ser nuestra primera conexión necesitamos los datos de una hora anterior, si utilizamos el mismo canal, el requisito de tiempo real se perdería, por lo que la solución a priori más efectiva es la de crear un canal independiente. Esta claro que el único problema que se os puede plantear viene del otro lado de la comunicación, el cliente. ¿que ocurriría si por el canal de Tiempo Real recibieran datos que solo pueden procesarse con la información que se tiene de un minuto anterior y sin embargo como me acabo de conectar y estoy actualizándome , no tengo dichos datos? Para este caso lo mejor es tener un buffer que almacene los datos que se reciben por el canal de Tiempo Real durante la actualización, y una vez acabada procese el buffer. Nota: El código:Las dos primeras funciones son las utilizadas para el envío multicast, como veréis no son nada del otro mundo. Por supuesto tener en cuenta que el código de la clase es mas grande. Public Sub EnviarDatos_multicast(ByVal Datos As String) Try If Not (Me.proceso_envio Is Nothing) Then ''esperamos por si todabia se estan enviando datos a todas las conexiones Me.proceso_envio.Join() Me.proceso_envio = New Thread(New ParameterizedThreadStart(AddressOf envio_multicast)) Me.proceso_envio.Priority = ThreadPriority.Normal Me.proceso_envio.Start(Datos) Else Me.proceso_envio = New Thread(New ParameterizedThreadStart(AddressOf envio_multicast)) Me.proceso_envio.Priority = ThreadPriority.Normal Me.proceso_envio.Start(Datos) End If Catch ex As Exception Throw New Exception("Error en EnvioDatos . CE:" + ex.Message) End Try End Sub Private Sub envio_multicast(ByVal d As Object) Try Dim datos As String = d SyncLock Me.info_clientes.SyncRoot For Each c As InfoDeUnCliente In Me.info_clientes.Values If c.Socket_Datos.Connected Then Try c.Socket_Datos.Send(Encoding.Default.GetBytes(datos)) Catch e As SocketException RaiseEvent Mensages_error(c.Identificador.ToString, _ "Error en el envio multicast.CE:" & e.ErrorCode) End Try Else End If Next End SyncLock Catch ex As Exception Throw New Exception("Error en Envio_multicast. CE:" + ex.Message) End Try End Sub
Función para que el cliente reciba las transferencias de ficheros.
''La variable socket_cliente es el socket del cliente que se conecta con el servir ''Leer actualizacion es la funcion asociada a un proceso para espera datos del servidor Private Sub leerActualizacion() Dim fichero As FileStream Dim Recibir() As Byte Dim Ret As Integer = 0 Dim tamaño As Integer Dim k As Integer Dim UltimosDatosRecibido As String = "" While True Try If socket_cliente.Connected Then Recibir = New Byte(62) {} socket_cliente.ReceiveBufferSize = 62 'Me quedo esperando a que llegue un mensaje desde el cliente If Me.modo_lectura_normal = True Then Ret = socket_cliente.Receive(Recibir) If Ret > 0 Then UltimosDatosRecibido = Trim(Encoding.ASCII.GetString(Recibir)) If InStr(UltimosDatosRecibido, "IF") Then tamaño = 0 Dim p As paquete_tcp = empaqueta(UltimosDatosRecibido) k = 0 tamaño = Int(p.op) fichero = New FileStream("FicheroTemp.xml", _ FileMode.Create, FileAccess.Write, FileShare.ReadWrite) Me.modo_lectura_normal = False End If Else Throw New Exception() End If Else Dim b(tamaño - 1) As Byte socket_cliente.ReceiveBufferSize = tamaño Ret = socket_cliente.Receive(b) If Ret > 0 Then If InStr(Encoding.ASCII.GetChars(b), "FA") > 0 Then Dim s As String = Encoding.ASCII.GetChars(b) s = Mid(s, 1, Ret - 2) If s <> "" Then fichero.Write(Encoding.ASCII.GetBytes(s), 0, s.Length) End If fichero.Close() Me.modo_lectura_normal = True .Close() Exit While Else Dim s As String = Encoding.ASCII.GetChars(b) fichero.Write(b, 0, Ret) k = k + Ret End If Else If Not (fichero Is Nothing) Then fichero.Close() End If RaiseEvent Mensages("Error en la tranferencia del fiechor") Exit While End If End If Else End If Catch ex As Exception ''Error en la transferencia End Try End While End Sub ''' <summary> ''' Empaqueta ''' </summary> ''' <param name="sms">Datos en formato String</param> ''' <returns>Devuelve el empaquetamiento de los datos a paquete_tcp</returns> ''' <remarks>Traduce los datos que llegan y los encapsula en el formato paquete_tcp</remarks> Public Function empaqueta(ByVal sms As String) As paquete_tcp sms = Trim(sms) Dim pa As paquete_tcp pa.codOp = Mid(sms, 1, 2) pa.tm = Int(Mid(sms, 3, 3)) pa.op = Mid(sms, 6, pa.tm) Return pa End Function
3-Soluciones a problemas varios.En este apartado explico algunos errores comunes que surgen al crear una aplicación con uso de socket. Un error típico o por lo menos que en mis comienzos tuve, es creer que si yo enviaba 2 byte la función recibe de socket los recibiría sin problemas, pero esto no fue así, hay que tener en cuenta que si nuestro buffer pone que recibiremos 30 bytes pues la función recibe nos devolverá los datos cuando el buffer se haya llenado, por lo tanto si yo envío Hola y después envío Adios y mi buffer es de 7 byte, el socket me devolverá primero "holaAdi" y no "Hola" y luego "Adios". Es por eso, que lo que yo hice fue encapsular mis mensajes siempre con un tamaño fijo, por ejemplo 60 byte, de tal forma que si mi mensaje tiene Adios y luego otro mensaje con Hola pues encapsulo "Adios" en mi paquete de 60 byte rellenado los que falten con espacios en blanco, de esta forma me aseguro que unos mensajes no se pisen con otros. En mi caso y como podréis comprobar por la funciones que hay en la clase que os dejo yo cree un paquete con 3 campos:Codigo Operacion,Operandos, tamaño de los operandos y mi cliente luego lo desempaqueta de tal forma que lo puede procesar de forma correcta. Otro problema cuando se trabaja con muchos hilos es la concurrencia, tenemos que tener en cuenta que dos procesos no deben de modificar o acceder a recursos simultáneamente, por lo tanto tenemos que tener cuidad en los código de los procesos con utilizar semáforos o en mi caso synclock, para de esta forma salvaguardarnos de problemas de este tipo. Para finalizar nada más decir que la aplicación funciona al 100% y que esta probada con uno sistema de 40 ordenadores, respetando bastante bien el tiempo real.
Espacios de nombres usados en el código de este artículo:System.Windows.Forms
|
Lo comentado en este artículo está probado (y funciona) con la siguiente configuración:
El autor se compromete personalmente de que lo expuesto en este artículo es cierto y lo ha comprobado usando la configuración indicada anteriormente.
En cualquier caso, el Guille no se responsabiliza del contenido de este artículo.
Si encuentras alguna errata o fallo en algún link (enlace), por favor comunícalo usando este link:
Gracias.
Código de ejemplo (comprimido): |
Fichero con el código de ejemplo: jose_villanueva_Emulacion_Multicast_en_TCP.zip - 5.38 KB
|