javanetwork-programmingrmikryonet

Kryonet RMI, cannot wait for response on connection's update thread


I would like to make a game using LibGDX and Kryonet library, using RMI. So I created clean project. What I want to do for now is, setup server to listen on port 10048 and on new connection to print client's name which will I get by calling a method on client's class...

Here is the code:

ICardsTableImpl.java

package clzola.cardstable.client;

public interface ICardsTableGameImpl {
    public String getName();
}

CardsTableServer.java

package clzola.cardstable.server;

import clzola.cardstable.client.ICardsTableGameImpl;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Server;
import com.esotericsoftware.kryonet.rmi.ObjectSpace;
import com.esotericsoftware.minlog.Log;

import java.io.IOException;
import java.util.HashMap;


public class CardsTableServer extends Server {
    private HashMap<Integer, Connection> connections;

    public CardsTableServer() throws IOException {
        connections = new HashMap<Integer, Connection>();
        addListener(new NetworkListener(this));

        Kryo kryo = getKryo();
        ObjectSpace.registerClasses(kryo);
        kryo.register(ICardsTableGameImpl.class);

        bind(10048);
    }

    @Override
    protected Connection newConnection() {
        Player player = new Player();
        addConnection(player);
        return player;
    }


    public void addConnection(Connection connection) {
        this.connections.put(connection.getID(), connection);
    }

    public Connection getConnection(int connectionId) {
        return this.connections.get(connectionId);
    }

    public Connection removeConnection(int connectionId) {
        return this.connections.remove(connectionId);
    }

    public static void main(String[] args) throws IOException {
        Log.set(Log.LEVEL_DEBUG);
        CardsTableServer server = new CardsTableServer();
        server.start();
    }
}

NetworkListener.java

package clzola.cardstable.server;

import clzola.cardstable.client.ICardsTableGameImpl;
import com.badlogic.gdx.Gdx;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.rmi.ObjectSpace;

public class NetworkListener extends Listener {
    private CardsTableServer server;

    public NetworkListener(CardsTableServer server) {
        this.server = server;
    }

    @Override
    public void connected(Connection connection) {
        Player player = ((Player) connection);

        ICardsTableGameImpl game = ObjectSpace.getRemoteObject(player, 0, ICardsTableGameImpl.class);
        player.name = game.getName(); // This is where I get excpetion...

        Gdx.app.log("Server", "Player name: " + player.name);
    }

    @Override
    public void disconnected(Connection connection) {
        server.removeConnection(connection.getID());
    }
}

Player.java

package clzola.cardstable.server;

import com.esotericsoftware.kryonet.Connection;

public class Player extends Connection {
    public String name;
}

CardsTableGame.java

package clzola.cardstable.client;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.kryonet.rmi.ObjectSpace;

public class CardsTableGame extends ApplicationAdapter implements ICardsTableGameImpl {
    SpriteBatch batch;
    Stage stage;
    Client client;
    String name = "Lazar";
    ObjectSpace objectSpace;

    @Override
    public void create () {
        batch = new SpriteBatch();
        stage = new Stage(new ScreenViewport(), batch);

        try {
            client = new Client();
            client.start();

            Kryo kryo = client.getKryo();
            ObjectSpace.registerClasses(kryo);
            kryo.register(ICardsTableGameImpl.class);

            ObjectSpace objectSpace = new ObjectSpace();
            objectSpace.register(0, this);
            objectSpace.addConnection(client);

            client.connect(5000, "127.0.0.1", 10048);
        } catch (Exception e) {
            Gdx.app.log("CardsTableGame", e.getMessage(), e);
        }
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    }

    @Override
    public String getName() {
        return this.name;
    }
}

After running it, I get exception on the server side:

Exception in thread "Server" java.lang.IllegalStateException: Cannot wait for an RMI response on the connection's update thread.
    at com.esotericsoftware.kryonet.rmi.ObjectSpace$RemoteInvocationHandler.waitForResponse(ObjectSpace.java:420)
    at com.esotericsoftware.kryonet.rmi.ObjectSpace$RemoteInvocationHandler.invoke(ObjectSpace.java:408)
    at com.sun.proxy.$Proxy0.getName(Unknown Source)
    at clzola.cardstable.server.NetworkListener.connected(NetworkListener.java:24)
    at com.esotericsoftware.kryonet.Server$1.connected(Server.java:48)
    at com.esotericsoftware.kryonet.Connection.notifyConnected(Connection.java:214)
    at com.esotericsoftware.kryonet.Server.acceptOperation(Server.java:417)
    at com.esotericsoftware.kryonet.Server.update(Server.java:249)
    at com.esotericsoftware.kryonet.Server.run(Server.java:372)
    at java.lang.Thread.run(Thread.java:745)

And I have no idea why... What am I doing wrong?? (This is the first time ever I am trying to use RMI)


Solution

  • The Listener is executed by the Kryonet-update-thread. This thread is checking the socket regularly to receive the messages. Calling game.getName() makes the caller wait until the answer was delivered over the network. If you do that on the update thread you'd probably put your server in deadlock because kryonet cannot receive the answer it is waiting on, since you block the update thread. This is why it throws the exception.

    In an rmi example from the kryonet git they solve this problem by using a Listener working on its own thread.

    // The ThreadedListener means the network thread won't be blocked when waiting for RMI responses.
        client.addListener(new ThreadedListener(new Listener() {
            public void connected (final Connection connection) {
                TestObject test = ObjectSpace.getRemoteObject(connection, 42, TestObject.class);
                // Normal remote method call.
                assertEquals(43.21f, test.other());
                // Make a remote method call that returns another remote proxy object.
                OtherObject otherObject = test.getOtherObject();
                // Normal remote method call on the second object.
                assertEquals(12.34f, otherObject.value());
                // When a remote proxy object is sent, the other side recieves its actual remote object.
                connection.sendTCP(otherObject);
            }
        }));