Practica5 Comunicacion sockets - raquelcedazo/sistemas-informaticos-industriales GitHub Wiki
Antes de comenzar con el desarrollo de la práctica 5, crea una etiqueta en el repositorio con el mensaje 'Fin de la práctica 4' para marcar el hito (si no lo habías hecho previamente).
A continuación, para la realización de la práctica 5, crea una rama y salta a dicha rama:
$ git branch practica5
$ git checkout practica5
Recuerda hacer el merge con la rama principal cuando termines la práctica.
Esta práctica tiene como objetivo desarrollar una versión del juego distribuida, utilizando los servicios proporcionados en POSIX para el manejo de sockets.
El esquema propuesto de los procesos para la realización de la práctica es el siguiente:

Tanto el envío de coordenadas del servidor al cliente como de las teclas pulsadas del cliente al servidor, se realizan a través de un socket.
En primer lugar, hay que eliminar las tuberías entre cliente y servidor de la anterior práctica como indica el esquema anterior.
Para esta práctica se proporciona una clase Socket en C++ que encapsula todas las funcionalidades necesarias para cliente y servidor. Copia los ficheros Socket.cpp y Socket.h de tu directorio external e inclúyelos en tu proyecto. Recuerda modificar el fichero CMakeLists.txt para incluirlos en el proceso de compilación.
Utilizando la API proporcionada por la clase Socket, modifica el código del servidor y del cliente para incorporar los siguientes cambios.
Los cambios en el servidor son:
- Debe declarar dos sockets diferentes, uno para la conexión y otro para la comunicación con el cliente.
- El servidor enlaza el socket de conexión a una dirección IP y un puerto (siendo la IP la de la máquina en la que corre el servidor).
- Se queda a la espera de una conexión escuchando en ese puerto.
- Cuando el cliente se conecta, el servidor se desbloquea y devuelve el socket para la comunicación con el cliente, que es por el que se envían y reciben los datos.
- El servidor recibe el nombre del cliente conectado a través del socket y lo imprime por pantalla.
- El servidor envía las coordenadas a través del socket al cliente en el OnTimer.
- El hilo que recibe los comandos del cliente es el que recibe los datos de las teclas a través del socket.
Los cambios en el cliente son:
- Debe declarar un socket para la comunicación con el servidor.
- El cliente pide por teclado un nombre para enviárselo al servidor.
- El cliente se conecta a la IP y puerto del servidor.
- Envía el nombre a través del socket.
- El cliente recibe las coordenadas a través del socket en el OnTimer.
- El cliente envía la tecla a través del socket en el método OnKeyboardDown.
Nota: Los programas logger y bot deben seguir funcionando correctamente siguiendo la especificación de las prácticas anteriores.
En este caso se ejecutarán las dos aplicaciones (cliente y servidor) en la misma máquina utilizando la dirección local 127.0.0.1.
Después, se puede probar en parejas. Para ello uno de los miembros de la pareja deberá ejecutar en su equipo los programas servidor y logger, mientras que el otro ejecutará los programas cliente y bot.
Previamente a la ejecución, configura correctamente en el código la IP y puerto de escucha del servidor (utiliza el mandato ifconfig para ver la IP de las máquinas).
El objetivo de este último ejercicio es permitir que jueguen dos clientes, uno con una raqueta distinta.
- Puesto que la aceptación de nuevos clientes es bloqueante, el servidor siempre tiene que estar ejecutando el accept() si se quiere que los clientes puedan conectarse. El hilo se crea en el momento del accept() y se le pasa el índice del cliente.
- La recepción de las teclas de los clientes no deben bloquear al servidor. Por tanto, la solución es crear un hilo por cada cliente conectado para recibir sus comandos.
Para permitir la conexión simultánea de varios clientes, es necesario mantener un socket por cada uno de dichos clientes. Para ello se declara en la clase CMundo de MundoServidor un vector de la STL de objetos de la clase Socket. Utilizar un vector STL permite añadir más cómodamente nuevos objetos, quitarlos y recorrer el vector de forma sencilla. Se sugiere implementar un método GestionaConexiones() que se encarga de realizar dicha gestión. De ese modo se tendría:
class CMundo
{
public:
...
Socket servidor;
std::vector<Socket> conexiones;
std::vector<std::string> nombres;
void GestionaConexiones();
void RecibeComandosJugador(int indice);
...
};
El método GestionaConexiones() deberá:
- Ejecutarse en un hilo independiente.
- Permanecer en un bucle infinito invocando a accept().
- Cada vez que se conecte un cliente: recibir su nombre, almacenar el socket y el nombre en los vectores correspondientes, crear un hilo que ejecute el método RecibeComandosJugador(int indice), pasando como parámetro el índice del cliente dentro del vector.
El índice del cliente permitirá identificar qué socket utilizar y qué raqueta controla dicho cliente.
void* hilo_conexiones(void* d)
{
CMundo* p = (CMundo*) d;
p->GestionaConexiones();
return NULL;
}