26

Índice de Contenidos :. - Escuela Superior de ... · Índice de Contenidos :. [Contenido] ... proporciona un canal de comunicación ... dentro de nuestro proyecto, se pueden incluir

Embed Size (px)

Citation preview

Índice de Contenidos :.[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 2 

Introducción General :: 1TCP Vs UDP :: 2

Cliente/Servidor TCP :: 2.1 Cliente/Servidor UDP :: 2.2

Servidores Multicliente :: 3Gestión de Sockets en Blender :: 3.1

Implementación de Persistencia :: 3.2 Cliente de Chat en Blender :: 3.3

Diseño Multijugador :: 4Técnicas de Construcción :: 4.1

Gestión Avanzada :: 5

Introducción General :.

● Multijugador: Es el área donde se preven más avances.● Incremento de la adicción debido al componente humano.● Cómo funciona Internet:

● Red orientada a paquetes; diferentes caminos.● Tolerante a fallos; se adapta a las propiedades de la red en

cada momento.● 2 operaciones fundamentales:

● División y Unión de paquetes: TCP.● Enrutado de los paquetes: IP.

● Opciones:● Protocolos orientados a la conexión. TCP.● Protocolos sin conexión. UDP.

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 3 

¿Qué es un Socket? :.● API para aislar la complejidad del trabajo con redes. ● Similar a acceder a un fichero; "apertura", "lectura", "escritura"...● Socket: dispositivo de entrada/salida que proporciona un canal de comunicación entre dos computadores. ● Nos proporciona automáticamente el mecanismo de partición y ensamblado de los paquetes en el destino. ● Según quién proporcione el servicio a quién, distinguimos dos tipos de máquinas: clientes y servidores. ● Pueden utilizar el protocolo TCP o UDP.

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Socket

Socket

Sesión 7 :: Transp. 4 

TCP Vs. UDP :.[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

TCP UDP

● Orientado a la conexión.● Paquetes de tamaño variable.● Garantiza la recepción.● FIFO.● Lento.

Aplicaciones● Juegos con movimientos esenciales; no se puede perder datos.● Estrategia.● RPGs.● Tablero● Etc...

● No mantiene la conexión.● Paquetes de tamaño fijo. ● No garantiza nada. ● En cualquier orden.● Rápido.

Aplicaciones● Fundamentalmente juegos en tiempo real, con alta tasa de frames por segundo.● Acción.● Deportivos. ● Etc...

Sesión 7 :: Transp. 5 

Cliente Servidor UDP Mínimo :.

import socket

puerto = 40000host = "localhost"s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.sendto ("Primer ejemplo del curso.", (host, puerto))

Ver ClienteUDP.py

import socket

puerto = 40000s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.bind(("", puerto)) print "Esperando tramas..."while 1: # Recibimos 1024 bytes en un datagrama datos, direccion = s.recvfrom(1024) print "Recibido: ", datos, " desde ", direccion

Ver ServidorUDP.py

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 6 

import socket

puerto = 40000host = "localhost"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind((host, puerto))# Numero max. de clientes en espera s.listen(5)print "Esperando conexiones..."try: while 1: nuevo_sock, dir = s.accept() print "Conectado desde ", dir # Bucle para servir a cada cliente while 1: datos = nuevo_sock.recv(1024) if not datos: break nuevo_sock.send(datos) nuevo_sock.close()finally: s.close()

Cliente Servidor TCP Mínimo :.

import socket

puerto = 40000host = "localhost"s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)# Conectamos con el servidor # en el puerto indicados.connect((host, puerto))# Enviamos peticion al hosts.send("Hola servidor!\n")# Recibimos la respuesta del # servidor (1024 bytes)respuesta = s.recv(1024)print respuestas.close()

Ver ClienteTCP.py Ver ServidorTCP.py

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 7 

Servidores Multicliente :.[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

● En escenarios de trabajo reales, se requieren servidores de, al menos 6-8 jugadores.● Sin embargo, las primitivas de envío y recepción por sockets son, por defecto, bloqueantes.● Debemos tener en cuenta la gestión de la conexión únicamente cuando haya datos que tratar (o no nos importe quedarnos bloqueados). 2 alternativas:

● Servidores concurrentes. ● Servidores iterativos con primitivas

no bloqueantes.

Sesión 7 :: Transp. 8 

Servidores Concurrentes :.● Lanzamos proceso hijo para cada conexión aceptada.● En sistemas UNIX, llamada a Fork, en sistemas Windows CreateThread.

h1

h2

h3

SocketAbierto

1. Crear el socket y enlazar con bind el puerto y la dirección IP.2. Llamada a listen: modo pasivo.3. Esperamos nueva conexión con accept.4. Llegada de una nueva conexión: Creación de un nuevo proceso/hilo.

4.1. Proceso padre: Volver al paso 3

4.2. Proceso hijo: 4.2.1. Bucle send-recv con la

nueva conexión.4.2.2. Cuando termine, close()

Pseudocódigo del Servidor

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 9 

Servidores Iterativos :.● Un único proceso. Nos aseguramos que no hay bloqueo. ● Bloqueos:

● Pedimos leer antes de que lleguen los datos.

● Leemos menos de lo esperado.● Errores, buffers llenos...

h1

SocketAbierto

● MSG_OOB: Out-Of-Band. Marca especial del cliente que envía como urgente. Se extrae de forma individual.● MSG_PEEK: Permite preguntar en el socket sobre los datos que contiene sin leer de él.

Flags interesantes...

Socket NO Bloqueante

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 10 

Un Ejemplo: Servidor de Chat :.

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('', 4000))s.setblocking(0)s.listen(1)

while not terminar: # Hasta que manden un "shutdown"u = ProcesaNuevaConexion() # Hay conexiones nuevas?if u:

lista_usuarios.append(u)print len(lista_usuarios)," conexion(es)"

for u in lista_usuarios: # Estan los usuarios vivos?u.Autentificar()try:

mensaje = u.conexion.recv(1024)if mensaje:

print "Desde",u.nombre,': ['+mensaje+']'u.ProcesaMensaje(mensaje)if mensaje == "shutdown": terminar=1

except: pass

for u in lista_usuarios:u.conexion.close()

Ver ServidorChat.py

(Fragmento de Código 1/3)

HOST = ''PORT = 4000FINLINEA = "\r\n"lista_usuarios = []terminar = 0sPregNombre = 0sEsperaNombre = 1sConectado = 2

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 11 

Un Ejemplo: Servidor de Chat :.

class Usuario:def __init__(self):

self.nombre = ""self.direccion = ""self.conexion = Noneself.estado = sPregNombre

def Autentificar(self):if self.estado == sPregNombre: self.PreguntaNombre()

def PreguntaNombre(self):self.conexion.send("Nombre? ")self.estado = sEsperaNombre

def ProcesaMensaje(self, msg):print "Procesando mensaje: ",msgglobal lista_usuariosif self.estado == sEsperaNombre: # Estamos esperando el nombre? Lo guardamos

if len(msg) < 2 or msg=="#": return # Quitamos ruido del telnet...print "Cliente conectado: ",msgself.nombre = msgself.estado = sConectadoself.conexion.send("--> Hola, "+self.nombre+" bienvenido"+FINLINEA)broadcast("--> "+self.nombre+" se ha conectado."+FINLINEA)return

broadcast("<"+self.nombre+"> "+msg+FINLINEA)

Ver ServidorChat.py

(Fragmento de Código 2/3)

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 12 

Un Ejemplo: Servidor de Chat :.

def ProcesaNuevaConexion():try:

conexion, direccion = s.accept()except:

return Noneprint "Conexion desde:", direccionconexion.setblocking(0)user = Usuario();user.conexion = conexionuser.direccion = direccionreturn user

def broadcast(msg): # Enviar un mensaje a todos los usuarios conectadosfor u in lista_usuarios:

u.conexion.send(msg)

Ver ServidorChat.py

(Fragmento de Código 3/3)

Ejercicio

● Implementar en el servidor dos comandos que puedan utilizar los usuarios del chat; "list", que mostrará (al usuario que lanzó la orden) un listado de la gente conectada en ese momento y "quit", que desconectará al usuario.

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 13 

Ajustando Blender para usar Sockets :.● Blender no posee soporte nativo para Sockets. ● En un subdirectorio "lib", dentro de nuestro proyecto, se pueden incluir librerías de python 2.0 que se deseen utilizar. ● El ejecutable generado funcionará correctamente si incluimos las DLLs fmod.dll y python20.dll (y se construye con dynamic runtime). ● Ejemplo, introducimos un plano en la escena y se añaden los siguientes bloques lógicos.

Nombre del script cliente de la ventana de código

Ver carpeta lib,client.py, server.py

Soporte de Sockets en Blender

● Importante: Los ficheros contenidos en la carpeta lib han sido modificados para eliminar las dependencias con otros módulos. No es la distribución oficial de sockets Python.

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 14 

Problemas de Persistencia... :.

import socket

class miSocket:class __miSocket:

def __init__(self):self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)self.sock.connect(("localhost",4000))

instancia = Nonedef __init__(self):

if not miSocket.instancia:miSocket.instancia = miSocket.__miSocket()miSocket.instancia.sock.setblocking(0)

else:pass

def enviar(self, msg):return self.instancia.sock.send(msg)

def recibir(self):try:

return self.instancia.sock.recv(1024)except:

passdef desconectar(self):

self.instancia.sock.close()Ver misocket.py

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 15 

Cliente de Chat en Blender :.

● Añadimos dos planos a la escena, y los colocamos perpendiculares a la cámara. Uno servirá para mostrar el texto que enviamos y otro el que recibimos (los renombramos a "Local" y "Remoto"). ● En modo de selección de caras F, ajustamos la textura al tamaño de una letra de la imagen (arialbd.tga) previamente cargada. ● Activamos en los botones de pintado, la propiedad Text y Alpha (para indicar que va a mostrar una fuente con fondo transparente).

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 16 

Bloques Lógicos y Código :.

import GameLogicfrom misocket import * c = GameLogic.getCurrentController()owner = c.getOwner()

miSocket().enviar(owner.Text)

● En el plano Local, ajustamos una propiedad que se tiene que llamar Text. Recogemos todas las pulsaciones de teclado sobre esa variable (sensor guardaText). Cuando se pulse Return, se limpia (Assign "").● Además, cuando se pulse Return, llamamos a nuestra clase persistente de envío de datos "miSocket".

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 17 

Bloques Lógicos y Código :.

import GameLogicfrom misocket import *

msj = miSocket().recibir()c = GameLogic.getCurrentController()owner = c.getOwner()owner.Text = msj[:-2]

● De igual forma, el plano Remoto requiere los siguientes bloques lógicos. En este caso, el texto se añade por código Python en recibir.

Ejercicio:

Añadir los elementos necesarios en el chat anterior para que permita ver, al menos, las dos últimas líneas recibidas.

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 18 

Diseñando Juegos Cliente/Servidor :.

● Arquitectura típica usada en juegos de poca carga del servidor (nº reducido de clientes)

Servidor TCP Servidor UDP

AcceptEnvío de Tramas

(2ª Fase)

Cliente 1 Cliente 2

● Dos fases diferenciadas; carga y transferencia de datos. ● ¿Qué ocurre si un cliente pierde su conexión?... Debemos localizar el problema, poner un socket en modo "accept" y esperar a que el cliente vuelva a enganchar.

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 19 

Moviendo Objetos... :.

● Comencemos por un ejemplo sencillo que cambia la posición de los objetos. Añadimos un Empty, y un cubo (el cubo en una capa oculta). Añadimos el siguiente código y bloques lógicos al Empty.

import GameLogic

contr = GameLogic.getCurrentController()move = contr.getActuator("move")random = GameLogic.getRandomFloat()

move.setDRot(0,(random-0.5)/5,0,1)move.setDLoc(random/5,0,0,1)

GameLogic.addActiveActuator(move,1)

Sesión 7 :: Transp. 20 

2 Jugadores en Red con Blender :.● Añadimos 3 objetos. Un Empty que servirá para iniciar el juego (en una capa oculta), un objeto (cubo) que representará a nuestro jugador "Yo", y otro objeto (esfera) que representará al contrario "Otro". Bloques Lógicos del objeto Empty:

import GameLogicfrom misocket import *

c = GameLogic.getCurrentController()owner = c.getOwner()while (owner.jugador == -1):

msj = miSocket().recibir()if msj:

owner.jugador = int(msj)

Código de init

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 21 

2 Jugadores en Red con Blender :.● Bloques lógicos del Jugador "Yo":

import GameLogicfrom misocket import * c = GameLogic.getCurrentController()owner = c.getOwner()

if (owner.njugador!= -1):n = owner.njugadorx,y,z = owner.getPosition()mensaje = str(n)+':'+str(x)+':'+str(y)+':'+str(z)

miSocket().enviar(mensaje)Código de enviar

Los sensores de teclado se encargan únicamente de desplazar el objeto (con actuadores Motion, dLoc)

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 22 

2 Jugadores en Red con Blender :.● Bloques lógicos del Jugador "Otro":

import GameLogicfrom misocket import *

c = GameLogic.getCurrentController()owner = c.getOwner()actuador = c.getActuator("mover")msj = miSocket().recibir()if (msj!=None):

njugador, x, y, z = [float(e) for e in msj.split(':')]ox, oy, oz = owner.getPosition()actuador.setDLoc(x-ox, y-oy, z-oz, 1)

else :actuador.setDLoc(0,0,0,1)

GameLogic.addActiveActuator(actuador,1)Código de recibir

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 23 

De 2 Jugadores a Multijugador... :.

Ejercicio

Realizar los cambios en el ejemplo anterior para que soporte un número mayor de clientes. En concreto, realizar una adaptación para 3 clientes (generalizable sin cambios en el código a N clientes). ● ¿Es necesario realizar cambios en el servidor?● ¿Es necesario variar la arquitectura del cliente?

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 24 

Manejo del Lag... Extrapolación de Datos :.[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

● Las redes, a menudo, no son tan rápidas y eficientes como quisiéramos. Hay que intentar eliminar el Lag.● Partimos de un conjunto de valores conocidos (N) en el espacio; P0, P1, P2..., y el instante de tiempo en el que han sucedido (T); T0, T1, T2.... ● Extrapolamos con polinomios de grado 2 (curva parabólica) que, además de interpolar la curva, permiten extrapolar valores, y preguntar por trayectorias futuras. ● Arregla problemas con Lags medios.

PosiciónextrapoladaP(T) = aT2+bT+c

Px(T) = axT2+bxT+cPy(T) = ayT2+byT+c

Sesión 7 :: Transp. 25 

Técnicas de Aceleración :.

● Mensajes Jerárquicos: Clasificación del tipo de mensajes, enviando a cada cliente de mayor prioridad a menos según la calidad de su enlace.

● Envío Único de Cambios de Estado: El cliente se convierte en poco más que un terminal gráfico. Todo el cálculo (totalmente determinista) se propaga al servidor. Los clientes sólo informan de los cambios en su estado. Hay que tener especial cuidado con las tareas que requieren aleatoriedad. Uso de tablas aleatorias precalculadas.

● Subdivisión Espacial: Especialmente utilizada en Juegos Multijugador Masivos. Establecer, en una estructura de datos, la posición relativa entre grupos de jugadores. Sólo los que estén dentro de un grupo recibirán mensajes de sus vecinos. Problema: Síndrome de Braveheart.

[Contenido] 1. Intro :: 2. TCP/UDP :: 3. Chat :: 4. Multijugador :: 5. Av.

Sesión 7 :: Transp. 26