0

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)

clzola
  • 1,925
  • 3
  • 30
  • 49
  • Why? Why not have the client provide its own name in the connect request? Don't add callbacks when you don't need them. – user207421 Jan 18 '16 at 01:01
  • How do I add name in connect request? Code above is just simple test to see how RMI works with KryoNet. If this does not work how can I expect for other more complex calls to succeed. Also a question, I would like to know if I can use KryoNet RMI on android devices? – clzola Jan 18 '16 at 01:20
  • Pass the name as another parameter to the method, after adjusting its signature accordingly. Or else get it directly from the `Player,` after the cast. Your second question is rhetorical. I have no information about Android. I've never seen the point of KryoNet myself. You could implement all this with just vanilla RMI. – user207421 Jan 21 '16 at 21:03

1 Answers1

1

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);
        }
    }));
Eashi
  • 347
  • 1
  • 9