Servidor genérico basado en sockets para .NET 3.5

El entorno computacional actual ofrece las más diversas opciones para el desarrollo de aplicaciones interconectadas.  Los programadores nuevos nacieron con el concepto de “alto nivel” y las tecnologías como el lenguaje ensamblador y la comunicación por sockets forman parte del paleolítico de la computación, en el mejor de los casos, son simplemente como los átomos, elementos constituyentes que siempre han existido y nunca morirán, pero de los cuales no tendrán nunca que preocuparse.  Por ejemplo, .NET ofrece Windows Communication Foundation (WCF), una super interfaz de muy alto nivel para la creación de aplicaciones orientadas a servicios, donde cada cliente puede acceder a múltiples servicios y cada servicio puede ser accedido desde múltiples clientes.

Sin embargo, aún en la actualidad todavía se presentan infinidad de proyectos que requieren tocar de cerca los metales y usar los “viejos” sockets. Al menos tres elementos pueden justificar esta decisión:

  • Eficiencia: las bibliotecas como WCF brindan una serie de abstracciones que permiten concentrarse en el procesamiento específico del dominio de trabajo y nos aislan de los detalles subyacentes de los protocolos de comunicación a más bajo nivel, pero esto ocurre a expensas de la adición de encabezados extra, conversión bidireccional a XML y en general el soporte de los protocolos específicos para el estándar de servicios web.  Si nuestro sistema requiere procesar miles de solicitudes cada segundo, entonces evidentemente una implementación con sockets será mucho más eficiente.
  • Compatibilidad entre plataformas: WCF está diseñado principalmente para conectar aplicaciones entre sistemas Windows.  Si estamos creando un servidor cuyos clientes pueden ser sistemas basados en Linux u otro sistema que no implemente los protocolos que expone WCF, entonces nuevamente tendremos que acudir a los sockets.
  • Madurez y completitud: he tomado WCF como ejemplo para la comparación pues, aunque existen otras bibliotecas, esta es la más conocida en el ámbito de .NET, pero me atrevo a especular que el resto en mayor o menor medida (incluyendo mi propio server genérico expuesto en este mismo post) sufren de las limitaciones expuestas aquí.  Como toda abstracción, WCF expone al desarrollador los elementos que sus creadores consideran más importantes de las capas que están por debajo y ello implica que siempre algo que pudieramos necesitar usar pudiera quedarse fuera.  Por ejemplo, cuando comencé a investigar WCF en la versión 3, algo tan simple como obtener la dirección IP del cliente era imposible, en la 3.5 ya se puede, pero de todas formas al menos para mi gusto, de forma demasiado verbosa.

Algunas desventajas de usar sockets:

  • Requiere la definición de un protocolo propio:  los sockets solo saben enviar y recibir arreglos de bytes, luego es necesario diseñar y programar la lógica que le impregna semántica a esos bytes y los convierte en un protocolo nuevo.  Esto, para cada servidor que construyamos con sockets.
  • Mayor dificultad:  al trabajar con el socket que es un elemento a más bajo nivel, se incrementa la dificultad, pues la libertad de acción se convierte en un arma de doble filo, por una parte abre un mundo de posibilidades y flexibilidad, pero por el otro nos responsabiliza con cada detalle del mecanismo, incluyendo el manejo completo de errores.

Luego de estos antecedentes … SI, este post habla de la programación con sockets en C#, especificamente a partir de la versión 3.5 de la plataforma, la primera que permite escribir en .NET servidores de alto desempeño.  Además pongo a disposición de los colegas una pequeña abstracción construída sobre esta tecnología que al menos personalmente me ha sido muy útil para aumentar la productividad en un proyecto en el cual he tenido que construir prácticamente “servidores en serie”. El código que refleja las ideas para el servidor genérico que se expone a continuación, se puede descargar aqui.

Anterior a .NET 3.5 el modelo más sofisticado disponible para escribir aplicaciones servidores basados en sockets consistía en el uso del patrón asincrónico Beginxxx … Endxxx (BeginAccept, BeginReceive, BeginSend).  Este modelo se soporta sobre el pool de hilos (threads) asociado a cada aplicación, liberando al programador de la responsabilidad del manejo de los hilos y proporciona un esquema muy coherente y eficiente para servidores que atiendan a unos cuantos cientos de usuarios.  Sin embargo, como se ha presentado ampliamente en los forums, cuando el número de usuarios simultáneos crece a miles o millones, ya se queda corto en cuanto a eficiencia.  Sus principales limitaciones radican en primer lugar en la asignación por parte del framework de objetos nuevos IAsyncResult en el heap para cada operación asincrónica, lo cual disminuye el rendimiento y aumenta la fragmentación de la memoria; y en segundo lugar: las operaciones ocurren en hilos propios de .NET en lugar de los del sistema operativo.

Como respuesta a los problemas expuestos en el párrafo anterior, a partir de .NET 3.5 se incluyeron un grupo de mejoras para la creación de servidores de alto rendimiento, las cuales dieron lugar a un nuevo patrón asincrónico que permite soportar hasta millones de clientes simultáneos de forma muy eficiente.  Este método esta basado en los IOCP (Input Output Completion Ports / Puertos de Completamiento de Entrada Salida).  Sin entrar en exceso de detalles para no alejarme del objetivo principal de este artículo (está ampliamente documentado en forums, blogs y en MSDN), consiste en la explotación de hilos del sistema operativo especialmente dedicados a las operaciones de entrada/salida (ej. lectura/escritura en disco, puerto serie, envio/recepción por sockets) y en la utilización de objetos reusables de tipo SocketAsyncEventArgs para el mantenimiento del estado entre el principio y el final de la operación asincrónica.

Desde el punto de vista del desarrollador todo lo anterior es puro background teórico.  La nueva clase Socket reprogramada para .NET 3.5 se encarga detrás del telón del manejo de los IOCP.  La implementación del nuevo modelo es realmente bien sencilla y consta de los siguientes pasos:

  1. “Conseguir” un objeto de tipo SocketAsyncEventArgs.  El entrecomillado para enfatizar que puede ser crear un nuevo objeto u obtener uno ya existente de un pool.  El segundo método es el que se usa aquí ya que resulta el más eficiente pues evita el problema mencionado con el modelo anterior de la fragmentación del heap.
  2. Fijar en este objeto los parámetros requeridos para la acción que se va a ejecutar con el Socket.  El parámetro fundamental es Completed, el cual define la función callback que será ejecutada cuando termine la operación asincrónica.
  3. Ejecutar las acciones con el Socket.  Las acciones principales son ReceiveAsync (poner el socket en estado de recepción) y SendAsync (enviar datos a través del socket); en el caso de un cliente también es posible usar ConnectAsync para conectar a un servidor.
  4. Si la acción retorna un booleano false significa que la operación ocurrió de forma sincrónica, por tanto debemos realizar un llamado a la función callback manualmente.
  5. Una vez completadas las operaciones con el socket, liberar el objeto SocketAsyncEventArgs o retornarlo al pool para que pueda ser reutilizado por otro cliente.

Recomiendo antes de continuar, la lectura del artículo de MSDN donde se muestran los pasos anteriores y se encuentra un ejemplo que muestra el funcionamiento de un servidor de sockets elemental.  El servidor genérico que describo a continuación está basado en dicho ejemplo y a través de la herencia y la genericidad lo extiende y modifica de la siguiente forma:

  • Se crea un componente genérico que puede ser reutilizado en varias aplicaciones sin necesidad de modificar el código fuente del “motor” principal del servidor, el cual encapsula los detalles operativos de comunicación, la creación y utilización del pool de SocketAsyncEventArgs, y en general las operaciones que de otra forma habría que copiar y pegar en cada nuevo servidor que se programe.
  • Se definen dos clases adicionales base.  SocketServerState: contiene el estado del servidor, permite definir un mensaje de bienvenida y funciones virtuales que puede sobrescribir el usuario para eventos como la conexión de un nuevo cliente, entre otras.  UserSockDataBase: contiene el estado de un cliente específico, un identificador único (GUID), su dirección IP de origen, un SocketAsyncEventArgs readArgs (para operaciones de envio/recibo) y otro sendArgs (solo para operaciones de recepción) y otras.  El desarrollador debe crear sus propias clases heredadas de SocketServerState y UserSockDataBase para cada servidor que implemente.
  • Los objetos de tipo UserSockDataBase corresponden a la propiedad UserToken de la clase SocketAsyncEventArgs, la cual permite mantener variables propias de estado entre el inicio y el fin de una operación asincrónica, además de las generales que conserva SocketAsyncEventArgs.
  • UserSockDataBase permite además la separación del procesamiento de los mensajes recibidos del motor implementado por el servidor.  El usuario debe sobreescribir la función AddInput en la cual implementará su propio mecanismo de interpretación del protocolo específico.
  • La creación de un pool de objetos SocketAsyncEventArgs reusables evita la fragmentación del heap, pero sucedería lo mismo si se crean/destruyen los objetos UserSockDataBase correspondientes, por tanto, en su lugar se crea un pool pero de UserSockDataBase.
  • El contar con un SocketAsyncEventArgs para las operaciones de envio (sendArgs), permite la creación de colas de envio: si el servidor envia un mensaje en el momento que se está ejecutando un Send asincrónico se generaría un error de ejecución indicando que el objeto SocketAsyncEventArgs está en uso.  Para evitarlo, el servidor genérico en este caso, almacena el mensaje en una cola de envio y cuando concluya la operación pendiente intentará enviarlo.

Implementación de un servidor usando el componente

A modo de ejemplo supongamos que se desea implementar un servidor que responda a cada mensaje recibido la misma cadena en mayúsculas.  Primero debemos crear dos clases UserSockDataMayusculas y SocketServerStateMayusculas que hereden de UserSockDataBase y SocketServerState respectivamente:

public class UserSockDataMayusculas : UserSockDataBase
{
 ...
}

public class SocketServerStateMayusculas : SocketServerState
{
   public override string WelcomeMsg(object args)
   {
       return "Bienvenido al servidor de mayusculas";
   }
}

Y luego creamos una variable de tipo SocketServer:

SocketServer&ltUserSockDataMayusculas, SocketServerStateMayusculas&gt servidor = new SocketServer&ltUserSockDataMayusculas, SocketServerStateMayusculas&gt(10000, 1024, 10);
servidor.Init();
servidor.Start(5555);

Esto creará un servidor que permitirá un máximo de 10000 usuarios conectados, con un buffer de entrada/salida de 1024 bytes y comenzará a escuchar por el puerto TCP 5555.  El tercer parámetro del constructor (el 10) corresponde al backlog del servidor, es propio del protocolo TCP/IP e indica el máximo de pedidos que pueden encolarse simultáneamente en caso que el servidor esté ocupado atendiendo una nueva conexión.

Ahora solo queda sobreescribir la función AddInput de UserSockDataMayusculas para implementar nuestro protocolo:

public override void AddInput(string s)
{
    parseBufferReceived += s;
    int posSeparador = parseBufferReceived.IndexOf("\r\n");
    string palabra = "";
    if (posSeparador != -1)
    {
        palabra = parseBufferReceived.Substring(0, posSeparador);
        parseBufferReceived = parseBufferReceived.Substring(posSeparador, parseBufferReceived.Length - posSeparador - 2);
        parseCommandResponse = palabra.ToUpper();
    }
}

Ya tenemos un servidor totalmente funcional que puede atender a miles de usuarios simultáneos pidiendo convertir palabras a mayúsculas 🙂

Principio de funcionamiento

El servidor genérico facilita la implementación de un amplio subconjunto de protocolos basados en mensajes de texto (ej. un servidor de correo electrónico) y expone una clase genérica principal SocketServer<T, S> definida:

public class SocketServer&lt;T, S&gt;
   where T : UserSockDataBase, new()
   where S : SocketServerState, new()
{
  ...
}

El mecanismo general de funcionamiento es bien sencillo y similar en concepto a cualquier servidor TCP/IP sobre cualquier plataforma:

  1. Se recibe una conexión entrante de un cliente a través del socket principal.
  2. El servidor ejecuta una serie de chequeos elementales de seguridad (ej. verificar si la dirección IP de origen es permitida). Si los resultados son satisfactorios obtiene un objeto de estado disponible del pool y se lo asigna al cliente. Este objeto ya contiene un socket nuevo a través del cual ocurrirá todo el intercambio posterior de mensajes.  El socket principal quedó libre nuevamente escuchando nuevas peticiones.
  3. El servidor le envia un mensaje de bienvenida (definido por el usuario) al cliente nuevo.
  4. A partir de aquí comienza un ciclo en el cual el cliente envia un mensaje y el servidor responde hasta que uno de los dos puntos termine la conexión como parte del protocolo o debido a un error de comunicación.

La clase expone un grupo de métodos públicos como Init, Start y Stop ninguno de los cuales implica el manejo directo del socket subyacente. El método CloseClientSocket permite terminar la conexión de un cliente en cualquier momento y SendData no es necesario usarla en el caso de los protocolos tradicionales que siguen la rutina de enviar comando – esperar respuesta. Sin embargo, en ocasiones se puede presentar un protocolo que luego de efectuar una serie de envios y recepciones, simplemente envia mensajes cada cierto tiempo. No es algo común pero en el proyecto que me sirvió de inspiración se presentó esta situación y por ello decidí dejar incluido el SendData.

Los métodos privados son los encargados de efectuar el trabajo “sucio” a través de los sockets de .NET y los nuevos métodos “async”.  La inspección del código fuente disponible para descargar con este artículo revela fácilmente los mecanismos empleados. La funcion OnIOCompleted sirve como punto de entrada para manejar los completamientos de los SendAsync y ReceiveAsync: si la última operación sobre el socket fue un envio, entonces se invoca ProcessSendCyclic y si fue una recepción se invoca ProcessReceive. Importante comprender como el framework se encarga de conservar el estado del cliente (incluídas las variables personalizadas que adicionamos a través del objeto UserSockDataBase) entre el inicio y el fin de la operación asincrónica.

Lo más interesante dentro de los métodos privados resulta la implementación de ProcessReceive:

/// &lt;summary&gt;
/// This method is invoked when an asynchronous receive operation completes.
/// If the remote host closed the connection, then the socket is closed.
/// &lt;/summary&gt;
/// &lt;param name=&quot;userData&quot;&gt;Context object for the receive operation&lt;/param&gt;
private void ProcessReceive(T userData)
{
    SocketAsyncEventArgs e = userData.recvArgs;
    Int32 bytesTransferred = e.BytesTransferred;

    // Get the message received from the listener.
    String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, bytesTransferred);

    // Increment the count of the total bytes received by the server.
    Interlocked.Add(ref this.totalBytesRead, bytesTransferred);

    // Add received string to UserSockData buffer and process
    try
    {
       userData.AddInput(received);
    }
    catch (Exception ex)
    {
        this.CloseClientSocket(userData);
        InvokeAppendLog(&quot;ERROR filling internal buffer: IP: [&quot; + userData.user_IP + &quot;], ID: [&quot; + userData.user_Guid + &quot;], Error: &quot; + ex.Message);
        return;
    }

    // if there was a parsing error then disconnect the client (probably a hacker)
    if (userData.parseError)
    {
        CloseClientSocket(userData);
        return;
    }
    else
    {
        // If I got an answer then send it
        if (userData.parseCommandResponse != &quot;&quot;)
        {
            string response = userData.parseCommandResponse;
            int resultNumberOfBytes = Encoding.ASCII.GetBytes(response, 0, response.Length, userData.recvArgs.Buffer, userData.recvArgs.Offset);
            if (resultNumberOfBytes &gt; bufferSizeReceive)
            {
                InvokeAppendLog(&quot;Response size in bytes is greater than buffer size&quot;);
                CloseClientSocket(userData);
                return;
            }

            userData.recvArgs.SetBuffer(userData.recvArgs.Offset, resultNumberOfBytes);
            try
            {
                bool willRaiseEvent = userData.Socket.SendAsync(e);
                if (!willRaiseEvent)
                    OnIOCompleted(userData.Socket, e);
            }
            catch (Exception)
            {
                CloseClientSocket(userData);
                return;
            }
        }
        else  // otherwise continue receiving
        {
                if (userData.Socket.Connected)
                {
                    if (userData.NextOperation == NextOperationModes.Normal)
                    {
                        try
                        {
                            e.SetBuffer(e.Offset, bufferSizeReceive);
                            bool willRaiseEvent = userData.Socket.ReceiveAsync(e);
                            if (!willRaiseEvent)
                                OnIOCompleted(userData.Socket, e);
                        }
                        catch (Exception ex)
                        {
                            InvokeAppendLog(&quot;Disconnecting client (ProcessReceive 2) &quot; + userData.user_Guid + &quot;, error: &quot; + ex.Message);
                            CloseClientSocket(userData);
                        }
                    }
                }
        }
    }
}

El método AddInput de la clase derivada de UserSockDataBase es llamado por ProcessReceive y constituye el enchufe entre el servidor genérico y el implementador del protocolo. A partir de aquí se desarrolla toda la lógica necesaria para generar la respuesta a un comando o mensaje del cliente. Antes de retornar el control AddInput es responsable de fijar los valores de sus variables parseError (true o false) y parseCommandResponse (la respuesta), a través de las cuales el servidor toma la decisión sobre el próximo paso a seguir: desconectar al cliente en caso de error o enviar la respuesta si no hubo problema.

Trucos

Aunque los principios básicos de un servidor de sockets resultan bien simples, la programación real resulta en mi opinión más que difícil, verdaderamente truculenta.  Algunos aspectos son propios de .NET y otros se aplican para cualquier plataforma y son consecuencia de la propia definición de TCP/IP. En general, la programación de comunicaciones es absolutamente defensiva: cualquier operación puede generar un error y nuestro código debe ser capaz de manejarlo si queremos evitar desagradables errores fatales en tiempo de ejecución.

A primera vista pudiera pensarse que ProcessReceive se invoca cada vez que se recibe un mensaje o comando completo del cliente, falso!  Lo que se trasmite realmente entre cliente y servidor son secuencias de bytes y la cantidad real de bytes transferidos depende de los buffers de envio y recepción de cada punto.  O sea, que si (convertido a string) esperamos la cadena “Bienvenido”, fácilmente puede llegar en dos partes “Bienv” y “nido”.  AddInput es responsable de verificar cada secuencia recibida y solo procesarla cuando tenga el comando completo. Para ello es común definir en los protocolos basados en texto, un carácter o secuencia “separador”; en el servidor presentado aqui se usa “\r\n”.  Si no tenemos un mensaje completo simplemente fijamos parseError=false y parseCommandResponse=”” lo que le indica al servidor que debe continuar recibiendo.

NO existe algo similar a un evento que indique que el socket o la conexión física generó error. La única forma de determinarlo es iniciar una operación real de envio o recepción y descubrir que causó error. En el caso de la recepción .NET indica que hubo error retornando cero bytes recibidos. El servidor genérico maneja bastante bien la siguiente situación, pero si se trabaja directo con los sockets y las funciones “async” y se reutilizan los objetos de contexto SocketAsyncEventArgs, es importante saber en que momento exacto se puede liberar el objeto para que otro cliente pueda usarlo. Por ejemplo, iniciamos un SendAsync y se interrumpió súbitamente la conexión física; si hay muchas operaciones simultáneas en ejecución o el envio se demora por dificultades en la conexión, pudiera demorarse el llamado a la función de completamiento y mientras no suceda esto, el SocketAsyncEventArgs estará “en uso”. Si se intenta reutilizarlo en otra operación, se generará un error de ejecución indicándolo.

Un ambiente de miles de clientes conectados simultáneamente implica un grupo de hilos (threads) del ThreadPool de la aplicación ejecutándose simultáneamente y por tanto varios hilos compartiendo al mismo tiempo un grupo de recursos de la aplicación. Hay que se extremadamente cuidadosos en el momento de acceder a estos recursos. Una regla elemental consiste en el uso del lock para variables compartidas.  Por ejemplo, si tenemos un índice (diccionario) de clientes por dirección IP almacenado en la clase derivada de SocketServerState, no se puede acceder a este al mismo tiempo que se elimina un elemento por desconexión en otro hilo. En general, un buen dominio de la programación multihilo y de las herramientas de concurrencia de .NET ayuda mucho a la creación de un servidor eficiente.

Y hablando de eficiencia, otra regla de oro: en la lógica implementada en el AddInput al procesar el comando para elaborar la respuesta, todas las optimizaciones que se realicen resultarán insuficientes. La demora en responder puede causar serios cuellos de botella en el servicio, lo cual unido a la superposición de los lock pudiera hasta bloquear el servidor debido a los puntos muertos (deadlocks), que son hilos esperando el desbloqueo entre ellos mismos. En protocolos para los cuales se conoce que la obtención de la respuesta a un comando es un proceso complejo pudiera resultar más eficiente implementar en otro hilo el Patrón Productor-Consumidor, lo cual evitaría la congestión de los hilos de envio/recepción del servidor.

Limitaciones y futuro

Aunque lo vengo usando en producción desde hace un año aproximadamente de forma muy estable, como comenté anteriormente, surgió a partir de un proyecto específico. Por ello aún no posee toda la generalidad que hubiese deseado. De todas formas lo he publicado pues estoy seguro que podrá sacarle las castañas del fuego a varios colegas.

Estas son algunos de los puntos que me gustaría mejorarle:

  • Permitir definir el separador \r\n a través de configuración en lugar de fijo por código fuente.
  • Posibilidad de elegir entre una versión binaria y string del servidor, finalmente el intercambio original de datos ocurre en secuencias de bytes y de hecho muchos protocolos están definidos en términos de bytes y no de cadenas. (En esto estoy trabajando poco a poco).
  • Añadir opciones de cifrado a los datos.
  • Modificar la estructura del proyecto de forma que permita la inclusión de tests.

Si alguien desea hacerle modificaciones, optimizaciones o ampliar su funcionalidad y desea compartirlo: BIENVENIDO!

Anuncios

19 Responses to Servidor genérico basado en sockets para .NET 3.5

  1. alejandro says:

    hola he creado un socket cliente pero solo me soporta 3 sobrecargas es decir solo 3 usuarios conectados en un servidor hay alguna manera de evitar esa limitacion que nos da el vb.net utilice la importacion sistem.net.socket

    • Juan Pedro Barquin says:

      Alejandro, no he trabajado con sockets en VB pero no creo que la limitacion del número de clientes se deba al lenguaje pues al final todo se compila a un mismo lenguaje común que ejecuta el .NET framework. Puedes descargar la biblioteca que explico en el artículo, aunque está programada en C# puedes usarla desde cualquier lenguaje soportado por .NET, incluído por supuesto VB.

  2. Excelente artículo, quisiera probarlo para un proyecto parecido de intercambio de mensajes.

    Donde puedo bajar el código para revisarlo?

  3. En cuanto a eficiencia, serán de igual eficiente si se haría con VC++, habría que probarlo

    • Juan Pedro Barquin says:

      En un servidor la eficiencia se relaciona más con la habilidad de manejar los hilos (threads) que con el lenguaje en que se programe. Es posible construir un servidor muy eficiente en C# que soporte miles de clientes simultáneos siempre que se usen las estructuras de datos y algoritmos necesarios, ej. usar diccionarios en lugar de listas para las búsquedas, StringBuilder en lugar de concatenación normal de cadenas de caracteres, introducir las herramientas de concurrencia (ej. locks).

  4. Muchas gracias Juan Pedro, muy buena aclaración, de hecho ya he decidido hacer unas pruebas para un proyecto en particular de integración.

  5. Hector says:

    Hola, lo he integrado con Unity ya que me parecio una manera mas practica y modular de usar el mecanismo y va bien, pienso que es buena idea para los que quieren separar el mecanismo de la parte de la logica y de los procesos de negocio, ademas que como todo patron de inyeccion de dependencias trae consigo un conjunto de ventajas como facilidad para desarrollo de Test, facilidad para realizar cambios sin afectar estabilidad, servicios transversales, etc.

    Por otro lado queria preguntarte algo el programa que tienes en produccion lo tienes en un Host de tipo Consola o has hecho un cambio para que sea un servicio windows ?
    Te lo pregunto por que el servicio windows puede recuperarse en caso de caidas o fallos de forma automatica mientras que un programa de consola no, ademas que es mas manual por ejemplo para ejecutarse necesita que alguien lo ejecute, mientras que un servicio windows puede lanzarse de manera automatica…espero tus comentarios al respecto ..
    Hector.

    • Juan Pedro Barquin says:

      Hola Hector, me parece muy bien la integracion con un framework como Unity que proporciona todas las ventajas que mencionas. Aunque inicialmente fue concebido de esta forma en un entorno de Test Driven Design, lamentablemente por cuestiones de tiempo nunca pude completarlo de esa forma. La version actual tiene varios cambios en el diseño de las clases que facilita por ejemplo la sintaxis para hacer referencia a parámetros globales del servidor desde los clientes conectados y mejoras en cuanto al tratamiento de la concurrencia; en la primera oportunidad que pueda organizar el código lo publico por acá.

      Sobre la segunda cuestión, efectivamente, ya otros programadores lo han empleado para crear servicios de Windows, aplicaciones Windows Form y WPF, simplemente que para los primeros prototipos y mostrar un ejemplo de uso fue más sencillo una aplicación de consola.

      Muchas gracias por el comentario, muy acertados todos tus planteamientos,

  6. agustin bua says:

    hola como puedo descargar el proyecto que sale mencionado en el articulo. Ya que busque la columna archivo en el blog. Pero no tengo ninguna opcion de descarga

    • Juan Pedro Barquin says:

      Hola, es que cambió el widget de box.net, ya lo actualicé y sale correctamente el Flash para descargarlo. Gracias por el aviso 😉

  7. Hector Hernandez A. says:

    Hola Juan Pedro, Sabes como se podria saber desde el servidor si un cliente que pre-definió un tiempo de timeout ya paso ese tiempo de espera por un mensaje del servidor, esto por que deseo tener saber cuantos timeouts tuvo el cliente y cuantos atendió bien(no quiero controlarlo desde el cliente por que no tengo control sobre eso), trate de usar funciones como esta que encontré en google:
    return !(socket.Poll(1, SelectMode.SelectWrite) && socket.Available == 0);
    pero el tema es que al parecer no lo detecta por que a pesar que el cliente ya dio una exepcion indicando timeout no hace el cierre o aborto de conexion, por tanto al parecer el socket se mantiene como si estuviera conectado normalmente,
    sin embargo cuando he probado que el cliente cierre conexion o se cae si lo detecta en servidor con esa funcion que mencione arriba y pueda hacer el conteo que te digo.
    gracias por los comentarios que puedas darme, un abrazo. Hector.

    • Juan Pedro Barquin says:

      Hector, la forma más efectiva que conozco de controlar los timeouts es guardando para cada cliente los tiempos de la última trasmisión (envio o recepción) para chequearlos periódicamente en un Timer. No existe otra forma de descubrir con certeza que un cliente se ha desconectado que no sea haciendo una operación de trasmisión sobre él. El socket.Poll puede detectar ciertos tipos de errores pero no es absouto:

      Nota: Este método no puede detectar ciertos problemas de conexión, como, por ejemplo, los debidos a un cable de red roto o a un cierre brusco del host remoto.Para detectar estos tipos de errores es necesario probar a enviar o recibir datos. (http://msdn.microsoft.com/es-es/library/system.net.sockets.socket.poll.aspx)

      Por eso es común que muchos protocolos implementen alguna forma de KeepAlive como un método de control de posibles conexiones “difuntas”.

      Saludos.

  8. Gustavo says:

    Hola amigo, queria saber como hacerle para que en lugar de que me aparesca la entrada en la consola me lo muestre en un cuadro de texto

  9. lu says:

    hola , el protocolo TCP/IP asegura que los paquetes enviados sean recibidos por el cliente , pero en el caso de los sockets envía los mensaje pero en ocasiones el cliente no recibe los mensajes a pesar de que el servidor haya enviado si ningún error ,alguna idea del porque el cliente no recibe ?

  10. Diego says:

    Hola Juan Pedro, necesito saber si todavia se encuentra disponible el archivo de descarga de este post y si tendrias la amabailidad de facilitarmelo, estoy empezando con la programación con IOCP y lo tuyo es de lo poco que encontre en español y me vendria muy bien.
    Muchas gracias.

    • Juan Pedro Barquin says:

      Diego el link es este https://app.box.com/s/y98p3gymf4oasbscvk9js57lqwkwwpwk , ya la actualicé en el post, sucede que el plugin de WordPress para box.net dejó de funcionar.

      • Diego says:

        Muchas gracias, por lo que entendi en base a este servidor que nos brindaste, deberemos crear un cliente que se conecte al mismo no es asi? Y si es asi, tambien hay que utilizar IOCP para el cliente o solo es para la parte del servidor? Es decir el cliente lo puedo crear con un socket asincronico cualquiera?

      • Juan Pedro Barquin says:

        El cliente puede crearse usando cualquier técnica de sockets y en cualquier lenguaje siempre que implemente el protocolo que “entiende” el servidor. IOCP puede usarse para clientes o servidores, aunque lo mas común es en servidores ya que es un mecanismo que permite utilizar grupos de hilos (thread pools) compartidos para el manejo simultáneo de procesos. La transición entre hilos (thread switch) es una operación costosa para el SO, por tanto si tienes un grupo de hilos que atienden miles de clientes es algo muy positivo.

        Dicho esto, un par de consejos:

        1. Si tu servidor no va a soportar millones de clientes simultáneos dale una ojeada a los métodos mas tradicionales de manejo de sockets en C# (beginSend, beginReceive). Según mi modesto criterio .NET tiene muchas lagunas en su implementación de IOCP y el manejo de concurrencia puede convertirse en un dolor de cabeza.

        2. Si realmente necesitas soportar millones de clientes usa alguna biblioteca más pulida. Como puedes ver este post es del 2010, y este servidor con ligeras modificaciones está en producción desde entonces pero no lo he tocado más. Te recomiendo Extasys (https://github.com/nsiatras/extasys) que es open source y está bien activo, lo he usado en un par de proyectos.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: