Peer2.net

Peer2.net (https://github.com/lontivero/peer2net) es una librería muy liviana y sencilla de usar orientada a la creación de aplicaciones peer to peer en .net utilizando el protocolo tcp. El punto es que la programación con sockets, y en particular con tcp, introduce algunas complejidades que bien pueden abstraerse y que enumero a continuación:

  • Desempeño: Actualmente existen tres alternativas para utilizar sockets en .net, cada uno con sus pros y contras:
    • Llamadas bloqueantes: Connect, Send, Receive son métodos bloqueantes, es decir que una vez invocados, detienen la ejecución del hilo y esperan que se complete la conexión, el envío o el recibo de datos. Por este motivo, si queremos mantener comunicación, enviando y recibiendo datos desde y hacia distintos peers, debemos utilizar threads y sincronizar los datos compartidos. La vida es mucho más sencilla cuando se evitan los threads pero además este modelo no escala porque 100 peers significan 100 thread y los constantes cambios de contexto y las esperas producidas por la exclusión mutua degradan la performance de la aplicación por lo que no es recomendable  en aplicaciones con muchas conexiones como lo son las pee to peer.
    • Llamadas asíncronas (Asynchronous Programming Model o APM): Este modelo está en .net desde siempre y es muy popular aún hoy, consta de los pares de métodos BeginXXX/EndXXX en los que un callback es invocado cuando la conexión se establece, se han enviado o recibido los datos. No bloquean el hilo de ejecución pero el callback es invocado desde un hilo distinto al que ha invocado al BeginXXX por lo que cierta sincronización es necesaria. Además, a partir de la versión 4 del framework .net, podemos simplificar su utilización con TaskFactory.FromAsync lo que lo vuelve más sencillo y elegante a la vez. Esta alternativa es muy buena y mucho proyectos de software  muy populares lo utilizan (RavenDb es un ejemplo). No obstante, el desempeño ha sido mejorado en la versión 3.5 con los sockets de alto desempeño.
    • Clase SocketAsyncEventArgs: El framework 3.5 introduce un nuevo patrón alternativo para el manejo de sockets especialmente diseñado para aplicación que requieren alto desempeño mediante la clase SocketAsyncEventArgs. Este patrón, si bien más sencillo de utilizar que los anteriores, no está libre de detalles que son buenos de abstraer. Como por ejemplo el manejo del pool de instancias de la clase SocketAsyncEventArgs y su reutilización. Peer2.Net utiliza este patrón.
  • Memoria:
    • Fragmentación: Cómo ya escribiera en mi artículo anterior creando nuestro propio administrador de memoria en c#, las continuas operaciones con sockets que requieren la asignación de buffers fragmentan la memoria (mucho más de lo usual) a la vez que fijan esos segmentos de memoria para evitar que el garbage collector los relocalice (puesto que el sistema operativo los está utilizando). Todo esto hace que el GC no pueda hacer su trabajo con la misma eficiencia que normalmente lo hace por lo que una ayuda por parte de los desarrolladores es bienvenida (aunque no necesaria). Peer2.Net implementa su propio administrador de memoria.
    • Limitar el uso: muchas veces nos encontramos con escenarios en los que deseamos tener cierto control sobre el consumo de memoria, las razones pueden ser de distinta índole y suele ser complicado de implementar.  Peer2.Net es capaz de limitar el uso de los buffers de modo tal que si un socket necesita memoria para recibir datos pero no disponde un buffer libre lo suficientemente grande, postone la ejecución hasta que los otros sockets liberan tanta memoria como este necesita, y en tal caso ejecuta la acción.
    • Reuso de objetos: en un entorno en donde potencialmen cientos de sockets realizan cientos de operaciones por segundo, es importante reducir la creación de nuevos instancias de aquellos objetos que se utilizan con mayor frecuencia. Si bien .net es extremadamente eficiente en este punto, es decir, es capaz de crear objetos de manera sorprendentemente eficiente, existen dos consideraciones especiales por el que la utilización de pooles de objetos tiene sentido, la primera razón es que el nuevo patrón asíncrono de sockets para aplicaciones de alto desempeño tiene implicitamente el requerimiento de la reutilización de las instancias de la clase  SocketAsyncEventArgs. La segunda razón para reutilizar instancias es que, si bien la creación de objetos es realmente eficiente, su creación requiere de más memoria y trabajo extra de destrucción y relocalización por parte del garbage colector. Peer2.Net hace uso intensivo de la reutilización de algunos objetos.
  • Ancho de banda: una aplicación peer to peer que no hace control de ancho de banda, esto es, restringir cuantos bytes pueden enviar/recibir a/desde los peers por unidad de tiempo, puede terminar consumiendo la totalidad del ancho de banda disponible e impedir que el usuario continúe trabajando con normalidad.
    • Medición: Peer2.Net lleva la cuenta de cuantos bytes se han enviando/recibido desde que se estableció la conexión como así también la velocidad promedio y la velocidad actual de transferencia. Estos datos son además utilizados para controlar la velocidad.
    • Control: Peer2.Net permite controlar (limitar) la velocidad de transferencia para que esta se ubique en el valor deseado. Esto es posible hacerlo conexión por conexión y subida y bajada independientemente. Es decir, suponiendo que estamos conectados a dos peers (A y B), podemos establecer que queremos enviar a 10Kb/s al peer A y a 50Kb/s al peer B mientras que queremos recibir a 200kb/s desde el peer A y a 1Kb/s desde el peer B.
  • Transmission Control Protocol: Tcp introduce también cierta complejidad ya que no garantiza que una operación de envío por parte de un nodo requiera una operación de recibo en nodo receptor. Esto es, si enviamos ‘hola’ a otro nodo con una sola operación de envío, puede que al destinatario le llegue ‘hola’, ‘hol’, ‘ho’ u ‘h’ por lo que necesitamos realizar tantas operaciones de lectura como sean necesarias. Peer2.Net maneja esto de manera automática repitiendo la operación de recibir hasta obtener la cantidad de bytes solicitados.
  • API simplificada: el API de Peer2.Net consiste básicamente de los métodos Connect, Send y Disconnect para conectarse a otro nodo, enviar datos y desconectarse del mismo. Por otra parte, expone callback DataReceived y Disconnected para notificar sobre la llegada de datos o la desconexión de un nodo. Otros callback notifican de errores al intentar conectarse o enviar datos. Esto permite la creación de un chat en poco más de 100 LOC.
  • Thread: Peer2.Net invoca los callback ya mencionados arriba de manera sequencial por lo que no debemos preocuparnos de los problemas de concurrencia que típicamente dominan la programación multi-hilo. En realidad, Peer2.Net utiliza una cola para sincronizar los thread que maneja internamente con el thread del usuario.

Arquitectura

El siguiente diagrama muestra dos de los componentes centrales del proyecto con los que interactuan las aplicaciones:

CommunicationManager

  • Listener: es el encargado de escuchar las solicitudes de conexión entrantes. Cuando una nueva conexión es establecida este componente notifica mediante un evento (ConnectionRequested) a sus subscriptores sobre la nueva conexión de manera asíncrona (para poder reanudar inmediatamente la escucha).
  • ConnectionManager: es el encardado administrar las conexiones y las operaciones de conexión, desconexión, envio y recibo de datos como así también llevar las estadísticas de bytes enviados y recibidos y, la velocidad de envio/recibo de cada conexión como así también la global. Además es el dueño del hilo principal en el que se realizan las operaciones de lectura escritura.

 

El trabajo de más bajo nivel de coneción, envio y recepción de mensajes es realizado por la clase ConnectionIOActor (no es un actor model realmente). Esta es la que se encarga de la asignación de buffers, de limitar el ancho de banda, de los timeouts de las conexiones, de realizar las suscesivas operaciones de envio/recepción de mensajes mediante la administración de las colas de envio y recepción. 

BufferAllocator, por su parte, es el dueño del array de bytes que se utilizará como buffer y se encarga del asignamiento y liberación de segmentos del mismo para ser utilizado por los sockets.

ConnectionIo

El framework lleva varias métricas sobre lo que sucede con cada conexión y las pone a disposición mediante la clase Peer. El diagrama de abajo es bastante autoexplicativo.

peer

 

Ejemplo (Clásico chat)

Para ejemplificar el uso de Peer2.Net, he creado un pequeño chat (un ejemplo no muy original, lo sé) el cual requiere de tan solo 100 líneas de código y puede verse en la imagen de abajo.

chat

Finalizando

Peer2.Net simplifica notablemente el desarrollo de aplicaciones que necesitan conectarse de manera ‘igualitaria’ como lo son la aplicaciones peer to peer abstrayendo muchas de las complejidades que presentan el desarrollo con sockets, gracias a una interface ultrasencilla, a la vez que permite la transmición de bytes en crudo.

Peer2.Net está siendo desarrollado en mis momentos libres (que son escasos) por lo que cualquier feedback o ayuda es bienvenida 😉

Sin categoría

One thought on “Peer2.net

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *