Comunicación entre procesos de igual a igual (Fase 2).

Motivación

Como ya se dijo en la práctica anterior, queremos implementar un modelo de comunicación entre procesos de igual a igual (peer-to-peer, P2P). Si has completado la práctica anterior ya dispones de un programa que al lanzarse es capaz de operar como un cliente y servidor de modo que puede operar con TCP-sockets tanto iniciando la comunicación, como esperando una nueva por parte de otro cliente.

Supongamos que los procesos P saben hacer cosas (ofrecen servicios a una comunidad de procesos P). Nuestro trabajo ahora es cómo establecer un mecanismo mediante el cual puedan conocerse unos a otros y establecer una comunicación medianamente fiable. Para ello cada proceso P se da a sí mismo un nombre por el que se conocerá en esta comunidad.

Una cuestión que va a quedar sin resolver en esta práctica es el problema del diálogo seguro entre las partes, si bien se van a introducir claves de autenticación, no se presenta más criptografía que ésa, por la dificultad de la tarea.

Las operaciones que se realicen con el Servidor de Contactos se adhieren al modelo "conexión-dialogo-desconexión", de modo que no se ocupan sockets innecesariamente después de haber realizado el diálogo.

Problemas a resolver

El primer contacto del proceso P con el Servidor de Contacto

El soporte de contacto lo va a proporcionar un proceso que vamos a denominar Servidor de Contacto (SC). Cada proceso P que quiera entrar en esta comunidad, deberá notificarlo en primer lugar al servidor de contacto. Este se encargará de resolver varios problemas:

  1. Verificar que no haya más de un proceso P con el mismo nombre propio.
  2. Asignar a cada proceso P de la misma máquina un puerto de escucha diferente para su servidor privado.
  3. Proporcionar una clave que será válida para la vida útil del proceso P y que impida que otro proceso le suplante.

Como es lógico, para llevar estas cuentas, el Servidor de Contacto dispondrá de un archivo sobre el que se irá escribiendo la lista de procesos P que estarán registrados en el sistema, junto con su IP, su puerto de conexión, su clave de contacto con el S.C. y demás información importante.

Nombre IP           Port IDPrivado_P_SC (otra información)
pepe   195.88.15.22 1501 aakkde993mnkk3 (impres, música, SMS)
juan   195.88.15.22 1502 33e9jdd99lkd   ()
...

Como es de esperar que el S.C. será un servidor multihilo, deberá proporcionarse los mecanismos de bloqueo de archivo sobre esta tabla; que por otra parte son muy elementales.

Las primitivas de contacto quedarían como sigue:

  1. P -> SC

EXISTO:miIP:miNombre

Con esta primitiva el proceso P notifica que existe y desde qué IP se está conectando (miIP). Además se indica el nombre propio del proceso P (miNombre).

  1. SC -> P

EXISTES:puertoServicio:IDPrivado_P_SC

Con esta primitiva el SC le indica al proceso P que todo va bien y que su puerto de servicio es el que se le dice (puertoServicio). El proceso P arrancará el servidor en este puerto. El SC proporciona este puerto de modo que no colisione (en la medida de lo posible) con el puerto de otro proceso que pudiera estar registrado en la misma máquina.

E_EXISTES:#:Mensaje de Error

donde # indica: 0-> ya existe otro proceso con ese nombre.   1-> ya existe otro proceso con ese nombre en tu misma IP. 2-> no has especificado el nombre.

El mensaje de error viene muy bien para imprimirlo en el cliente para depurar el código.

Informando al servidor de lo que sabe hacer el proceso P

El proceso P podrá informar al servidor de los servicios que implementa, con el fin de que otro proceso cualquiera sea capaz de encontrar en él lo que necesita. Los servicios tendrán el nombre de una sola palabra, como "imprimir", "e-mail", "cantar", ... y no es cuestión de este sistema dar cuenta de lo que cada proceso P puede ofrecer ahora o en el futuro (ya tenemos bastante problema con lo nuestro).

  1. P -> SC

    SEHACER:funcionNueva:IDPrivado_P_SC

    Con esta primitiva el proceso P se identifica frente al SC mediante su IDPrivado_P_SC y le indica que añada esta funcionNueva a la lista de funciones de las cosas que el proceso P sabe hacer.

  2. SC -> P

RECAHES:IDPrivado_P_SC

Con esta primitiva el SC le dice al proceso P que se ha incorporado la nueva función a la lista.

E_RECAHES:0

No eres un proceso P registrado en el sistema o la clave no es válida, por lo que no puedo hacer lo que me dices.

E_RECAHES:#:Mensaje de error:IDPrivado_P_SC

Donde # es un valor entero distinto de 0 que significa: 1-> la funcionNueva que me has indicado ya está incluida en tu lista.

El proceso P, también puede dejar de saber hacer cosas. De este modo será necesario incluir alguna primitiva que nos permita eliminar funciones del conocimiento público.

  1. P -> SC

NOSEHACER:funcionAntigua:IDPrivado_P_SC

Con esta primitiva el proceso P se identifica frente al SC mediante su IDPrivado_P_SC y le indica que elimine esta funcionAntigua de la lista de funciones de las cosas que el proceso P sabe hacer.

  1. SC -> P

RECAHESON:IDPrivado_P_SC

Con esta primitiva el SC le dice al proceso P que se ha eliminado la función a la lista.

E_RECAHESON:0

No eres un proceso P registrado en el sistema o la clave no es válida, por lo que no puedo hacer lo que me dices.

E_RECAHESON:#:Mensaje de error:IDPrivado_P_SC

Donde # es un valor entero distinto de 0 que significa: 1-> la funcionAntigua que me has indicado no está incluida en tu lista.

Informando al proceso P de lo que sabe hacer la comunidad

La comunidad, entendiendo por comunidad cualquier proceso que esté registrado, necesita conocer a sus semejantes para poder establecer contacto. esto se realizará mediante tres primitivas:

  1. Obtener un listado de los nombres de los procesos P que están funcionando en la comunidad y que son capaces de ofrecer cierta "función"
  2. Obtener un listado de las funciones que puede ofrecer un cierto proceso P dado su "nombre".
  3. Obtener un listado completo de "nombres" y "funciones" de la comunidad.

Para ellos diseñamos tres primitivas:

Primitiva NDIR (1)

  1. P -> SC

NDIR:IDPrivado_P_SC

Pide una lista de nombres de los procesos P que están actualmente activos en la comunidad.

  1. SC -> P

RIND:(nombre1, nombre2, ...):IDPrivado_P_SC

Proporciona una lista de nombres (nombre1, nombre2, ...) de la comunidad.

E_RIND:0

No eres un proceso P registrado en el sistema o la clave no es válida, por lo que no puedo hacer lo que me dices.

E_RIND:#:Mensaje de error:IDPrivado_P_SC

Donde # es un valor entero distinto de 0 que significa: (no se me ocurre nada ahora)

 

Primitiva FDIR (1)

  1. P -> SC

NDIR:nombre:IDPrivado_P_SC

Pide al SC la lista de funciones de que es capaz cierto proceso P, cuyo nombre es "nombre".

  1. SC -> P

RIDF:(función1, función2, ...):IDPrivado_P_SC

Proporciona una lista de funciones (función1, función2, ...) propias del proceso P indicado anteriormente.

E_RIDF:0

No eres un proceso P registrado en el sistema o la clave no es válida, por lo que no puedo hacer lo que me dices.

E_RIDF:#:Mensaje de error:IDPrivado_P_SC

Donde # es un valor entero distinto de 0 que significa: (no se me ocurre nada ahora)

Primitiva DIR (3)

  1. P -> SC

DIR:IDPrivado_P_SC

Pide al servidor central una lista con todos los procesos P alojados en la comunidad, junto a sus funcionalidades.

  1. SC -> P

RID:(nombre;funcion1, funcion2, ...+nombre;funcion1, funcion2, ...):IDPrivado_P_SC

Proporciona una lista de nombres de procesos P, cada uno de los cuales va seguido de una lista de sus funciones.

E_RID:0

No eres un proceso P registrado en el sistema o la clave no es válida, por lo que no puedo hacer lo que me pides.

E_RID:#:Mensaje de error:IDPrivado_P_SC

Donde # es un valor entero distinto de 0 que significa: (no se me ocurre nada ahora mismo)

Solicitando una conexión con un proceso P

La única entidad autorizada para realizar las conexiones es el Servidor de Contactos, de hecho, ningún proceso P conoce la dirección IP ni el puerto de comunciación con cualquier otro proceso P hasta que lo ha solicitado al Servidor de Contactos. Más aun, el Servidor de Contactos se encarga de proporcionar una clave privada para los dos procesos que se van a comunicar (IDPrivada_P_P'). Esto tiene las siguientes ventajas:

  1. El Servidor de Contactos va a contener un registro histórico de las conexiones que se han establecido (archivo de conexiones).
  2. El Servidor de Contactos va a poder (en su caso) filtrar las conexiones.
  3. El secreto compartido (IDPrivada_P_P') va a funcionar como clave de autenticación de un proceso P frente a otro, y con ello se evita cualquier suplantación.

Para implementarlo necesitamos una primitiva.

  1. P -> SC

CONECTA:nombre':IDPrivado_P_SC

Pide al servidor central una conexión con el proceso P nombre'.

  1. SC -> P'

INVITA:nombre:IDPrivado_P_P':IDPrivado_P'_SC

Indica al proceso P' que hay un proceso P llamado nombre que va a iniciar una conexión con él, y compartiendo el secreto IDPrivado_P_P'.

  1. SC -> P

ATCENOC:IP:Puerto:IDPrivado_P_P':IDPrivado_P_SC

Proporciona al proceso solicitante el IP y el Puerto de servicio del proceso solicitado, junto a un secreto compartido (IDPrivado_P_P') que acompañará a todas las conversaciones con el segundo proceso P.

E_ATCENOC:0

No eres un proceso P registrado en el sistema o la clave no es válida, por lo que no puedo hacer lo que me pides.

E_ATCENOC:#:Mensaje de error:IDPrivado_P_SC

Donde # es un valor entero distinto de 0 que significa: 1-> en este momento no hay ningún proceso P que se llame "nombre". 2-> El proceso P llamado "nombre" no ofrece ninguna funcionalidad.

  1. P -> P'

HOLA:IDPrivado_P_P'

El proceso P ha conectado un socket con el servidor de P' y se identifica frente a él. Si el diálogo tiene éxito se conserva, si no tiene éxito, la conexión se rompe, no sin antes enviarle al proceso P algún tipo de mensaje de reconocimiento o mensaje de error.

Para que el diálogo tenga éxito, el proceso P debe emplear la clave correcta (IDPrivado_P_P').

  1. P' -> P

ALOH:IDPrivado_P_P'

El proceso P' debe contestar con la clave correcta (IDPrivado_P_P'). Si no es así, el proceso P tiene la posibilidad de romper la conexión sin despedirse.

Saliendo de la comunidad de procesos P

Al salir de la comunidad de procesos, hay que realizar una pequeña visita al Servidor de Contactos para que él nos dé de baja en sus tablas, consecuentemente se puede disponer de su puerto, y se invalida el secreto compartido IDPrivado_P_SC. Esta operación tiene consecuencias laterales:

  1. Se invalidan todos los secretos compartidos P_P que provengan del proceso P que abandona la comunidad.
  2. En consecuencia, todas las conexiones P_P que provengan de este proceso P deben finalizarse, si es que está vigente alguna conexión (lo normal es que el proceso P se haya desconectado antes de sus amigos).

Para implementarlo necesitamos una primitiva.

  1. P -> SC

ADIOS:IDPrivado_P_SC

Despide el proceso P de su comunidad.

  1. SC -> todo P' conectado con P

DESPIDE:nombre:ID_Privado_P_P':IDPrivado_P'_SC

Indica al proceso P' conectado con P (nombre) que este último cesa en su conexión con la comunidad. A partir de ese momento, los secretos compartidos entre P y el proceso P 'ID_Privado_P_P' dejan de ser válidos.

Es de suponer que el proceso P debe haberse despedido de P' anteriormente, pero si no fuera así se desconectará obligatoriamente.

  1. P' -> SC

EDIPSED:nombre:IDPrivado_P'_SC

El proceso P (nombre) ha sido desconectado

En el caso de que IDPrivado_P'_SC no sea válido el proceso P' no deberá desconectarse, y tampoco deberá responder al SC (como es lógico).

  1. SC -> P

SOIDA:ID_Privado_P_SC

Queda confirmado que el proceso P se ha desligado de la comunidad, y que todos los procesos con los que hubiera contactado han eliminado su clave ID_Privado_P'_P, incluida ID_Privado_P_SC. Esta primitiva se utiliza solo cuando el SC se ha cerciorado de la despedida colectiva.

E_SOIDA:0

No eres un proceso P registrado en el sistema o la clave no es válida, por lo que no puedo hacer lo que me pides.


Epílogo

En principio estas son las primitivas propuestas. Agradezco sinceramente que detecteis algún error apreciable y me lo comuniqueis para poder corregir las especificaciones.

El resto de la práctica es equivalente a la práctica 2. (FASE 1)

Entre otras sugerencias interesantes que se me ocurren estaría: