Doble Ingeniero en rama de Informática y rama de Ing. del Software por la Universidad Rey Juan Carlos.
Perteneciente a la primera partida de la doble titulación, la gran cantidad de conceptos aprendidos me han ayudado a realizar este enorme proyecto que en los primeros días veía inabordable, pero que avanzaba a pasos agigantados.
Partiendo en un principio de una comparativa entre los lenguajes Go y Node bajo un cliente desarrollado en C++, este proyecto fue aumentando el número de lenguajes hasta un total de tipos de servidores distintos, convirtiéndolo en un gran trabajo con un gran esfuerzo.
Por favor, envía un correo a mi correo personal si tienes alguna duda que pueda solucionarte acerca de este tema.
Puedes obtener la memoria en el siguiente link.
EventMachine es un event-driven I/O y sistema concurrente ultraligero escrito en Ruby. Provee de una arquitectura concurrente basada en el modelo Reactor.
Está diseñado para aumentar la escalabilidad, rendimiento y estabilidad, y eliminar la complejidad de los procesos network, dejando que los desarrolladores solo se concentren en la lógica de la aplicación.
Las principales funciones del servidor EventMachine son los siguientes:
Desarrollado por Guido Van Rossum en torno a 1991, Python es un lenguaje de propósito general, multiparadigma, open source y multiplataforma. Su filosofía de diseño es empatizar la legibilidad de código con una sintaxis simple que permite escribir en pocas líneas programas complejos.
Entre los paradigmas soportados, destacar la programación orientada a objetivos, la programación imperativa, la programación funcional y la programación procedural. Además, incluye un tipado dinámico y un recolector de basura propio.
Uno de los principales problemas de este lenguaje es la existencia de dos versiones en paralelo que difieren, en parte, en la sintaxis de desarrollo.
Python es un lenguaje que ha mejorado enormemente en la programación con threads desde sus inicios, facilitando una potente arma de trabajo que, unido a la sencillez de programación, aumenta enormemente el rendimiento de nuestras aplicaciones.
La idea principal es crear un thread al cual, como argumento “target”, se le facilita la función a ejecutar. Todo el funcionamiento interno del servidor funciona a base de procesos y forks internos.
Gracias a la facilidad para el manejo de JSON, la separación de la lógica de las distintas acciones resulta verdaderamente sencilla. Junto a esto, la librería SocketServer de Python permite evitar tener que controlar las nuevas conexiones.
Aunque parece que en las últimas versiones de Python se permite que las variables compartidas tengan condición de carrera, se ha preferido utilizar algunos locks para controlar los nuevos sockets y posiciones de los usuarios.
Ruby es un lenguaje de programación interpretado, reflexivo y orientado a objetos, creado por el programador japonés Yukihiro 'Matz' Matsumoto, quien comenzó a trabajar en Ruby en 1993, y lo presentó públicamente en 1995.
Combina una sintaxis inspirada en Python y Perl, con características de programación orientada a objetos similares a Smalltalk. Además, comparte diversas funcionalidades con otros lenguajes e programación, como Lisp o Lua.
Su nombre viene de una broma que los compañeros de Manz aludiendo al lenguaje Perl. El objetivo principal de Ruby es mejorar la productividad y la diversión del desarrollador, siguiendo para ello varios principios de una buena interfaz de usuario. Además, sostiene que el diseño de sistemas necesita enfatizar más las necesidades humanas que las de las máquinas.
A la hora de desarrollar el servidor en Ruby con las librerías nativas, se utilizó la información recogida en el artículo Sockets programming in Ruby de IBM. La idea principal de este servidor en Ruby nativo fue un selector que se encargara de la siguiente acción o evento a tratar.
Pese a varios intentos en la implementación del servidor siguiendo este manual y algunos otros, el principal cuello de botella se encontraba en la recogida de información (stdin) de los sockets, debido a que no soporta gran cantidad de mensajes por segundo.
C es un lenguaje de programación compilado creado en 1972 por Dennis M. Ritchie en los Laboratorios Bell, como evolución del anterior lenguaje B.
Se trata de un lenguaje orientado a la implementación de Sistemas Operativos, y es muy apreciado por la eficiencia del código producido, además de ser el más popular para el desarrollo de software de sistemas y, en menor parte, de aplicaciones.
La última versión del estándar data del 2007, pero la versión más utilizada es el ANSI-C.
La implementación elegida para el desarrollo en C bajo Windows hace un uso de la librería nativa de este Sistema Operativo: Winsock.
Esta interfaz se trata de una biblioteca dinámica de funciones DLL creara para la implementación de TCP/IP. Incluye soporte para envío y recepción de paquetes de datos a través de sockets BSD.
La implementación de gran parte del servidor no se completó debido a diversos problemas relacionados con estructuras de datos y a la escasa funcionalidad y documentación ofrecida y encontrada, respectivamente, respecto a Winsock. No obstante, se incluye en el disco disponible con este trabajo el código desarrollado hasta el momento.
D (o D-lang) es un lenguaje de programación orientada a objetos, imperativa y multiparadigma diseñado por Walter Bright en 2001. Entre los distintos paradigmas soportados, cabe destacar la metaprogramación, la programación funcional, la programación paralela y la concurrente, junto al a orientada a objetos e imperativa anteriormente mencionados.
Diseñado como un rediseño de C++ con un enfoque más pragmático, D ha incorporando a sus librerías una gestión de memoria a través de un recolector de basura, programación funcional, control de concurrencia incorporada y multiplataforma (en contraposición a C y C++ que dependen del Sistema Operativo). Incluye sobrecarga de operadores como C++ y la incorporación de código ensamblador, además de ciertas ideas de lenguajes interpretados a código intermedio como Java y C\#, pero con posibilidad de gestión propia de memoria.
Siguiendo un modelo muy parecido al de C, el servidor desarrollado en D, por motivos de un desarrollo bastante rápido, se decidió reservar toda la memoria a utilizar al iniciar el servidor para añadir un número determinado de usuarios (esto en la realidad no debería ser así, pero era la solución más rápida para implementar)
La API de sockets de Dlang recomienda el uso de un selector, el cuál recibe un conjunto con todos los sockets conectados.
A la hora de leer los cambios de estado de los sockets realizamos un recorrido uno por uno, controlando si se han recibido nuevos datos, de forma que podamos lanzar las distintas acciones implementadas. A diferencia de otros servidores implementados, una vez completado el tamaño fijado para la lista de sockets, la aplicación avisa al usuario y desconecta al cliente.
Cabe destacar dos aspectos:
Akka es un conjunto de herramientas open source para la construcción de sistemas concurrentes y distribuidos en la JVM. Akka soporta múltiples modelos de programación de concurrencia, pero está más empatizado con el modelo de concurrencia con actores, inspirado en Erlang.
Los actores en Scala se desarrollaron en torno al 2006 por Philip Haller. Nació debido a la dificultad del desarrollo de programación de servidores complejos, repletos de memoria compartida y códigos sincronizados con locks. Para solucionar este problema, inspirándose en Erlang, Jonas Bonér desarrolló Akka.
Existen dos versiones en paralelo, con los que puedes desarrollar tanto en Scala (lenguaje original), como en Java.
Para desarrollar el servidor, como en cualquiera de los otros servidores, se ha utilizado un único actor cuya única labor es la de recibir conexiones, las cuales crean otros actores, que en nuestro caso son los propios clientes conectados.
Cada uno de estos actores dispone de dos acciones:
Esta solución basada en agentes facilita bastante la separación de labores de cada parte de la aplicación, pudiendo modificar fácilmente cambios sin que afecte al resto del programa.
(No concluyente, es idéntica a la desarrollada en el Servidor Akka con Scala).
Es importante destacar que Java no dispone de actores de forma nativa, por lo que Akka provee de la funcionalidad necesaria para poder utilizarlos en Java.
Debido al gran parecido de la API de Java con la de Scala (salvando excepciones), traducir el código fue algo bastante lineal, llegando al punto que ambos códigos eran un calco, únicamente cambiando la sintaxis, favoreciendo enormemente una exactitud de la funcionalidad de un 90 % (el único código distinto es la librería de gestión de JSON).
Al igual que en Scala con Akka, guardamos las referencias a todos los clientes conectados con el fin de tener guardados todos los clientes y poder comunicar los distintos mensajes según las distintas acciones requeridas por los clientes.
Apache MINA is a network application framework which helps users develop high performance and high scalability network applications easily. It provides an abstract event-driven asynchronous API over various transports such as TCP/IP and UDP/IP via Java NIO.
Apache MINA is often called:
Apache MINA comes with many subprojects :
El modelo de desarrollo de servidores planteado por Apache Mina, en especial, para servidores TCP, es un sistema altamente escalable con un uso intensivo de eventos. Para ello, Apache Mina dispone de diversos métodos que pueden ser redefinidos (en nuestro caso, se ha sobrescrito los métodos de captura de excepciones, broadcast, y la creación y destrucción de sesiones).
Encapsulando los sockets o clientes bajo una clase IoSession, podemos añadir dinámicamente atributos, como si de una sesión de un servidor HTTP se tratase.
Desarrollado por Ryan Dahl (y otros desarrolladores dentro de la empresa Joyent) en 2009, Node.js es un entorno de programación open source y multiplataforma en la capa del servidor (aunque no limitado únicamente a ello), basado en el lenguaje de programación ECMAScript, asíncrono, con I/O de datos basada en una arquitectura orientada a eventos. Su origen proviene del motor V8 de Google.
Aunque en su origen fue creado con el enfoque de facilitar la creación de programas en red altamente escalables, como servidores web, se puede utilizar para otras funcionalidades.
La inspiración para la creación de este lenguaje proviene de la barra de subida de ficheros de Flickr, la cuál no demostraba claramente cuánta cantidad de fichero se había subido ya. Además, utiliza ciertas ideas de otros modelos, como es el caso de EventMachine (Ruby) o Twisted (Python), entre otros.
Pese a que Node.js únicamente se basa en sistemas monothread, se puede activar el modo multi threaded a partir de la versión 0.10+. No obstante, Node.js utiliza un sistema multithreads a la hora de controlar ficheros o de eventos.
Debido al modelo de programación dirigido por prototipos y eventos por parte de JavaScript-NodeJS, y según la API de sockets de NodeJS, la arquitectura básica del servidor se basa en un socket que “escucha” las nuevas peticiones, creando internamente otros sockets hijos, a los cuales se les puede enviar mensajes.
Este socket padre se encarga principalmente de tres eventos básicos más el tratamiento de nuevas conexiones. Los tres eventos básicos son:
Para lanzar el servidor, utilizamos el host local (localhost) y el puerto 8089 y, para simplificar la codificación, se decidió utilizar CoffeeScript, una pequeña librería de azúcar sintáctico con cierto parecido a la sintaxis de Ruby.
Go (más conocido como Golang), es un lenguaje inicialmente desarrollado en Google en 2007 por R. Griesemer, R. Pike y K. Thompson.
Se trata de un lenguaje con tipado estático y con una sintaxis muy parecida a C, pero que añade un recolector de basura, control de tipos o capacidad para tipos dinámicos. Además, permite utilizar librerías en C.
Go está centrado en el cálculo pi, que provee una gran API para desarrollo de programación concurrente y programación paralela.
Tras buscar información sobre el desarrollo de aplicaciones bajo TCP en Go, se plantearon varias alternativas, las cuales se implementaron para probar cuál era la más rápida y eficiente:
C# es un lenguaje de programación orientada a objetos creado por Anders Hejlsberg en torno al 2001, desarrollado y estandarizado por Microsoft como parte de su plataforma .NET.
Tiene una sintaxis derivada de C/C++ y, aunque forme parte de la plataforma .NET, que es una interfaz de programación de aplicaciones, C# es un lenguaje independiente que originariamente se creó para producir programas sobre esta plataforma .NET.
C# incorpora todas las ventajas de Java, pero además, algunas ventajas de C++, como la sobrecarga de operadores.
C# propone un cambio en el modelo de desarrollo utilizando métodos asíncronos, esto es, los métodos no se ejecutan en serie, sino en “paralelo”. De esta manera, dos o más tareas pueden ejecutarse a la vez bajo un uso intensivo de retrollamadas (lo que se ejecuta al finalizar) y de funciones anónimas (generalmente, la función ejecutada dentro del método asíncrono).
Como casi todos los servidores implementados, se crea un socket principal de escucha de nuevas conexiones (usando en este caso una llamada asíncrona sin retrollamada). Una vez aceptada la conexión, se ejecuta un bloque asíncrono para el control de mensajes del socket.
Debido al sistema de programación de C#, no es necesario realizar retrollamadas para volver a ejecutar un bloque (a diferencia de otros lenguajes, como Erlang).
F# es un lenguaje de programación multiparadigma de código abierto para la plataforma .NET, que conjunta la programación funcional con la imperativa y la orientación a objetos. Desarrollado inicialmente por Miguel Tentei Cortés y Dom Syme de Microsoft Research, actualmente está siendo supervisado por la División de Desarrolladores de Microsoft.
Se trata de un lenguaje fuertemente tipado que utiliza inferencia de tipos que, como resultado, no requiere el tipado explícito por parte del programador.
A diferencia de C\#, F\# facilita enormemente la creación de código asíncrono y paralelo, cosa que en otros lenguajes de .NET nos llevaría bastante más tiempo. Además, permite el desarrollo de código basado en agentes.
El código desarrollado para este lenguaje utiliza el mismo paradigma que C#, es decir, programación asíncrona.
A diferencia de en C#, el código se simplifica bastante debido a la existencia de variables asíncronas y un uso de buzones para enviar la información a los clientes conectados.
El principal problema, el cuál coartó el posterior desarrollo completo de la aplicación era el manejo de JSON complejos, y diversos problemas asociados a ello. Por este motivo, el servidor en F# no se finalizó, aunque se dejó todo el código desarrollado en el disco disponible con este trabajo.
Desarrollado por Bjarne Stroustrup en 1979, nace como parte de su tesis doctoral. C++ es un lenguaje de programación imperativo, orientado a objetos,con posibilidad de acceso a instrucciones a bajo nivel y manejo de punteros (debido a ser una extensión de C).
Diseñado principalmente para la programación de sistemas (sistemas embebidos, kernels, ...), provee de una gran performance, eficiencia y flexibilidad, por lo que también se utiliza para el desarrollo de aplicaciones de escritorio, servidores, sistemas críticos o compiladores.
Debido a que las primeras versiones no incluyen diversas funcionalidades mostradas en otros lenguajes posteriores, como Java o C\#, con el paso de los años se han desarrollado otras versiones del lenguaje, como la versión 11, que incluye manejo de funciones lambda o recolección de punteros.
Cuando se empezó a plantear el desarrollo del servidor en C++, se empezó a cuestionar cuál era la mejor opción, ya que los sockets nativos, como en el caso de C, no eran multiplataforma. Por tanto, se decidió el uso de la librería Network de las mismas librerías de SFML utilizadas en los clientes.
En este caso, creamos un escuchador TCP, el cual se encarga de aceptar las nuevas conexiones. Una vez añadidos los nuevos clientes en un mapa, recorremos cada uno de ellos y, según el estado de los sockets, se realiza una acción u otra. En cuanto a los eventos de broadcast, se decidió utilizar threads con un uso de funciones anónimas.
Cabe destacar que debido a que C++ no es un lenguaje centrado en el uso de la concurrencia y las variables compartidas (aunque actualmente se han integrado el uso de funciones lambda), se han utilizado algunos mutex para evitar problemas de condición de carrera.
Twisted es un framework de red desarrollado en Python, basado en el paradigma de la programación dirigida por eventos.
Tiene soporte para varias arquitecturas, como TCP, UDP o Unix domain sockets, un gran número de protocolos (incluidos HTTP o FTP), entre otros.
La idea principal de este framework es separar los distintos eventos en pequeños callbacks predefinidos, de forma que es más fácil separar las tareas complejas.
Entre las aplicaciones más conocidas que utilizan esta herramienta, destacan TwitchTV, Omegle (en sus primeras versiones), o el sistema de alojamiento de archivos de Ubuntu One.
La librería Python Twisted consigue separar de una forma bastante lógica las nuevas conexiones, conexiones perdidas y procesos de broadcast. Para ello, se basa en el uso de factorías, las cuales almacenan las variables globales al sistema. En este caso se ha optado por un modelo de datos muy parecido al desarrollado en Python con threads del punto anterior.
A diferencia del modelo basado en threads, podemos crear variables dinámicamente dentro de la factoría, lo cual facilita bastante la gestión de identificadores de sockets.
Julia es un lenguaje dinámico de alto nivel diseñado para ofrecer un alta performance para computación numérica y científica, además de ser bastante efectivo para programación de propósito general. El nombre del lenguaje Julia fue puesto en honor a Gaston Julia, un matemático francés que descubrió los fractales.
Entre los aspectos distintivos de Julia destaca un sistema de tipado paramétrico en un lenguaje de programación completamente dinámico, además del envío múltiple como paradigma de programación distribuida. Permite programación paralela y computación distribuida, además de acceso directo a bibliotecas de C y Fortran.
Otras particularidades de Julia es el uso de un recolector de basura por defecto, bibliotecas muy eficientes de punto flotante, álgebra lineal, generación de números aleatorios, expresiones regulares y rápidas transformadas de Fourier.
El modelo de desarrollo concurrente propuesto con el lenguaje Julia consiste en un uso intensivo de bloques que se ejecutan de forma asíncrona.
En el servidor desarrollado existe un bloque que es utilizado para aceptar las nuevas peticiones y, cuando un usuario se conecta, se ejecuta otro bloque asíncrono mediante el uso de una corutina (@async) y, de esta forma, conviven en la ejecución del programa tantos hilos o procesos como clientes conectados hubiese.
Debido al uso de la memoria compartida y la inexistencia de variables atómicas, se ha requerido un uso de ReentralLock para evitar la inconsistencia de los datos. Al igual que en el resto de servidores, primeramente intentamos convertir a un objeto JSON el mensaje recibido y, según el valor del campo “Action”, se ejecuta una cantidad de instrucciones específicos.
A pesar de intentar solucionar diversos errores, las últimas versiones de Julia impedían la total ejecución del proyecto. Es por ello por lo que no se pudo llegar a terminar las diferentes acciones y sus respectos tests de rendimiento, ya que ocurrieron bastantes cambios en la API del lenguaje.
Groovy es un potente lenguaje dinámico orientado a objetos con tipado opcional, con capacidad para compilar el tipado estático y dinámico que trabaja sobre la JVM.
Usa una sintaxis muy parecida a Java, compartiendo el modelo de objetos, hilos y seguridad, pero permite usar Groovy tanto de manera dinámica como lenguaje de scripting. También aprovisiona de funcionalidad como DSL o Lenguaje Específico de Dominio.
GPars, por su parte, se trata de una librería para desarollo concurrente basado en Groovy, que provee de hilos, procesos y actores, entre otras funcionalidades.
La arquitectura de desarrollo utilizada para este servidor está basada en actores, con el fin de poder realizar una mejor comparación entre los tres lenguajes principales de la JVM : Scala, Java y Groovy. Debido a que Groovy no tiene actores por defecto (como en el caso de Java que se soluciona con Akka), este problema se soluciona con la librería GPars.
Akka internamente trabaja con Akka IO para proveer de funcionamiento a los actores para funcionar como sockets, pero GPars no contiene esa funcionalidad, se ha tenido que simular usando actores que despachan las acciones de los sockets con estados internos.
IO.JS nace con la privatización del proyecto NodeJS por parte de Joyent.
Se trata de un proyecto Open Source basado en un fork realizado sobre el propio NodeJS, donde los propios usuarios contribuyen para mejorar la plataforma, de forma que se sigue un modelo abierto.
Se trata del mismo código de Node.JS ejecutado con IO.js, por lo que no se produce ningún en código fuente o arquitectura (aunque se ejecuta en este caso un coffeescript compilado)