Skip to content

Protocolo (Networking) Ejemplo

Joaquín edited this page Aug 9, 2020 · 3 revisions

Networking

Tanto el cliente como el servidor envian paquetes usando KryoNet. Esto quiere decir que una vez declaradas las clases que se van a usar en la clase NetworkDirectory, podemos enviarlas a través de la red y de ambos lados van a saber serializar y deserializar dicho objeto. Todas estas clases estan en el modulo shared ya que estan tanto del lado del cliente como del servidor. Por ejemplo:

public NetworkDictionary() {
        registerAll(
                // Game Requests
                MovementRequest.class,
                AttackRequest.class,
                MeditateRequest.class,
                TalkRequest.class,
                ItemActionRequest.class,
                TakeItemRequest.class,
                SpellCastRequest.class,
                TimeSyncRequest.class,

                // Game Responses
                MovementResponse.class,
                TimeSyncResponse.class,
                ...
        )
}

Cada una de estas clases debería implementar/extender de alguna de las siguientes: IRequest, IResponse o INotification. Esto es porque tanto el cliente como el servidor tienen "procesadores" por cada uno de estos (IResponseProcessor, IRequestProcessor, INotificationProcessor)

Entonces al recibir un paquete, si este es instancia de alguna de estas interfaces, se procesara como corresponda, en caso de no extender de ninguno, el paquete se ignora.

Cliente

Procesadores

  • ClientResponseProcessor
  • GameNotificationProcessor

ClientSystem

Este sistema es el que se encarga de recibir objetos y procesarlos.

public void received(int connectionId, Object object) {
    Gdx.app.postRunnable(() -> {
        Log.info(object.toString());
        if (object instanceof IResponse) {
            ((IResponse) object).accept(responseProcessor);
        } else if (object instanceof INotification) {
            ((INotification) object).accept(notificationProcessor);
        }
    });
}

Server

Hoy en dia tenemos un servidor que hace de Lobby (la clase se llama Finsiterra) y por cada sala/juego, tenemos un "Server"

Procesadores

  • FinisterraRequestProcessor: procesa y tiene la logica de todo lo relacionado al Lobby o las salas.
  • ServerRequestProcessor: recibira todo lo relacionado a una partida.
  • ServerNotificationProcessor: algunas notificaciones de parte de algun cliente, como cosas relacionadas a los items, inventarios, etc.

ServerSystem

Utiliza una cola para guardar los paquetes, a medida que los recibe los agrega a la cola (concurrente) y en el proximo ciclo del servidor, se procesan.

@Override
public void received(int connectionId, Object object) {
    netQueue.add(new NetworkJob(connectionId, object));
}

private void processJob(NetworkJob job) {
    int connectionId = job.connectionId;
    Object object = job.receivedObject;
    if (object instanceof IRequest) {
        ((IRequest) object).accept(requestProcessor, connectionId);
    } else if (object instanceof INotification) {
        ((INotification) object).accept(notificationProcessor);
    }
}

@Override
protected void processSystem() {
    super.processSystem();
    while (netQueue.peek() != null) {
        processJob(netQueue.poll());
    }
}

Enviar notificaciones de sonido.

El objetivo es que el server notifique a un cliente cuando tiene que reproducir un sonido. Lo primero que tenemos que hacer es definir un paquete, por ejemplo, creamos una clase que implemente INotification:

package shared.network.sound;

import shared.network.interfaces.INotification;
import shared.network.interfaces.INotificationProcessor;

public class SoundNotification implements INotification {

    private int soundNumber;

    // Constructor vacio necesario para que Kryo pueda recrear el objeto
    public SoundNotification() {}

    public SoundNotification(int soundNumber) {
        this.soundNumber = soundNumber;
    }

    public int getSoundNumber() {
        return soundNumber;
    }

    public void setSoundNumber(int soundNumber) {
        this.soundNumber = soundNumber;
    }

    @Override
    public void accept(INotificationProcessor processor) {
        // hay que crear el metodo para esta clase en la interfaz INotificationProcessor 
        processor.processNotification(this);
    }
}

Ahora nos falta enviar este paquete por parte del servidor, y del lado del cliente, reproducirlo luego de recibir dicho paquete:

  • Digamos que, por ejemplo, queremos reproducir el sonido del rechazo de un ataque con un escudo. En la metodo de la clase PhysicalCombatSystem::canAttack se encarga de calcular la probabilidad de que el usuario rechace con escudo. Primero tenemos que pensar en quien va a escuchar el sonido, en este caso, todos los que esten cerca, para esto podemos usar el metodo WorldManager::notifyUpdate que lo que hace es, enviar un paquete al usuario, y a todos los que esten en un rango de "15" tiles. En caso de querer que se entere solo un cliente, podemos usar WorldManager::sendEntityUpdate.
// shield evasion
if (E(targetId.get()).hasShield() && ThreadLocalRandom.current().nextInt(101) <= prob) {
    notifyCombat(targetId.get(), SHIELD_DEFENSE);
    notifyCombat(userId, format(DEFENDED_WITH_SHIELD, getName(targetId.get())));
    // TODO send sound
    getWorldManager().notifyUpdate(targetId.get(), new SoundNotification(10));
} else {
    ...
}
  • El procesador (GameNotificationProcessor) de notificaciones del cliente debe implementar el metodo:
@Override 
public void processNotification(SoundNotification soundNotification) {
    int soundNumber = soundNotification.getSoundNumber();
    // TODO obtener el sonido a reproducir
    // TODO reproducir
}

Get Starting

Requirements

Source Code

Reference

run

Graphics

balance

external documentation

Clone this wiki locally