Upload
vanvan
View
8
Download
0
Embed Size (px)
Citation preview
5/21/2018 47508930-ProgramacionAvanzada
1/157
Diego Rodrguez-Losada GonzlezPablo San Segundo Carrillo
Programacin Avanzada,
Concurrente y Distribuida
5/21/2018 47508930-ProgramacionAvanzada
2/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 2
Universidad Politcnica de Madrid -UPM
5/21/2018 47508930-ProgramacionAvanzada
3/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 3
Universidad Politcnica de Madrid -UPM
PRLOGO 7
PARTE I. Desarrollo de una aplicacin distribuida yconcurrente en LINUX
11.. EDICIN, COMPILACIN, Y DEPURACIN DE UNA APLICACIN C/C++ BAJO LINUX 11
1.1. INTRODUCCIN 111.2. LOGIN EN MODO TEXTO 121.3. MANEJO DE ARCHIVOS Y DIRECTORIOS EN MODO TEXTO. 121.4. EL EDITOR DE TEXTO 151.5. DESARROLLO C/C++EN LINUX EN MODO TEXTO 151.6. EL PROCESO DE CREACIN DE UN EJECUTABLE 161.7. LAS HERRAMIENTAS DE DESARROLLO 171.8. EL COMPILADOR GCC 171.9. MAKEFILE Y LA HERRAMIENTA MAKE 19
1.10. TIPOS DE ERROR 201.11. DEPURACIN DE LA APLICACIN. 211.12. CREACIN DE UN SCRIPT 221.13. DESARROLLO EN UN ENTORNO GRAFICO 231.14. EJERCICIO PRCTICO 241.15. EJERCICIO PROPUESTO 25
22.. INTRODUCCIN A LOS SISTEMAS DISTRIBUIDOS. COMUNICACIN POR SOCKETS 27
2.1. OBJETIVOS 27
2.2. SISTEMA DISTRIBUIDO 282.3. SERVICIOS DE SOCKETS EN POSIX 292.3.1 PROGRAMA CLIENTE 302.3.2 SERVIDOR 322.4. ENCAPSULACIN DE UN SOCKET EN UNA CLASEC++ 352.4.1 ENVO DE MLTIPLES MENSAJES 362.4.2 CONEXIONES MLTIPLES. 382.5. ESTRUCTURA DE FICHEROS 422.6. TRANSMITIENDO EL PARTIDO DE TENIS 442.6.1 CONEXIN 442.6.2 ENVO DE DATOS 45
2.7. EJERCICIOS PROPUESTOS 45
5/21/2018 47508930-ProgramacionAvanzada
4/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 4
Universidad Politcnica de Madrid -UPM
33.. COMUNICACIONES Y CONCURRENCIA 47
3.1. INTRODUCCIN 473.2. REQUISITOS 493.3. FUNCIONAMIENTO DE GLUT 49
3.3.1 LANZANDO UN HILO 503.4. ESTRUCTURA DEL SERVIDOR 513.5. MLTIPLES CONEXIONES SIMULTANEAS 523.6. MOSTRAR LOS CLIENTES CONECTADOS 533.7. RECEPCIN COMANDOS MOVIMIENTO 553.8. GESTIN DESCONEXIONES 563.9. FINALIZACIN DEL PROGRAMA 563.10. EJERCICIO PROPUESTO 57
44.. COMUNICACIN Y SINCRONIZACIN INTERPROCESO 59
4.1. INTRODUCCIN 594.2. EL PROBLEMA DE LA SINCRONIZACION 604.3. COMUNICACIN INTERPROCESO 614.4. TUBERAS CON NOMBRE 624.5. MEMORIA COMPARTIDA 644.6. EJERCICIOS PROPUESTOS 68
PARTE II. Programacin avanzada
55.. PROGRAMACIN DE CDIGO EFICIENTE 73
5.1. INTRODUCCIN 735.2. MODOS DE DESARROLLO 775.3. TIPOS DE OPTIMIZACIONES 775.4. VELOCIDAD DE EJECUCIN 785.5. ALGUNAS TCNICAS 795.5.1 CASOS FRECUENTES 795.5.2 BUCLES 805.5.3 GESTIN DE MEMORIA 835.5.4 TIPOS DE DATOS 855.5.5 TCNICAS EN C++ 865.6. CASOS PRCTICOS 875.6.1 ALGORTMICA VS.MATEMTICAS 875.6.2 GENERACIN DE NMEROS PRIMOS 885.6.3 PRE-COMPUTACIN DE DATOS 905.7. OBTENIENDO PERFILES (PROFILING)DEL CDIGO 935.8. CONCLUSIONES 95
5/21/2018 47508930-ProgramacionAvanzada
5/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 5
Universidad Politcnica de Madrid -UPM
66.. SERIALIZACIN DE DATOS 97
6.1. INTRODUCCIN 976.2. REPRESENTACIN OBJETOS EN MEMORIA 1026.3. SERIALIZACIN EN C 103
6.3.1 CON FORMATO (TEXTO) 1046.3.2 SIN FORMATO (BINARIA) 1046.4. SERIALIZACIN EN C++ 1076.4.1 CON FORMATO (TEXTO) 1086.4.2 SIN FORMATO (BINARIA) 1116.5. CONCLUSIONES 112
77.. BSQUEDAS EN UN ESPACIO DE ESTADOS MEDIANTE RECURSIVIDAD 113
7.1. INTRODUCCIN 113
7.2. BSQUEDA PRIMERO EN PROFUNDIDAD 1157.2.1 TERMINOLOGA 1167.2.2 ESTRUCTURAS DE DATOS 1167.2.3 ANLISIS 1177.3. BSQUEDA PRIMERO EN ANCHURA 1197.4. METODOLOGA GENERAL DE RESOLUCIN DE UN PROBLEMA DE BSQUEDA MEDIANTE COMPUTACIN 1207.5. IMPLEMENTACIN DE UNA BSQUEDA DFSMEDIANTE RECURRENCIA 1217.5.1 LA PILA DE LLAMADAS 1227.5.2 BSQUEDA DFSCOMO RECURSIN 124
88.. EJECUCIN DISTRIBUIDA DE TAREAS 133
8.1. INTRODUCCIN 1338.2. EL PROBLEMA DE LAS N-REINAS 1348.2.1 HISTORIA 1348.2.2 CARACTERSTICAS 1358.2.3 2ESTRUCTURAS DE DATOS 1368.3. IMPLEMENTACIN CENTRALIZADA 1388.3.1 DESCRIPCIN 1398.3.2 ESTRUCTURAS DE DATOS 1408.3.3 CONTROL DE LA BSQUEDA 141
8.3.4 ALGORITMO DE BSQUEDA 1458.4. IMPLEMENTACIN DISTRIBUIDA 1478.4.1 ARQUITECTURA CLIENTE-SERVIDOR 1478.4.2 PROTOCOLO DE COMUNICACIN 1488.4.3 IMPLEMENTACIN DEL CLIENTE 1488.5. IMPLEMENTACIN DEL SERVIDOR 1538.5.1 COMUNICACIN CON EL CLIENTE 153
5/21/2018 47508930-ProgramacionAvanzada
6/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 6
Universidad Politcnica de Madrid -UPM
5/21/2018 47508930-ProgramacionAvanzada
7/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 7
Universidad Politcnica de Madrid -UPM
PRLOGOGeneralmente la formacin en informtica de un ingeniero (industrial,
automtica, telecomunicaciones o similar) comienza por la programacin estructurada,en lenguajes como C o Matlab, y luego se complementa con Programacin Orientada aObjetos (POO) e Ingeniera del Software, con Anlisis y Diseo Orientados a Objetos,UML, etc.
Sin embargo, existen una serie de tcnicas y tecnologas software que escapan
del alcance de los anteriores cursos. La programacin de tareas concurrentes, lossistemas distribuidos, la programacin de cdigo eficiente o algortmica avanzada sontemas que quedan a menudo relegados, y sin embargo son muy necesarios en tareasde ingeniera industrial, comunicaciones y similares.
Este libro trata de cubrir dichos aspectos, de una manera prctica y aplicada. Laprimera parte desarrolla una aplicacin grfica distribuida: un tpico juego decomputador en red. En esta aplicacin se requiere el uso de comunicaciones por red(con sockets), as como la utilizacin de tcnicas de programacin concurrente conmulti-proceso y multi-hilo, de una manera que esperamos que sea atractiva y
motivadora para el lector. El desarrollo se realiza en Linux (Posix), presentando unaintroduccin al manejo bsico, desarrollo y depuracin con herramientas GNU comog++, make y gdb. El cdigo de soporte para estos captulos se encuentra enwww.elai.upm.es
La segunda parte cubre algunos tpicos genricos avanzados como laprogramacin de cdigo eficiente, la serializacin de datos, la recurrencia o lacomputacin distribuida, tpicos que muchas veces estn ntimamente relacionadoscon los anteriores.
http://www.elai.upm.es/http://www.elai.upm.es/http://www.elai.upm.es/5/21/2018 47508930-ProgramacionAvanzada
8/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 8
Universidad Politcnica de Madrid -UPM
5/21/2018 47508930-ProgramacionAvanzada
9/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 9
Universidad Politcnica de Madrid -UPM
Parte I. Desarrollo deuna aplicacindistribuida y
concurrente en LINUX
5/21/2018 47508930-ProgramacionAvanzada
10/157
5/21/2018 47508930-ProgramacionAvanzada
11/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 11
Universidad Politcnica de Madrid -UPM
11.. EDICIN,COMPILACIN,YDEPURACIN DE UNA APLICACIN C/C++
BAJO LINUX
1.1.INTRODUCCINEn este primer tema realizamos una aproximacin al SO operativo linux, y
fundamentalmente al desarrollo de aplicaciones en C/C++, desarrolladas, depuradas yejecutadas en un computador con Linux. Aunque el objetivo de este curso es elaprendizaje de programacin concurrente y sistemas distribuidos, en este primer temanos ceiremos al trabajo de desarrollo convencional en linux, para aprender tanto eldesarrollo sin interfaz grafica de ventanas, como algunas de las herramientas graficas.Tambin se manejaran algunos comandos o mandatos bsicos de linux para crear,editar y manejar archivos, y se introducir el uso de las herramientas de desarrollobsico como son gcc, g++, make y gdb.
Este tema comienza por la descripcin de los comandos bsicos para trabajaren modo texto, para despus desarrollar y depurar una pequea aplicacin ejemplo enmodo texto. Por ultimo, se trabajara en modo grafico, completando un cdigo yaavanzado para terminar con el juego del tenis que funcione en modo local, para dos
jugadores, esto es, los dos jugadores utilizan el mismo teclado y la misma pantalla.
5/21/2018 47508930-ProgramacionAvanzada
12/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 12
Universidad Politcnica de Madrid -UPM
Figura 1-1. Objetivo del captulo: Desarrollo del juego del Tenis en modo local
1.2.LOGIN EN MODO TEXTOAunque el computador arranque en modo grafico, la primera parte de esta
prctica se va a desarrollar en modo texto. Para ello cmbiese del terminal grafico alprimer terminal de texto, mediante la correspondiente combinacin de teclas(Ctrl+Alt+F1)
Entrar en la cuenta de usuario correspondiente. Consejo: Aunque dispongas de
la contrasea de administrador es absolutamente recomendable no utilizarla paratrabajar normalmente. En caso de que seas el administrador del sistema, crea unacuenta de usuario normal para realizar la prctica.
Probar a realizar el login en los distintos terminales virtuales (saliendo luegocon el comando exit de los que no se vayan a utilizar)
1.3.MANEJO DE ARCHIVOS Y DIRECTORIOS EN MODO TEXTO.Para familiarizarse con el manejo de archivos y directorios en linux se va a crear
la siguiente estructura de archivos, en la que los archivos de texto contienen el textoHola que tal:
/home/usuario/
|------------->carpeta1
| |------->subcarpeta11
| | |------->archivo11.txt
| |------->archivo1.txt
|------------->carpeta2|------->archivo2.txt
5/21/2018 47508930-ProgramacionAvanzada
13/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 13
Universidad Politcnica de Madrid -UPM
Utilizar y explorar los comandos y opciones siguientes:
Tabla 1-1. Comandos bsicos consola linux
Comando Accin Opciones
pwd muestra el directorio actual
ls muestra el contenido del
directorio actual
-a (muestra todos los
archivos, incluidos ocultos)
l, muestra detalles de los
archivos
mkdir [directorio] crea el directorio con el
nombre dado
cd [ruta] cambia al directorio que
indica la ruta
correspondientecat [fichero] concatena el fichero a salida
estndar
- significa entrada
estndar. Para crear un
archivo se puede
redireccionarla de la
siguiente forma cat -
>nombre_fichero.txt
chmod usuario+permiso
[fichero]
cambia los permisos
(r=read, w=write,
x=execute) a usuario (a=all,
o=others, u=user, g=group)
rm [archivo] Borra el archivo -r = borra recursivamente el
directorio seleccionado
(OJO, usar con mucha
precaucin)
cp [origen] [destino] Copia el archivo o archivos
origen al destino
seleccionado
mv [origen] [destino] Mueve el archivo o archivos
origen al destino
seleccionado
Tambin sirve para
renombrar un archivo
rmdir [directorio] Borra el directorio, que
previamente debe estar
vaco
exit o logout Termina la sesin (salir)
5/21/2018 47508930-ProgramacionAvanzada
14/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 14
Universidad Politcnica de Madrid -UPM
Tabla 1-2. Caracteres comodin (wildcars)
* Una cadena de caracteres
cualesquiera
? Un carcter cualquiera
Tabla 1-3. Directorios importantes
. Directorio actual Opcion
.. Directorio superior
cd Vuelve al directorio inicial
raiz del usuario
\home\usuario
Tabla 1-4. Ayuda
Comando funcin Opcionman [comando] Muestra las paginas man
del comando seleccionado
comando Muestra una ayuda breve
del comando al que se
aplica
--help -h
info [comando] Muestra las paginas info
del comando al que se
aplica
whatis [comando] Busca en una base de datos
descripciones cortas del
comando
Tabla 1-5. Ayudas del shell bash
Teclas funcin Opcion
Tab Autocompletar, rellena el
nombre del comando o
archivo segn las posibles
opciones que conozcaTab+Tab Muestra todas las opciones
que tiene autocompletar
Arrow Up Sube en la historia de
comandos
Arrow Down Baja en la historia de
comandos
Una vez creada la estructura, quitar el permiso de escritura al archivo11.txt eintentar concatenarle la cadena Muy bien gracias. Volver a reinstaurar el permiso y
repetir la operacin.
Borrar primero el archivo2.txt y luego la carpeta2. Borrar a continuacin todo elrbol de la carpeta1.
5/21/2018 47508930-ProgramacionAvanzada
15/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 15
Universidad Politcnica de Madrid -UPM
1.4.EL EDITOR DE TEXTOSe va a utilizar el editor vi o vim para crear y modificar los archivos de
cdigo fuente necesarios, por ser el editor incluido por defecto en linux, y del queconviene tener al menos unas nociones bsicas que nos permitan sacarnos de unapuro en caso de necesidad.
Para crear un archivo nuevo en la carpeta actual teclear:
vi [fichero]
Si el archivo no existe lo crea y si existe lo abre para editar.
vi tiene dos modos de funcionamiento:
Modo comando: cada tecla realiza una funcin especfica (borrar,mover) Este es el modo por defecto al arrancar el editor.
Modo insercin: cada tecla inserta el carcter correspondiente en eltexto. Para entrar en este modo se debe pulsar la tecla i y para salir del se debe pulsar Esc.
Operaciones bsicas
:wgraba el archivo al disco
:qsalir de editor
:q!salir del editor sin grabar los cambios (forzar la salida)
:wqgrabar y salir
1.5.DESARROLLO C/C++EN LINUX EN MODO TEXTOVamos a construir una aplicacin con dos ficheros fuente, que muestre por
pantalla una tabla de senos de varios ngulos. Para ello seguiremos los siguientespasos:
1. Verificar mediante pwd que se encuentra en el directorio de usua rioadecuado
2. Crear una carpeta pract1 que va a contener los archivos de la prctica,y cambiar el directorio actual a la misma
3. Crear los archivos fuente siguientes:
5/21/2018 47508930-ProgramacionAvanzada
16/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 16
Universidad Politcnica de Madrid -UPM
/** archivo: principal.c
*/
#include #include misfunc.h
int main(void){int i;for(i=0;i
5/21/2018 47508930-ProgramacionAvanzada
17/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 17
Universidad Politcnica de Madrid -UPM
Figura 1-2. Proceso de creacin de un ejecutable
1.7.LAS HERRAMIENTAS DE DESARROLLOSe van a utilizar a partir de ahora los compiladores y distintas herramientas.
Puede ser que en su sistema linux no vengan instaladas por defecto. Si ese es el caso,debe de instalarlas. El gestor de aplicaciones o paquetes de su distribucin le ayudara ahacerlo. En cualquier caso es importante remarcar que las herramientas de desarrollo
utilizadas son GNU, con licencia GPL, es decir son gratuitas y su instalacin estotalmente legal. Si utiliza un sistema basado en Debian, la forma ms sencilla deinstalar estas herramientas seria:
sudo apt-get install build-essential
1.8.EL COMPILADOR GCCEl compilador utilizado en linux se llama gcc. La sintaxis adecuada para la
compilacin y linkado del anterior programa seria:
gcco prueba principal.c misfunc.c lm
Fichero fuente A.c, .cpp
Fichero fuente B.c, .cpp
COMPILADOR
LINKADO
Bibliotecaesttica A.a
Bibliotecaesttica B.a
EJECUCION
Ejecutable
Modulo objeto A.o
Modulo objeto B.o
Libreras dinmicas.so
Proceso enejecucin
5/21/2018 47508930-ProgramacionAvanzada
18/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 18
Universidad Politcnica de Madrid -UPM
Ejecutar el programa mediante:
./prueba
Figura 1-3. Salida por pantalla de nuestra aplicacin
Comprobar con lsal los permisos de ejecucin del archivo
ls -al
La sintaxis es la siguiente:
gcco [nombre_ejecutable] [ficheros_fuente] l[librera]
Realmente este comando ha realizado la compilacin y el linkado todo seguido,de forma transparente para el usuario. Si se desea desacoplar las dos fases se realizade la siguiente manera:
Compilacin fichero a fichero :
gccc principal.c
gccc misfunc.c
(Ntese que aqu no es necesario especificar que se va a linkar con la libreramatemtica, ya que solo se esta compilando en un modulo objeto .o)
Compilacin de varios ficheros en la misma lnea
gccc principal.c misfunc.c
Enlazado
gcco prueba principal.o misfunc.o lm
Ntese que la opcin lm hace referencia a linkar l con la librera m o denombre completo libm.a o libm.so que es la librera estndar matemtica en susversiones estticas o dinmicas. Buscar con find / -name libm.*
Eliminar archivos objeto
rm *.o
5/21/2018 47508930-ProgramacionAvanzada
19/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 19
Universidad Politcnica de Madrid -UPM
1.9.MAKEFILE Y LA HERRAMIENTA MAKEHemos visto un ejemplo sencillo, en el que teclear el comando para compilar y
crear el ejecutable es muy sencillo. Sin embargo este procedimiento puede ser largo ytedioso en el caso de grandes proyectos con muchos ficheros fuente y mltiples
opciones de compilacin.
Por ello existe una herramienta, el make, que haciendo uso de la
configuracin de un fichero denominado Makefile (sin extensin, tpicamentesituado en la carpeta en la que tenemos el proyecto), se encarga de todo este trabajo.Entre otras cosas, se encarga de realizar la comprobacin de que ficheros han sidomodificados, para solo compilar dichos archivos, ahorrando mucho tiempo al usuario.
La sintaxis del Makefile es muy potente y compleja, por lo que aqu se realiza
solamente la descripcin de una configuracin bsica para el proyecto de esta practica.Para ello crear y editar con el vi el archivo siguiente:
#Makefile del proyecto
CC=gccCFLAGS= -gLIBS= -lmOBJS=misfunc.o principal.o
prueba: $(OBJS)$(CC) $(OBJS) $(LIBS) o prueba
principal.o: principal.c misfunc.h$(CC) c principal.c
misfunc.o: misfunc.c misfunc.h
$(CC) c principal.c
clean:rm f *.o prueba
Los comentarios en un Makefile se preceden de #
#Makefile del proyecto
El Makefile permite la definicin de variables, mediante una simple asignacin.En la primera parte del Makefile establecemos algunas variables de conveniencia. Sedefine la cadena CC que nos definir el compilador que se va a usar
CC=gcc
Se define la cadena CFLAGS que nos definir las opciones de compilacin, eneste caso habilita la informacin que posibilita la depuracin del ejecutable
CFLAGS= -g
La cadena LIBS almacena las libreras con las que hay que linkar para generar elejecutable
LIBS= -lm
La cadena OBJS define los mdulos objeto que componen el ejecutable. Aqu se
deben listar todos los archivos objeto necesarios, si nos olvidamos alguno, el enlazadorencontrara un error.
5/21/2018 47508930-ProgramacionAvanzada
20/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 20
Universidad Politcnica de Madrid -UPM
OBJS=misfunc.o principal.o
A partir de aqu comienzan las reglas, cada regla tiene la siguiente estructura:
objetivo (target): prerequisitos o dependenciascomando
Cada regla mira si los prerrequisitos o dependencias han sido modificados, ycaso de que lo hayan sido, construye el objetivo utilizando el comando. La siguientecadena establece la construccin del ejecutable a partir de los objetos, y linkando conlas libreras LIBS y generando el ejecutable prueba
prueba: $(OBJS)$(CC) $(OBJS) $(LIBS) o prueba
Que es totalmente equivalente a:
prueba: misfunc.o principal.ogcc misfunc.o principal.o -lm o prueba
Que significa: Si alguno o ambos de los ficheros objeto han cambiado, se tieneque volver a linkar el ejecutable prueba, a partir de los ficheros objeto y enlazando
con la librera matemticalm.
A su vez especificamos la compilacin de cada uno de los mdulos objeto:
principal.o: principal.c misfunc.h$(CC) c principal.c
misfunc.o: misfunc.c misfunc.h$(CC) c principal.c
Las dos primeras lneas, analizan si han sido modificados principal.c omisfunc.h, y en su caso, significa que hay que volver a compilar el modulo objeto a
partir del cdigo fuente.El Makefile analiza las dependencias recursivas, esto es, si el fichero
misfunc.h ha sido modificado, primero compilara con las dos ultimas reglas los
ficheros objeto principal.o y misfunc.o. Como estos ficheros han sido modificados,
invocara a su vez a la regla superior, linkando y obteniendo el ejecutable prueba.
La regla clean (make clean) elimina los objetos y el ejecutable
clean:rm f *.o prueba
Lo que significa que si tecleamos en la lnea de comandos:
make clean
en vez de construir el ejecutable, se borran los archivos binarios temporales y elejecutable
1.10. TIPOS DE ERRORExisten dos tipos de errores en un programa, errores en tiempo de ejecucin y
errores en tiempo de compilacin. Vamos a ver la diferencia entre ambos:
Errores en tiempo de compilacin. Son errores, principalmente desintaxis. El compilador los detecta y nos informa de ello, no produciendo
5/21/2018 47508930-ProgramacionAvanzada
21/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 21
Universidad Politcnica de Madrid -UPM
un ejecutable. Vamos a provocar un error de este estilo. Realizamos elcambio:
printf("Seno de %d es %f \n",i,seno(float(i));
Quitamos el punto y coma del final:
printf("Seno de %d es %f \n",i,seno(float(i))
Y compilamos de nuevo. Nos saldr un mensaje informndonos del errorsintctico y en que lnea se produce.
Errores en tiempo de ejecucin. Tambin llamados errores lgicos orun-time error. Es un error que no es capaz de detectar el compiladorporque no es un fallo en la sintaxis, pero que produce un error alejecutar el programa por un fallo lgico. Por ejemplo, la divisin porcero, sintcticamente no es un error en el programa, pero al realizar ladivisin, se produce un error en tiempo de ejecucin. En todo caso, si el
compilador detecta la divisin por cero (por ejemplo al hacer int a=3/0;)puede emitir un warning.
int a=0;int b=3;int c=b/a;
Compilamos este programa y lo ejecutamos. El programa fallara y nos saldr unmensaje informndonos de ello. Tambin cabe la posibilidad de que un fallo en elcdigo del programa produzca un comportamiento no deseado, pero que este noresulte en un fallo fatal y el programa finalice bruscamente.
1.11. DEPURACIN DE LA APLICACIN.Para depurar un programa se debe ejecutar el depurador seguido del nombre
del ejecutable (que debe haber sido creado con la opcing)
gdb prueba
El depurador arranca y muestra un nuevo prompt (gdb) que espera a recibirlos comandos adecuados para ejecutar el programa paso a paso o como se le indique.Los comandos que puede recibir este prompt se dividen en distintos grupos,
mostrados por el comando
(gdb) help
Si se desea ver los comandos que pertenecen a cada grupo se debe escribir(p.ej. para ver los comandos que permiten gestionar la ejecucin del programa)
(gdb) help [nombre grupo] (ejemplo: running )
Y para ver la ayuda de un comando en particular:
(gdb) help [comando]
Caben destacar por su utilidad los siguientes comandos pertenecientes a losgrupos:
5/21/2018 47508930-ProgramacionAvanzada
22/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 22
Universidad Politcnica de Madrid -UPM
Tabla 1-6. Comandos bsicos de gdb
Grupo Comando Accin
running run comienza la depuracin del
programa
step ejecuta un paso, entrando en
funciones
next ejecuta un paso, sin entrar en
funciones
finish termina la ejecucin del programa
continue continua la ejecucin del programa,
hasta el siguiente breakpoint
data display [exp] muestra el contenido de la variable
exp cada vez que el programa se
para
undisplay [exp] quita el comportamiento anterior
print [exp] Muestra el contenido de expbreakpoint break [num_linea] inserta un punto de parada o
Breakpoint en la lnea
correspondiente
clear [num_linea] Eliminan el breakpoint de la lnea
correspondiente
delete break Pregunta si se desea eliminar todos
los breakpoints
status info [opcion] Muestra informacin acerca de la
opcin elegida, por ejemplo info
break muestra los breakpoints.
ninguno quit sale del debugger
Realizar la depuracin del programa anterior, viendo el valor de las posiblesvariables, ejecutando paso a paso.
1.12. CREACIN DE UN SCRIPTSe puede crear un archivo de texto que sirva para ejecutar una serie de
comandos consecutivos en el shell, en lo que se llama un script. Para ver un ejemplo se
va a crear un script que muestre el nombre de la carpeta actual y a continuacinmuestre el contenido de dicha carpeta, para termina ejecutando el programa prueba.
Para ello creamos un archivo:
vi miscript
echo La carpeta actual es pwdecho Y contiene lo siguiente ls
Si intentamos ejecutar el script, nos dir que no tiene permisos de ejecucin.
Para eso realizamos el cambio:chmod a+x miscript
5/21/2018 47508930-ProgramacionAvanzada
23/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 23
Universidad Politcnica de Madrid -UPM
Debe de quedar claro que con un script no se tiene cdigo mquina, ni secompila, ni se inicia un proceso. Simplemente se la pasan al shell unos comandos enlotes.
1.13. DESARROLLO EN UN ENTORNO GRAFICOExisten distintas herramientas para el desarrollo C/C++ en linux, entre las que
se podran destacar el Kdevelop, Anjuta, o Eclipse. Para el desarrollo de nuestraaplicacin hemos optado por Geany, que realmente es ms un editor de texto que unentorno de desarrollo, pero sin embargo tiene las caractersticas necesarias paranuestra aplicacin. Geany dispone de resaltado en colores del cdigo, y de gestion dela compilacin mediante Makefile, que permite mediante la pulsacin de F9 lainvocacin automtica de Makefile (aunque el fichero Makefile lo debemos proveernosotros), as como la gestin de los posibles errores de compilacin, con la posibilidadde saltar a la lnea del error simplemente haciendo doble click en el mensaje de error.
Figura 1-4. El editor Geany
Si se desea instalar el editor, as como las libreras necesarias de Glut, esnecesario:
sudo apt-get install geany glutg3-dev
5/21/2018 47508930-ProgramacionAvanzada
24/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 24
Universidad Politcnica de Madrid -UPM
1.14. EJERCICIO PRCTICOSe suministra en una carpeta un conjunto de ficheros de cdigo fuente, con
algunas clases de C++, necesarias para el desarrollo del juego del Tenis,fundamentalmente las clases Mundo, Esfera, Plano, Raqueta, y la clase auxiliar
Vector2D. Todas las clases estn completas, exceptuando la clase Mundo.#include "Vector2D.h"class Esfera{public:
Esfera();virtual ~Esfera();
Vector2D centro;Vector2D velocidad;float radio;
void Mueve(float t);void Dibuja();
};
#include "Esfera.h"#include "Vector2D.h"class Plano{public:
bool Rebota(Esfera& e);bool Rebota(Plano& p);void Dibuja();
Plano();virtual ~Plano();
float x1,y1;float x2,y2;float r,g,b;
protected:float Distancia(Vector2D punto, Vector2D *direccion);
};
#include "Plano.h"#include "Vector2D.h"class Raqueta : public Plano{public:
void Mueve(float t);Raqueta();virtual ~Raqueta();
Vector2D velocidad;};
class CMundo
{public:void Init();
5/21/2018 47508930-ProgramacionAvanzada
25/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 25
Universidad Politcnica de Madrid -UPM
CMundo();virtual ~CMundo();
void InitGL();void OnKeyboardDown(unsigned char key, int x, int y);void OnTimer(int value);
void OnDraw();};
Se solicita al alumno que complete la clase Mundo para obtener el juego deltenis funcional. Se debe escribir un Makefile para la construccin del ejecutable.
1.15. EJERCICIO PROPUESTOEl alumno debe de completar el juego con alguna funcionalidad extra, como por
ejemplo, que cada una de las raquetas sea capaz de disparar un disparo, que cuando
impacta al oponente lo inmoviliza, o disminuye el tamao de su raqueta.
Tambin se propone el desarrollo de cualquier otro juego de complejidadsimilar.
5/21/2018 47508930-ProgramacionAvanzada
26/157
5/21/2018 47508930-ProgramacionAvanzada
27/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 27
Universidad Politcnica de Madrid -UPM
22.. INTRODUCCIN A LOS SISTEMASDISTRIBUIDOS.COMUNICACIN POR
SOCKETS
2.1.OBJETIVOSEn el captulo anterior se ha desarrollado el juego bsico del tenis en el que dos
jugadores, compartiendo el mismo teclado y el mismo monitor, cada uno con distintasteclas puede controlar su raqueta arriba y abajo para jugar la partida. El objetivo finales la consecucin del juego totalmente distribuido, es decir, cada jugador podr jugaren su propio ordenador, con su teclado y su monitor, y los dos ordenadores estarnconectados por la red.
En este captulo se presenta una introduccin a los sistemas distribuidos, los
servicios proporcionados en POSIX para el manejo de Sockets, que son los conectoresnecesarios (el recurso software) para la comunicacin por la red, y su uso en nuestraaplicacin. No pretende ser una gua exhaustiva de dichos servicios sino unadescripcin prctica del uso ms sencillo de los mismos, y como integrarlos en nuestraaplicacin para conseguir nuestros objetivos. De hecho, en el curso del captulo sedesarrolla una clase C++ que encapsula los servicios de Sockets, permitiendo al usuarioun uso muy sencillo de los mismos que puede valer para numerosas aplicaciones,aunque obviamente no para todo.
Como primera aproximacin al objetivo final se va a realizar en este captulo laretransmisin del partido de tenis por la red. Esto es, los dos jugadores van a seguir
jugando en la misma mquina con el mismo teclado, pero sin embargo otro usuariodesde otra mquina podr conectarse remotamente a travs de la red a la mquina y a
5/21/2018 47508930-ProgramacionAvanzada
28/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 28
Universidad Politcnica de Madrid -UPM
la aplicacin en la que juegan los jugadores (el servidor), y esta le enviaraconstantemente los datos necesarios para que la mquina remota (el cliente) puedasimplemente dibujar el estado actual de la partida. De esta forma lo que se permite esque los clientes sean meros espectadores de la partida. Inicialmente se plantea lasolucin para un nico espectador, y finalmente se aborda la solucin para mltiples
espectadores. No obstante esta ltima requerir para su correcto funcionamiento eluso de programacin concurrente (hilos) que se abordara en sucesivos captulos.
Figura 2-1. Objetivo del captulo: Retransmisin de la partida de tenis a ordenadores
remotos conectados a travs de la red al servidorEn sucesivos captulos se completar el desarrollo del juego distribuido
haciendo que los jugadores puedan realmente jugar en dos mquinas distintas, quetransmitirn los comandos de los jugadores por la misma red al servidor, para que estelos ejecute sin necesidad de tener a dichos jugadores utilizando el mismo teclado fsicode la mquina en la que corre el servidor.
2.2.SISTEMA DISTRIBUIDOLlamaremos sistema distribuido a una solucin software cuya funcionalidad es
repartida entre distintas mquinas, teniendo cada mquina su propio procesador (opropios procesadores), su propia memoria, y corriendo su propio sistema operativo.Adems, no es necesario que las mquinas sean iguales, ni ejecuten el mismo SO ni elmismo software. Las mquinas estarn interconectadas por una red que sirve para elintercambio de mensajes entre dichas mquinas.
RED
Retransmisin
partido
N posiblesclientes
que seconectanal servidorpara ver elpartido
Servidor, en el quejuegan los dosjugadores con elmismo teclado
5/21/2018 47508930-ProgramacionAvanzada
29/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 29
Universidad Politcnica de Madrid -UPM
2.3.SERVICIOS DE SOCKETS EN POSIXA continuacin se presenta el cdigo de un programa cliente y de un programa
servidor, para describir breve y generalmente los servicios de sockets implicados. Estecdigo es prcticamente el ms bsico posible, sin comprobacin de errores. El
funcionamiento ser como sigue: Primero se arranca el programa servidor, queinicializa el socketservidor y se queda a la espera de una conexin. A continuacin sedebe lanzar el programa cliente que se conectar al servidor. Una vez que ambos estnconectados, el servidor enviara al cliente unos datos (una frase) que el clientemostrar por pantalla, y a finalmente terminarn ambos programas. El funcionamientoen lneas generales queda representado en la siguiente figura:
Figura 2-2. Conexin sockets
ServidorCliente
Se crea el socketde conexin
Se le asigna unadireccin y unpuerto y se ponea la escucha
El socket deconexin sequeda bloqueadoa la esperaAceptando una
conexin
socket()
bind()
listen()
accept()
Se crea el socketde conexin ycomunicacin (esel mismo)
Se conecta a ladireccin delservidor
socket()
connect()
Comunicacin Comunicacinsend()
recv()
send()
recv()
TCP/IP
Cierre
shutdown()
close()Cierre shutdown()
close()
Cuando el cliente se conecta alsocket de conexin que estaAceptando, este devuelve unsocket de conexin que es con elque se realiza la comunicacin
5/21/2018 47508930-ProgramacionAvanzada
30/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 30
Universidad Politcnica de Madrid -UPM
2.3.1Programa cliente
El cdigo del programa cliente bsico es el siguiente:
//includes necesarios para los sockets#include
#include #include #include #include #include
#define INVALID_SOCKET -1
int main(){//declaracion de variables
int socket_conn;//the socket used for the send-receivestruct sockaddr_in server_address;
char address[]="127.0.0.1";int port=12000;
// Configuracion de la direccion IP de connexion al servidorserver_address.sin_family = AF_INET;server_address.sin_addr.s_addr = inet_addr(address);server_address.sin_port = htons(port);
//creacion del socketsocket_conn=socket(AF_INET, SOCK_STREAM,0);
//conexionint len= sizeof(server_address);
connect(socket_conn,(struct sockaddr *) &server_address,len);
//comunicacionchar cad[100];int length=100; //read a maximum of 100 bytes
int r=recv(socket_conn,cad,length,0);std::cout
5/21/2018 47508930-ProgramacionAvanzada
31/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 31
Universidad Politcnica de Madrid -UPM
La primera lnea declara el descriptor del socket (de tipo entero) que se utilizatanto para la conexin como para la comunicacin. La segunda declaracin declara unaestructura de datos que sirve para almacenar la direccin IP y el nmero de puerto delservidor y la familia de protocolos que se utilizaran en la comunicacin. La asignacinde esta estructura a partir de la IP definida como una cadena de texto y el puerto
definido como un entero se hace como sigue:
char address[]="127.0.0.1";int port=12000;server_address.sin_family = AF_INET;server_address.sin_addr.s_addr = inet_addr(address);server_address.sin_port = htons(port);
Ntese que la IP que utilizaremos ser la 127.0.0.1. Esta IP es una IP especialque significa la mquina actual (direccin local). Realmente ejecutaremos nuestras 2aplicaciones (cliente y servidor) en la misma mquina, utilizando la direccin local de lamquina. No obstante esto se puede cambiar. Para ejecutar el servidor en una
mquina que tiene la IP 192.168.1.13 por ejemplo, basta poner dicha direccin enambos programas, ejecutar el servidor en esa mquina, y el cliente en cualquier otra(que sea capaz de enrutar mensajes hacia esa IP).
A continuacin se crea el socket, especificando la familia de protocolos (en estecaso protocolo de Internet AF_INET) y el tipo de comunicacin que se quiere
emplear (fiable=SOCK_STREAM, no fiable=SOCK_DGRAM). En nuestro casoutilizaremos siempre comunicacin fiable.
//creacion del socketsocket_conn=socket(AF_INET, SOCK_STREAM,0);
Esta funcin generalmente no produce errores, aunque en algn caso podrahacerlo. Como regla general conviene comprobar su valor, que ser igual a -1(INVALID_SOCKET) si la funcin ha fallado. A continuacin se intenta la conexincon el socket especificado en la direccin del servidor.
//conexionint len= sizeof(server_address);connect(socket_conn,(struct sockaddr *) &server_address,len);
Esta funcin connect() fallar si no esta el servidor preparado por algnmotivo (lo que sucede muy a menudo). Por lo tanto es ms que convenientecomprobar el valor de retorno de connect()para actuar en consecuencia. Se podra
hacer algo como:if(connect(socket_conn,(struct sockaddr *) &server_address,len)!=0){
std::cout
5/21/2018 47508930-ProgramacionAvanzada
32/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 32
Universidad Politcnica de Madrid -UPM
Dicha informacin puede ser menor que el tamao mximo suministrado. El valor deretorno de la funcin recv()es el numero de bytes recibidos.
//comunicacionchar cad[100];int length=100; //read a maximum of 100 bytes
int r=recv(socket_conn,cad,length,0);std::cout
5/21/2018 47508930-ProgramacionAvanzada
33/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 33
Universidad Politcnica de Madrid -UPM
//escuchabind(socket_server,(struct sockaddr *) &server_address,len);// Damos como maximo 5 puertos de conexion.listen(socket_server,5);
//aceptacion de cliente (bloquea hasta la conexion)unsigned int leng = sizeof(client_address);socket_conn = accept(socket_server,
(struct sockaddr *)&client_address, &leng);
//notese que el envio se hace por el socket de communicacionchar cad[]="Hola Mundo";int length=sizeof(cad);send(socket_conn, cad, length,0);
//cierre de los dos sockets, el servidor y el de comunicacion
shutdown(socket_conn, SHUT_RDWR);
close(socket_conn);socket_conn=INVALID_SOCKET;
shutdown(socket_server, SHUT_RDWR);close(socket_server);socket_server=INVALID_SOCKET;
return 1;}
Hasta la creacin del socket del servidor, el programa es similar al cliente,quitando la excepcin de que se declaran los 2 sockets, el de conexin y el decomunicacin. La primera diferencia son las lneas:
//configuracion del socket para reusar direccionesint on=1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
Estas lneas se utilizan para que el servidor sea capaz de re-usar la direccin y elpuerto que han quedado abiertos sin ser cerrados correctamente en una ejecucinanterior. Cuando esto sucede, el sistema operativo deja la direccin del socketreservada y por tanto un intento de utilizarla para un servidor acaba en fallo. Con estaslneas podemos configurar y habilitar que se re-usen las direcciones previas.
La segunda diferencia es que en vez de intentar la conexin con connect(),el servidor debe establecer primero en que direccin va a estar escuchando su socketde conexin, lo que se establece con las lneas:
int len = sizeof(server_address);bind(socket_server,(struct sockaddr *) &server_address,len);// Damos como maximo una cola de 5 conexiones.listen(socket_server,5);
La funcin bind() enlaza el socket de conexin con la IP y el puertoestablecidos anteriormente. Esta funcin tambin es susceptible de fallo. El fallo mscomn es cuando se intenta enlazar el socket con una direccin y puerto que ya estnocupados por otro socket. En este caso la funcin devolver -1, indicando el error. Aveces es posible que si no se cierra correctamente un socket (por ejemplo, si el
programa finaliza bruscamente), el SO piense que dicho puerto esta ocupado, y al
5/21/2018 47508930-ProgramacionAvanzada
34/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 34
Universidad Politcnica de Madrid -UPM
volver a ejecutar el programa, el bind() falle, no teniendo sentido continuar laejecucin. La gestin bsica de este error podra ser:
if(0!=bind(socket_server,(struct sockaddr *) &server_address,len)){
std::cout
5/21/2018 47508930-ProgramacionAvanzada
35/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 35
Universidad Politcnica de Madrid -UPM
El cierre de los sockets se realiza de la misma manera que en el cliente,exceptuando que se deben cerrar correctamente los 2 sockets, el de conexin y el decomunicacin. La salida por pantalla al ejecutar las aplicaciones (primero arrancar elservidor y luego el cliente) debera ser (en el lado del cliente):
Rec: 11 contenido: Hola MundoNtese que los bytes recibidos son 11 porque incluyen el carcter nulo \0 de
final de la cadena
2.4.ENCAPSULACIN DE UN SOCKET EN UNA CLASE C++La API vista en el apartado anterior es C, y aparte de las funciones descritas,
tiene otras funcionalidades que no se vern en este curso. Es una prctica habitualcuando se puede desarrollar en C++ encapsular la funcionalidad de la API en una claseo conjunto de clases que oculten parcialmente los detalles ms complejos, facilitandola tarea al usuario. As, por ejemplo, las Microsoft Fundation Classes(MFC) tienen susclases CSocket y CAsyncSocket para estas tareas. Tambin se puedenencontrar en Internet numerosos envoltorios (wrappers) de C++ para los sockets enlinux.
Vamos a desarrollar una clase C++ que encapsule la funcionalidad vista en losprogramas anteriores. Es comn encontrar, bajo una perspectiva estricta deProgramacin Orientada a Objetos (POO) que el cliente y servidor se implementan enclases separadas. No obstante, se adopta ahora un enfoque ms sencillo con una sola
clase, que utiliza diferentes mtodos en caso del cliente y del servidor.EJERCICIO: Desarrollar la clase Socket, de acuerdo con la cabecera siguiente, paraque encapsule los detalles de implementacin anteriores.
//includes necesariosclass Socket{public:
Socket();virtual ~Socket();
// 0 en caso de exito y -1 en caso de errorint Connect(char ip[],int port); //para el clienteint InitServer(char ip[],int port);//para el servidor
//devuelve un socket, el empleado realmente para la comunicacion//el socket devuelto podria ser invalido si el accept fallaSocket Accept();//para el servidorvoid Close();//para ambos
//-1 en caso de error,// numero de bytes enviados o recibidos en caso de exitoint Send(char cad[],int length);int Receive(char cad[],int length);
private:int sock;
};
5/21/2018 47508930-ProgramacionAvanzada
36/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 36
Universidad Politcnica de Madrid -UPM
El cdigo del servidor se ver simplificado a:
#include #include "Socket.h"
int main(){
Socket servidor;servidor.InitServer("127.0.0.1",12000);
Socket conn=servidor.Accept();
char cad[]="Mensaje";int length=sizeof(cad);
conn.Send(cad,length);
conn.Close();servidor.Close();
return 1;}
Y el cdigo del cliente:
#include "Socket.h"#include
int main(){
Socket client;client.Connect("127.0.0.1",12000);
char cad[1000];int length=1000;
int r=client.Receive(cad,length);std::cout
5/21/2018 47508930-ProgramacionAvanzada
37/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 37
Universidad Politcnica de Madrid -UPM
for(int i=0;i
5/21/2018 47508930-ProgramacionAvanzada
38/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 38
Universidad Politcnica de Madrid -UPM
2. Existe un convenio entre el cliente y el servidor que especifica como sonlos mensajes, para que el cliente sepa que es lo que va a recibir y comolo tiene que interpretar. Este convenio puede consistir en especificaruna longitud fija para los mensajes, o en establecer un carcterterminador de mensaje. En el caso anterior podramos haber recorrido
los mensajes buscando los caracteres nulos \0 que nos separaran cadamensaje. Si consideramos los mensajes de longitud fija el cdigo delservidor podra ser:
//definimos los mensajes de 100 bytes siemprechar cad[100]="Hola Mundo";int length=sizeof(cad); //length=100
for(int i=0;i
5/21/2018 47508930-ProgramacionAvanzada
39/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 39
Universidad Politcnica de Madrid -UPM
Un cliente solo puede comunicarse con un servidor.
2.4.2.1. Conexiones secuenciales
Una primera opcin es que el servidor atienda secuencialmente las conexiones
de los distintos clientes, esto es, se conecta un cliente, se comunica con el y vuelve aesperar aceptando en el accept()a un nuevo cliente.
Figura 2-3. Servidor que permite mltiples conexiones secuenciales de clientes
El cliente permanecera inalterado, y el cdigo del servidor quedara comosigue:
Servidor
Cliente
Se crea el socketde conexin
Se le asigna unadireccin y unpuerto y se ponea la escucha
El socket deconexin sequeda bloqueadoa la esperaAceptando una
conexin
Comunicacin
Cierre del socket
Cierre del socket
Seguiraceptandoclientes?
SI
NO
Comunicacin
Conexin
Se crea el socketde conexin ycomunicacin
Cierre
Socket deconexion
5/21/2018 47508930-ProgramacionAvanzada
40/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 40
Universidad Politcnica de Madrid -UPM
#include #include "Socket.h"
int main(){
Socket servidor;
servidor.InitServer("127.0.0.1",12000);
while(1){
Socket conn=servidor.Accept();
//comunicacion, en este caso envio de 1 unico mensajechar cad[]="Hola mundo";int length=sizeof(cad);
conn.Send(cad,length);
conn.Close();
}servidor.Close();
return 1;}
La funcin listen()toma sentido en este contexto, ya que permite poner ala cola peticiones de conexiones de varios clientes que intentan la conexin mientras elservidor esta comunicando con el cliente actual. Cuando el servidor vuelve alaccept()se atienden dichas peticiones de conexin.
2.4.2.2. Conexiones simultneas.
Es posible que el servidor acepte la conexin de varios clientes y enve datos atodos ellos, manteniendo la conexin activa con todos simultneamente.
Para ello y dado que aun no estamos utilizando programacin concurrente,primero se realiza el accept() de tantos clientes como se vayan a conectar (elservidor debe conocer dicho numero). Hay que recordar que el accept() bloqueahasta que se conecta un cliente, por lo tanto hasta que no se conecten tantos clientescomo accept() se intenten, el programa no podr continuar. Como cada conexindevuelve un socket diferente a travs del accept(), estos sockets se puedenalmacenar en un vector, y manejar todas las conexiones en el servidor a travs de
dicho vector.
5/21/2018 47508930-ProgramacionAvanzada
41/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 41
Universidad Politcnica de Madrid -UPM
Figura 2-4. Comunicacin simultanea con varios clientes
El cdigo resultante en el servidor podra ser:
#include #include "Socket.h"int main(){
Socket servidor;servidor.InitServer("127.0.0.1",12000);
Socket conexiones[5];for(i=0;i
5/21/2018 47508930-ProgramacionAvanzada
42/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 42
Universidad Politcnica de Madrid -UPM
2.5.ESTRUCTURA DE FICHEROSAhora que se ha visto como realizar el envo de informacin por la red, y se
dispone de una clase que encapsula la funcionalidad de los sockets se va a proceder acomenzar el desarrollo de la aplicacin distribuida del juego del tenis. Debe quedar
claro que solo hay que desarrollar dos aplicaciones, la aplicacin servidor y laaplicacin cliente. La aplicacin servidor se ejecutar una vez, pero la aplicacin cliente(el mismo binario) puede ser ejecutado mltiples veces y en distintas mquinas. Separte de la aplicacin desarrollada en el tema anterior, que constituye el juego deltenis (los dos jugadores en la misma mquina), cuyos ficheros se encuentran todos enla misma carpeta y los cuales son:
Esfera.h y Esfera.cpp (la clase Esfera)
Plano.h y Plano.cpp (la clase Plano)
Raqueta.h y Raqueta.cpp (la clase Raqueta)
Vector2D.h y Vector2D.cpp (la clase Vector2D)
Mundo.h y Mundo.cpp (la clase Mundo)
Tenis.cpp (el fichero principal con el main() )
Makefile
La primera intencin podra ser duplicar esta carpeta para realizar lasmodificaciones necesarias en cada una de ellas y transformarlas en el servidor y elcliente. No obstante, esto implicara que habra mucho cdigo idntico duplicado endos sitios. Por ejemplo, la clase
Plano ser exactamente igual en el cliente y en el
servidor, su parametrizacin es igual, se dibuja igual. Por tanto no es necesario (dehecho es contraproducente) que el cdigo este repetido. Se pueden desarrollar ambosprogramas, el cliente y el servidor compartiendo uno o varios archivos de cdigofuente.
Si se analiza la funcionalidad del servidor y del cliente se llega a la conclusinque ambas aplicaciones son iguales, exceptuando:
El servidor atiende el teclado, cambiando la velocidad de las raquetas,pero los clientes no, son solo espectadores. Esto se hace en la funcinCMundo::OnKeyboardDown()
El servidor cambia las posiciones de los objetos (anima), realiza losclculos de las colisiones. El cliente no tiene que mover los objetos(podra moverlos de forma diferente al servidor), solo tiene que recibirla informacin del servidor de donde estn los objetos en cada instantede tiempo. El cambio de posicin de los objetos se hace en la funcinCMundo::OnTimer().
Como se ve, la nica clase que va a tener diferencias entre el servidor y elcliente es la clase CMundo. Por tanto, se propone nicamente duplicar este archivocon dos nombres diferentes (aunque el nombre de la clase se puede mantener.)
Tambin es necesario duplicar el archivo en el que se encuentra el main(), ya que es
5/21/2018 47508930-ProgramacionAvanzada
43/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 43
Universidad Politcnica de Madrid -UPM
el que instancia la clase CMundo, y en funcin de si es el servidor o el cliente,
necesitara hacer un #includea MundoServidor.h o a MundoCliente.h
Esfera.h y Esfera.cpp (la clase Esfera)
Plano.h y Plano.cpp (la clase Plano)
Raqueta.h y Raqueta.cpp (la clase Raqueta)
Vector2D.h y Vector2D.cpp (la clase Vector2D)
MundoServidor.h y MundoServidor.cpp (la clase Mundo para elservidor)
MundoCliente.h y MundoCliente.cpp (la clase Mundo para el cliente)
servidor.cpp (el fichero principal con el main(), para el servidor )
cliente.cpp (el fichero principal con el main(), para el cliente )
Makefile
En el Makefile se especifican como se construyen las dos aplicacionesdiferentes:
CC=g++CFLAGS= -gLIBS= -lm -lglutOBJS=Esfera.o Plano.o Raqueta.o Vector2D.o Socket.oHEADERS=Esfera.h MundoCliente.h MundoServidor.h Plano.h Raqueta.hVector2D.h
all: servidor cliente
servidor: $(OBJS) MundoServidor.o servidor.o$(CC) $(CFLAGS) -o servidor servidor.o MundoServidor.o $(OBJS)
$(LIBS)
cliente: $(OBJS) MundoCliente.o cliente.o$(CC) $(CFLAGS) -o cliente cliente.o MundoCliente.o $(OBJS)
$(LIBS)
Socket.o: Socket.cpp $(HEADERS)$(CC) $(CFLAGS) -c Socket.cpp
MundoCliente.o: MundoCliente.cpp $(HEADERS)$(CC) $(CFLAGS) -c MundoCliente.cpp
MundoServidor.o: MundoServidor.cpp $(HEADERS)$(CC) $(CFLAGS) -c MundoServidor.cpp
Esfera.o: Esfera.cpp $(HEADERS)$(CC) $(CFLAGS) -c Esfera.cpp
Plano.o: Plano.cpp $(HEADERS)$(CC) $(CFLAGS) -c Plano.cpp
Raqueta.o: Raqueta.cpp $(HEADERS)$(CC) $(CFLAGS) -c Raqueta.cpp
Vector2D.o: Vector2D.cpp $(HEADERS)$(CC) $(CFLAGS) -c Vector2D.cpp
servidor.o: servidor.cpp $(HEADERS)$(CC) $(CFLAGS) -c servidor.cpp
cliente.o: cliente.cpp $(HEADERS)
$(CC) $(CFLAGS) -c cliente.cppclean:
rm -f *.o cliente servidor
5/21/2018 47508930-ProgramacionAvanzada
44/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 44
Universidad Politcnica de Madrid -UPM
Con este Makefilela simple invocacin
make
construye tanto el servidor como el cliente
2.6.TRANSMITIENDO EL PARTIDO DE TENISInicialmente vamos a realizar el envo de los datos necesarios del servidor a un
nico cliente. Para ello se deben de seguir los siguientes pasos:
2.6.1Conexin
Aadir el Socket de conexin y el de comunicacin en la clase Mundo del
servidor:
Socket server;Socket conn;
Aadir el Socket en la clase Mundodel cliente
Socket client;
En la funcin de inicializacin del juego en el servidor se establece la direccinIP y el puerto del servidor y se espera la aceptacin de un cliente:
//en el fichero MundoServidorvoid CMundo::Init()
{//inicializacion de la pantalla, coordenadas, etc
server.InitServer("127.0.0.1",12000);conn=server1.Accept();
}
Ntese en este punto que si se compila y ejecuta el servidor no se muestranada por pantalla. Sencillamente el programa esta bloqueado a la espera de laconexin y ni siquiera ha creado aun la ventana grafica. No obstante el accept sepodra realizar ms tarde, despus de haber creado la ventana.
El cliente tambin realiza en su funcin init()la conexin del socket:
//del fichero MundoClientevoid CMundo::Init(){
//inicializacion de la pantalla, coordenadas, etc
client.Connect("127.0.0.1",12000);}
5/21/2018 47508930-ProgramacionAvanzada
45/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 45
Universidad Politcnica de Madrid -UPM
2.6.2Envo de datos
Lo primero es necesario establecer cuales son los datos que es necesario queenve el servidor al cliente. Dado que la pantalla es en su mayora esttica, las variablesque es necesario transmitir podran ser:
Coordenadas (x, y) de la pelota
Coordenadas (x1, y1, x2, y2) de la raqueta del jugador 1.
Coordenadas (x1, y1, x2, y2) de la raqueta del jugador 2.
Estos datos deben de ser enviados por el servidor cada vez que se produce uncambio en los mismos, es decir, en cada temporizacin del timer. Cmo se envandatos numricos? Aunque una solucin ms evolucionada se presentara en un temaposterior, una primera solucin sencilla consiste en escribir (sprintf()) estosvalores numricos en una cadena de texto y enviar dicha cadena de texto.
EJERCICIO:
1. Realizar el envo de los datos por el socket de comunicacin en el servidor(MundoServidor), en la funcin CMundo::OnTimer(), al final de la misma,manteniendo el cdigo existente encargado de realizar la animacin y lgica del
juego.
2. Eliminar el cdigo de la funcin CMundo::OnTimer() de MundoCliente y sustituirlopor la recepcin del mensaje del servidor y la extraccin de los valores numricos.
2.7.EJERCICIOS PROPUESTOS
Realizar la retransmisin del juego a un numero fijo de clientes, porejemplo 3
Implementar los conceptos desarrollados en este tema en un juego decomplejidad similar.
5/21/2018 47508930-ProgramacionAvanzada
46/157
5/21/2018 47508930-ProgramacionAvanzada
47/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 47
Universidad Politcnica de Madrid -UPM
33.. COMUNICACIONES Y CONCURRENCIA
3.1.INTRODUCCINEn el captulo anterior hemos concluido con dos programas, un servidor y un
cliente, en el que el servidor enviaba los datos de la partida de tenis de forma continuaal cliente. De hecho, tambin podamos permitir que se conectaran varios clientes y
despus (una vez conectados todos los clientes, con lo que se tenia que conocer sunumero) enviar los datos a todos los clientes. Pero aun no podemos permitir que losclientes espectadores se conecten y desconecten cuando quieran, o que los
jugadores puedan efectivamente jugar de forma remota.
Tal como esta planteado el programa, esto no es posible hacerlo conprogramacin convencional (secuencial). Analizaremos en este captulo el porque yveremos la solucin a dichos problemas. Comenzamos analizando un sencillo ejemplo.Supngase que se esta diseando un controlador de una mquina, que se plasmafinalmente en un regulador que podra tener el siguiente aspecto (en pseudocdigo):
void main()
{float referencia=3.0f;float K=1.2f;while(1){
float medida=GetValorSensor();float error=referencia-medida;float comando=K*error;//regulador proporcionalEnviaComando(comando);
}}
Donde las funciones GetValorSensor() y EnviaComando()realizaran
la interfaz correspondiente con el hardware de la mquina. Obviamente el programase tiene que ejecutar de forma continua, recalculando en cada pasada el nuevo error y
5/21/2018 47508930-ProgramacionAvanzada
48/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 48
Universidad Politcnica de Madrid -UPM
enviando un comando nuevo. El programa anterior utiliza una referencia (el punto alque se quiere llevar el sistema) fija. Supngase que ahora se desea que el usuario seacapaz de introducir por teclado dicha referencia tantas veces como quiera (para llevarla mquina a distintos puntos) y que se programa de la siguiente forma:
void main(){
float referencia=3.0f;float K=1.2f;while(1){
printf("Introduzca referencia: ");scanf("%f",&referencia);float medida=GetValorSensor();float error=referencia-medida;float comando=K*error;EnviaComando(comando);
}}
El efecto conseguido es que el programa se queda parado en el scanf()esperando a la entrada del usuario. Cuando el usuario teclea un valor, se calcula yenva un comando a la mquina y el programa se vuelve a quedar parado en elscanf(). Si el usuario no teclea una nueva referencia, la mquina sigue funcionandocon el comando anterior de forma indefinida.
Obviamente, la solucin anterior no es valida. Tenemos dos tareas diferentes:la ejecucin de forma continua del control y la interfaz con el usuario. Dichas tareastienen que ejecutarse de forma paralela a la vez. No podemos dejar de ejecutar elcontrol por el hecho de que el usuario este tecleando una referencia, ni podemos
inhabilitar al usuario de teclear una referencia por el hecho de que se este ejecutandoel control de forma continua.
La solucin es utilizar programacin concurrente. En el ejemplo anterior sepodra lanzar un hilo dedicado a la gestin de la entrada del usuario mientras que elhilo principal ejecuta el control. El programa en pseudo cdigo podra quedar as:
float referencia=0.0f;//variable globalvoid hilo_usuario(){
while(1){
printf("Introduzca referencia: ");scanf("%f",&referencia);
}}void main(){
float K=1.2f;crear_hilo ( hilo_usuario );while(1){
float medida=GetValorSensor();float error=referencia-medida;float comando=K*error;EnviaComando(comando);
}}
5/21/2018 47508930-ProgramacionAvanzada
49/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 49
Universidad Politcnica de Madrid -UPM
Ntese como se ha puesto la variable referencia como global, para queambos hilos tengan acceso a la misma. Los hilos comunican informacin entre ellos atravs de memoria global de la aplicacin.
3.2.REQUISITOSVamos a resumir las funcionalidades que nos quedan por implementar en
nuestro sistema distribuido:
Queremos permitir que los clientes se puedan conectar en el instanteque quieran. El servidor no debe quedar bloqueado por esperar a quelos clientes se conecten.
Queremos permitir cualquier nmero de clientes espectadores. Dedichos espectadores, nicamente los dos primeros podrnefectivamente controlar las raquetas.
Los dos primeros clientes que se conecten podrn controlar lasraquetas, el primero de ellos con las teclas w y s y el segundo con las
teclas l y o.
El servidor debe de gestionar adecuadamente las desconexiones de losclientes.
3.3.FUNCIONAMIENTO DE GLUTEl funcionamiento bsico de la librera GLUT se plasma en la funcin
glutMainLoop(),que es invocada desde el main():
//los callbacksvoid OnDraw(void);void OnTimer(int value);void OnKeyboardDown(unsigned char key, int x, int y);
int main(int argc,char* argv[]){
//Inicializar el gestor de ventanas GLUT
//y crear la ventanaglutInit(&argc, argv);glutInitWindowSize(800,600);glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);glutCreateWindow("ClienteTenis");
//Registrar los callbacksglutDisplayFunc(OnDraw);glutTimerFunc(25,OnTimer,0);glutKeyboardFunc(OnKeyboardDown);//pasarle el control a GLUT,que llamara a los callbacksglutMainLoop();
return 0;}
5/21/2018 47508930-ProgramacionAvanzada
50/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 50
Universidad Politcnica de Madrid -UPM
Dicha funcin contiene en su interior un bucle continuo (en caso contrarioterminara la funcin main() y terminara el programa). Dicho bucle continuo sepodra representar a nivel conceptual como:
void glutMainLoop(){
while(1){
if(pulsacion_teclado)OnKeyBoardDown(tecla); //la funcion del usuario
if(hay_que_dibujar)OnDraw(); //la funcion del usuario
if(tiempo_temporizador)OnTimer();//la funcion del usuario
}}
Por lo tanto, si se introduce alguna funcin que bloquee la secuencia continuade ejecucin, la aplicacin se vera bloqueada por completo. Por ejemplo, supngase
que se ubica un scanf() en la funcin CMundo::OnTimer() para cambiar elradio de la pelota:
void CMundo::OnTimer(int value){
printf("Introduzca el radio: ");scanf("%f",&esfera.radio);
jugador1.Mueve(0.025f);jugador2.Mueve(0.025f);esfera.Mueve(0.025f);
El resultado final es la aplicacin bloqueada.
3.3.1Lanzando un hilo
Podramos conseguir el anterior objetivo, mediante el uso de un hilo, de lasiguiente forma:
void* hilo_usuario(void* d){
CMundo* p=(CMundo*) d;while(1)
{printf("Introduzca el radio: ");scanf("%f",&p->esfera.radio);
}}void CMundo::Init(){
//inicializaciones varias
pthread_t thid;pthread_create(&thid,NULL,hilo_usuario,this);
}
En este caso, la esfera esta contenida dentro de la clase CMundo, sin embargo,el hilo es una funcin global, no es una funcin de la clase CMundo. Para conseguir el
5/21/2018 47508930-ProgramacionAvanzada
51/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 51
Universidad Politcnica de Madrid -UPM
acceso del hilo al objeto mundo, lo que se puede hacer es pasarle un puntero al
mismo aprovechando el cuarto parmetro de la funcin pthread_create(). El hilose encargara a su vez de hacer el cast correspondiente para poder acceder a losmiembros de la clase CMundo.
3.4.ESTRUCTURA DEL SERVIDORSe ha visto en los requisitos que es necesario realizar distintas tareas, de forma
simultanea:
El hilo principal del servidor se encargara de realizar la animacin de laescena (a travs de la funcin OnTimer), del dibujo y de enviar los datospor los sockets a los clientes. Como el envo no es bloqueante, no esnecesario crear un hilo para esta tarea.
La aceptacin de nuevos clientes si que es bloqueante. Siempre se tieneque estar ejecutando el accept() si queremos que los clientespuedan conectarse y desconectarse cuando quieran. Por lo tanto esnecesario un hilo para esta tarea.
Para que los clientes remotos puedan efectivamente jugar de formadistribuida, es necesario que enven informacin al servidor. Cada vezque se pulse una tecla, enviaran dicha tecla al servidor. El servidor debede estar esperando a dicho mensaje. El problema es que la recepcin demensajes, en principio tambin es bloqueante, por lo que el programa
queda bloqueado hasta que se recibe dicho mensaje. La solucin esimplementar un hilo para cada uno de los dos jugadores que este a laespera de dichos mensajes.
5/21/2018 47508930-ProgramacionAvanzada
52/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 52
Universidad Politcnica de Madrid -UPM
Figura 3-1. Estructura del servidor
Ntese adems que las frecuencias a las que funcionan los distintos hilos sonmuy variables. El hilo principal ejecuta cada 25 milisegundos, aproximadamente. Sinembargo el hilo de aceptacin de nuevos clientes ejecuta una iteracin del bucle cadavez que se conecta un nuevo cliente, lo que puede tardar de forma variable desdepocos milisegundos a infinito tiempo. Los hilos de recepcin de los comandos de los
jugadores funcionan a una frecuencia variable que coincide con las pulsaciones deteclado de los jugadores.
3.5.MLTIPLES CONEXIONES SIMULTANEASPara permitir la conexin simultanea de mltiples clientes, es necesario
mantener un socket por cada uno de dichos clientes. Para tal efecto declaramos en laclase CMundo(del fichero MundoServidor.h) un vector de la STL de objetos de la claseSocket. Usamos un vector STL porque nos permite de forma cmoda aadir nuevosobjetos, quitar elementos y recorrerlo de forma sencilla. Tambin aadimos unmtodo a CMundo denominado GestionaConexiones(), que se encargara derealizar dicha gestin.
class CMundo
{public:
Programa servidor
//hilo principal
OnTimer(){//tareas//animacion
//envio//datos
}
//hilo de//aceptacion de
//nuevos clientes
while(1){
//accept()}
//hilo de
//recepcion de//comandos del//jugador1
while(1){
//recv()}
//hilo de
//recepcion de//comandos del//jugador2
while(1){
//recv()}
5/21/2018 47508930-ProgramacionAvanzada
53/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 53
Universidad Politcnica de Madrid -UPM
Socket servidor;std::vector conexiones;void GestionaConexiones();
};
A continuacin lanzamos un hilo denominado hilo_conexiones(), y deforma similar a como hacamos anteriormente, pasamos un puntero al objeto actual(this) a dicho hilo. Como es interesante manejarnos dentro de la clase mundo, lanica tarea que tiene que hacer la funcin hilo_conexiones()es invocar al
mtodo GestionaConexiones(). Dicho mtodo entrara en un bucle infinito enel que se repite un accept(). Cada vez que se conecte un cliente, se le aade alvector de clientes conectados.
void* hilo_conexiones(void* d){
CMundo* p=(CMundo*) d;
p->GestionaConexiones();}void CMundo::GestionaConexiones(){
while(1){
Socket s=servidor.Accept();conexiones.push_back(s);
}}void CMundo::Init(){
//inicializacion datos
servidor.InitServer("127.0.0.1",12000);pthread_t thid_hilo_conexiones;pthread_create(&thid_hilo_conexiones,NULL,hilo_conexiones,this);
}
3.6.MOSTRAR LOS CLIENTES CONECTADOSUna ampliacin interesante al apartado anterior seria mostrar en la ventana los
clientes conectados y sus nombres, aparte de los puntos de los dos jugadores. Para elloaadimos un nuevo vector a la clase CMundodel servidor. Tambin transformamos lasvariables de los puntos de los jugadores en un vector:
class CMundo{public:
Socket servidor;std::vector conexiones;std::vector nombres;void GestionaConexiones();
int puntos[2];};
5/21/2018 47508930-ProgramacionAvanzada
54/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 54
Universidad Politcnica de Madrid -UPM
Cada vez que se conecte un cliente nuevo nos deber enviar su nombre, paraaadirlo a nuestro vector. Por lo tanto segn se conecta un cliente, esperamos con unReceive()dicho mensaje con el nombre.
void CMundo::GestionaConexiones(){
while(1){
Socket s=servidor.Accept();char cad[100];s.Receive(cad,100);nombres.push_back(cad);conexiones.push_back(s);
}}
Los nombres de los clientes pueden ser mostrados por pantalla:
void CMundo::OnDraw(){
char cad[100];sprintf(cad,"Servidor");print(cad,300,10,1,0,1);int i;for(i=0;i
5/21/2018 47508930-ProgramacionAvanzada
55/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 55
Universidad Politcnica de Madrid -UPM
3.7.RECEPCIN COMANDOS MOVIMIENTOCuando el programa cliente detecte una pulsacin de teclado, enviara dicha
pulsacin al servidor, para que el servidor la interprete como juzgue necesario. El envodel cliente se realiza fcilmente en la funcin OnKeyboardDown():
void CMundo::OnKeyboardDown(unsigned char key, int x, int y){
char cad[100];sprintf(cad,"%c",key);cliente.Send(cad,strlen(cad)+1);
}
Ntese como este envo se realiza nicamente si el usuario pulsa una tecla. Elhilo implementado en el servidor tendr una forma similar al hilo anterior:
void* hilo_comandos1(void* d){
CMundo* p=(CMundo*) d;
p->RecibeComandosJugador1();}void CMundo::RecibeComandosJugador1(){
while(1){
usleep(10);if(conexiones.size()>=1){
char cad[100];conexiones[0].Receive(cad,100);unsigned char key;sscanf(cad,%c,&key);
if(key=='s')jugador1.velocidad.y=-4;if(key=='w')jugador1.velocidad.y=4;
}}std::cout
5/21/2018 47508930-ProgramacionAvanzada
56/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 56
Universidad Politcnica de Madrid -UPM
3.8.GESTIN DESCONEXIONESEn cualquier instante los clientes espectadores pueden desconectar. Qu pasa
entonces con el vector de sockets mantenido por el servidor? Las desconexionesdeben de ser analizadas y gestionadas adecuadamente.
La forma ms sencilla de detectar las desconexiones es en el envo realizadodentro de la funcin CMundo::OnTimer()en el lado del servidor. El envo hay quehacerlo a todos los clientes conectados. Podramos utilizar el retorno de Send()pararealizar la eliminacin del cliente del vector. No obstante hay que tener en cuenta losefectos del borrado sobre el vector que se est recorriendo.
void CMundo::OnTimer(int value){
for(i=0;i=conexiones[i].Send(cad,200)){
conexiones.erase(conexiones.begin()+i);nombres.erase(nombres.begin()+i);if(i
5/21/2018 47508930-ProgramacionAvanzada
57/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 57
Universidad Politcnica de Madrid -UPM
Aadir una variable denominada acabar, que inicialmente vale 0 a laclase CMundo.
Poner dicha variable a 1 en el destructor de la clase CMundo.
Utilizar la variable como condicin de repeticin en los bucles while()
de los hilos:
while(!acabar){
}
Poner los identificadores de todos los hilos como variables de la claseCMundo, para que puedan ser utilizados en el pthread_join
Ejecutar el pthread_join()tantas veces como sea necesario en el
destructor de la clase CMundo, para esperar a que terminen los hilos.
En este punto se analiza el resultado cuando se cierra el programa servidor.Realmente se est esperando a la finalizacin de los hilos? La respuesta es no. Loshilos estn bloqueados en el accept() y en el recv() por lo que aunquemodifiquemos la bandera acabar esta no es tenida en cuenta hasta la siguiente
iteracin del bucle. Hay que conseguir que se desbloqueen el accept()y el recv()de los hilos, lo que se puede hacer de forma sencilla cerrando el socket del servidor,antesde los pthread_join()
3.10. EJERCICIO PROPUESTO
Realizar la misma tarea con otro juego de complejidad similar.
Analizar los posibles problemas de sincronizacin que pueden apareceren caso de conexiones y desconexiones de clientes.
Aumentar la informacin que se retransmite, para que los clientestengan tambin la informacin de quien esta conectado y quien esta
jugando, as como los puntos actuales de la partida.
5/21/2018 47508930-ProgramacionAvanzada
58/157
5/21/2018 47508930-ProgramacionAvanzada
59/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 59
Universidad Politcnica de Madrid -UPM
44.. COMUNICACIN Y SINCRONIZACININTERPROCESO
4.1.INTRODUCCIN
Existen otros mecanismos para comunicar datos entre distintos procesosdiferentes a los sockets, cuando los procesos se ejecutan en una mquina con unamemoria principal comn y gestionada por un nico sistema operativo(monocomputador). A diferencia de la comunicacin por sockets, que se sueledenominar programacin distribuida, estos mecanismos entran dentro de ladenominada comunicacin interproceso (Inter Process ComunicationIPC). Entre estosmecanismos destacan:
Las tuberas sin nombre (pipes) y con nombre (FIFOS)
La memoria compartida
El hecho de tener varios procesos (o hilos) accediendo a unos datos comunesde forma concurrente puede originar problemas de sincronizacin en esos datos. Paraprevenir estos problemas hay tambin otros mecanismos como:
Los mutex y las variables condicionales
Las tuberas (usadas para sincronizar)
Los semforos
5/21/2018 47508930-ProgramacionAvanzada
60/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 60
Universidad Politcnica de Madrid -UPM
4.2.EL PROBLEMA DE LA SINCRONIZACIONCuando existen varios hilos accediendo de forma concurrente a unos datos, se
pueden presentar problemas de concurrencia. En nuestra aplicacin, tenemos varioshilos accediendo de forma concurrente al vector de conexiones. En concreto el hilo
principal, a travs del timer:void CMundo::OnTimer(int value){
for(i=conexiones.size()-1;i>=0;i--){
char cad[1000];sprintf(cad,"%f %f %f %f %f %f %f %f %f %f",
esfera.centro.x,esfera.centro.y,jugador1.x1,jugador1.y1,jugador1.x2,jugador1.y2,jugador2.x1,jugador2.y1,
jugador2.x2,jugador2.y2);if(0>=conexiones[i].Send(cad,strlen(cad)+1)){
conexiones.erase(conexiones.begin()+i);nombres.erase(conectados.begin()+i);puntos[0]=puntos[1]=0;
}}
}
El hilo de gestin de las conexiones:
void CMundo::GestionaConexiones(){
while(!acabar){Socket s=server.Accept();char cad[100];s.Receive(cad,100);nombres.push_back(cad);conexiones.push_back(s);
}
}
Y los hilos de recepcin de mensajes de los jugadores:
void CMundo::RecibeComandosJugador1(){
Socket s;while(!acabar){
usleep(10);if(conexiones.size()>0){
char cad[100];conexiones[0].Receive(cad,100); //peligrosoprintf("Llego la tecla %c\n",cad[0]);unsigned char key=cad[0];if(key=='s')jugador1.velocidad.y=-4;if(key=='w')jugador1.velocidad.y=4;
}
}}
5/21/2018 47508930-ProgramacionAvanzada
61/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 61
Universidad Politcnica de Madrid -UPM
Ms concretamente: Es posible que mientras el hilo que recibe los mensajes deljugador decide que hay un jugador conectado (conexiones.size()>0), el hiloprincipal que enva los datos por el socket, se de cuenta que dicho cliente ha sidodesconectado y decida borrarlo del vector. Si el vector queda vacio, un acceso aconexiones[0] genera un error fatal segmentation fault, y nuestro servidor
abortara de manera inesperada.
No obstante, en la prctica es bastante improbable que suceda esto, yseguramente serian necesarias cientos de conexiones y desconexiones para que esteefecto fuera visible. Por lo tanto, no abordaremos de momento el problema de lasincronizacin, pero hay que tener en cuenta que en una aplicacin real seratotalmente obligatorio realizar esta sincronizacin, sino nuestro programa podra fallaren un momento inesperado.
Sin embargo si hay un motivo por el que el servidor puede cerrarinesperadamente. Es la recepcin de la seal SIGPIPE cuando se intenta enviar algo por
un socket que ha sido cerrado. Si no se gestiona esta seal, el comportamiento pordefecto termina el programa. La forma ms sencilla de obviar esta seal, es indicar a lafuncin send()en sus banderas, que no enve esta seal en caso de error, lo que sehace de la siguiente forma:
int err=send(sock, cad, length,MSG_NOSIGNAL);
4.3.COMUNICACIN INTERPROCESOEn este tema se propone el siguiente esquema como ejemplo del uso de
distintos mecanismos de comunicacin interproceso:
Figura 4-1. Ejemplo de comunicacin interproceso con tuberas y memoriacompartida
RED
TCP/IPLoggerFIFO Bot
Memoriacompartida
Servidor Cliente
5/21/2018 47508930-ProgramacionAvanzada
62/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 62
Universidad Politcnica de Madrid -UPM
En el computador que corre el servidor, se desarrollara un programa que sirvapara mostrar eventos de una forma ordenada por pantalla, aunque tambin podradecidir guardarlos a disco, a una base de datos, etc. Los eventos sern los puntosmarcados, y quien (el nombre del jugador) que ha marcado un tanto, y sern enviadosmediante cadenas de texto por una tubera con nombre o FIFO, al programa que
llamaremos logger.
En el lado del cliente se desarrollara un programa sencillo que pueda controlarlos movimientos de la raqueta correspondiente automticamente. A este programa lellamaremos bot. El cliente y la aplicacin bot intercambiaran datos en una zona dememoria compartida.
Ambas aplicaciones nuevas sern aplicaciones de tipo consola. El makefile delas cuatro aplicaciones quedara como sigue:
CC=g++CPPFLAGS=-g
LIBS= -lm -lglut -lpthreadOBJS=Esfera.o Plano.o Raqueta.o Vector2D.o Socket.o
all: servidor cliente bot logger
logger: logger.o$(CC) $(CPPFLAGS) -o logger logger.o $(LIBS)
bot: bot.o$(CC) $(CPPFLAGS) -o bot bot.o $(LIBS)
servidor: $(OBJS) MundoServidor.o servidor.o$(CC) $(CPPFLAGS) -o servidor MundoServidor.o servidor.o $(OBJS)
$(LIBS)cliente: $(OBJS) MundoCliente.o cliente.o
$(CC) $(CPPFLAGS) -o cliente MundoCliente.o cliente.o $(OBJS)$(LIBS)depend:
makedepend *.cpp -Yclean:
rm -f *.o servidor cliente bot logger
#DEPENDENCIAS
4.4.TUBERAS CON NOMBRELas tuberas son un mecanismo tanto de comunicacin como de sincronizacin.
Las tuberas sin nombre o pipes se utilizan en procesos que han sido creados mediantefork() y tienen relaciones padre-hijo, de tal forma que heredan dicha tubera.Cuando se trata de procesos totalmente separados, la tubera tiene que ser connombre para que ambos procesos sean capaces de acceder a ella.
Las tuberas con nombre se direccionan como un archivo (un archivo especial)en la estructura de directorios. En las tuberas con nombre tiene que existir un procesoque se encargue de crear dicho pseudoarchivo, que adems tiene que ser el primer
proceso que comience a ejecutar. Dicho proceso podra tener un cdigo como elsiguiente, para enviar por el FIFO una frase a otro proceso que se conecte al mismo:
5/21/2018 47508930-ProgramacionAvanzada
63/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 63
Universidad Politcnica de Madrid -UPM
#include #include #include #include #include #include
int main(int argc,char* argv[]){
mkfifo("/ruta/MiFifo1",0777);
int pipe=open("/ruta/MiFifo1",O_WRONLY);
char cad[150]=Hola que tal;int ret=write(pipe,cad,strlen(cad)+1);
close(pipe);unlink("/ruta/MiFifo1");
return 0;}
Donde:
mkfifo("/ruta/MiFifo1",0777);
crea un archivo con un icono especial en forma de tubera en la ruta indicada, ycon los permisos correspondientes (0777= permisos de lectura, escritura y ejecucinpara todo el mundo).
int pipe=open("/ruta/MiFifo1",O_WRONLY);
La funcin open() abre dicha tubera con el acceso especificado (O_WRONLY,O_RDONLY, O_RDWR) y devuelve un descriptor de archivo (pipe) que es el utilizadopara enviar y recibir datos. Ntese que esta funcin bloquea hasta que se conectaalguien en el otro extremo de la tubera.
A continuacin se hace un envo de datos:
int ret=write(pipe,cad,strlen(cad)+1);
Y finalmente se cierra la tubera y se elimina el pseudoarchivo
close(pipe);unlink("/ruta/MiFifo1");
El otro proceso nicamente debe de abrir la tubera, usarla y cerrarla, pero nocrear el archivo ni borrarlo. Lgicamente, este segundo proceso debe de ser arrancadodespus del anterior, para que la tubera sea creada primero antes de intentar abrirla.
int main(void){
int pipe=open("/ruta/MiFifo1",O_RDONLY);
char cad[150];read(pipe,cad,sizeof(cad));
printf("Cadena=%s\n",cad);
close(pipe);return 1;
}
5/21/2018 47508930-ProgramacionAvanzada
64/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 64
Universidad Politcnica de Madrid -UPM
Hay que recordar que la tubera es un mecanismo totalmente unidireccional, nopermite que el receptor enve datos por la misma tubera. Si se desea implementarcomunicacin bidireccional es necesario el uso de 2 tuberas.
Ejercicio: Implementar el programa Logger y los cambios necesarios en el Servidor,
para que este enve al Logger el nombre y nmero de puntos que lleva un jugadorsolo en el momento de marcar un tanto. Seguir los siguientes pasos:
1. El programa Logger se ejecuta antes que el servidor, por lo tanto ser elencargado de crear y destruir el archivo.
2. El logger entra en un bucle infinito de recepcin de datos.
3. Aadir el identificador del FIFO como atributo de la clase CMundoen elservidor.
4. Abrir la tubera (antes de lanzar los hilos)
5.
Enviar los datos cuando se produzcan puntos.6. Cerrar la tubera adecuadamente
4.5.MEMORIA COMPARTIDALa memoria compartida es un mecanismo exclusivamente de comunicacin que
permite tener en comn una zona de memoria, accesible desde varios procesos.Dichos procesos, una vez inicializada y accedida, vern la zona de memoria compartidacomo memoria propia del proceso. Esta forma de trabajar resulta muy interesante
especialmente si la cantidad de datos a compartir entre los distintos procesos es muyelevada.
Hay distintas interfaces a la memoria compartida, como las funciones de BSD yla memoria compartida POSIX. En este captulo se utiliza la memoria compartidaPOSIX.
As un proceso que quisiera tener en comn una zona de memoria de 10 datosde tipo entero, compartida con otros procesos, podra hacer algo de la forma:
#include #include #include #include #include #include
int main(void){
int datos[10]={0};
//memoria compartidakey_t mi_key=ftok("/bin/ls",12);int shmid=shmget(mi_key,sizeof(datos),0x1ff|IPC_CREAT);char* punt=(char*)shmat(shmid,0,0x1ff);
5/21/2018 47508930-ProgramacionAvanzada
65/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 65
Universidad Politcnica de Madrid -UPM
while(1){
int i,num;printf("Numero de dato: ");scanf("%d",&i);printf("Dato: ");
scanf("%d",&num);datos[i]=num;memcpy(punt,datos,sizeof(datos));
}
shmdt(punt);shmctl(shmid,IPC_RMID,NULL);
return 1;}
Donde
key_t mi_key=ftok("/bin/ls",12);
obtiene una llave nica que sirve para identificar la zona de memoriacompartida. Los parmetros suministrados a esta funcin tienen que ser los mismos enlos diferentes procesos que utilicen la zona de memoria, y son un nombre de archivo(uno cualquiera existente en el sistema de archivos) y un numero entero.
A continuacin se obtiene el descriptor mediante la funcin shmget(), a laque se le indica el tamao en nmero de bytes de la misma, los permisos (0x1ffsignifica acceso total a todos). En el caso que el proceso realmente quiera crear la zonaporque todava no existe, debe especificar la bandera IPC_CREAT.
int shmid=shmget(mi_key,sizeof(datos),0x1ff|IPC_CREAT);
La obtencin de un puntero, cuyo tipo se puede adaptar sencillamente con uncast, se obtiene con la funcin shmat(), a la que se especifican otra vez lospermisos particulares de este acceso.
char* punt=(char*)shmat(shmid,0,0x1ff);
El acceso posterior a la zona de memoria se puede hacer con algn tipo de cast,de indireccin por ndices de un vector o directamente copiando datos a esa zona dememoria. Una vez terminada de utilizar, es necesario soltar el puntero asignado yliberar la zona de memoria:
shmdt(punt);
shmctl(shmid,IPC_RMID,NULL);
Como en el caso anterior, el proceso que efectivamente crea la zona dememoria debe de ser arrancado antes que los procesos que accedan a ella. Uno deestos procesos, podra tener el aspecto siguiente:
#include #include #include #include #include #include
int main(void){
int datos[10];
5/21/2018 47508930-ProgramacionAvanzada
66/157
Rodrguez-Losada & San Segundo, 2009.Programacin Avanzada, Concurrente y Distribuida 66
Universidad Politcnica de Madrid -UPM
int i;key_t mi_key=ftok("/bin/ls",12);int shmid=shmget(mi_key,sizeof(datos),0x1ff);char* punt=(char*)shmat(shmid,0,0x1ff);
while(1)
{ memcpy(datos,punt,sizeof(datos));for(i=0;i=0;i--){
char cad[1000];sprintf(cad,"%d %f %f %f %f %f %f %f %f %f %f",
i,esfera.centro.x,esfera.centro.y,jugador1.x1,jugador1.y1,jugador1.x2,jugador1.y2,jugador2.x1,jugador2.y1,jugador2.x2,jugador2.y2);
}
El cliente, tambin aadir una variable denominada num_clientea la claseCMundo, y la extraer convenientemente de la cadena recibida.
Es necesario aadir las variables siguientes a la clase CMundodel cliente, paraque acceda adecuadamente a la zona de memoria compartida:
#include "DatosMemCompartida.h"
5/21/2018 47508930-ProgramacionAvanzada
67/157
Rodrgue