diff --git a/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableClient.java b/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableClient.java new file mode 100644 index 00000000..de15d325 --- /dev/null +++ b/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableClient.java @@ -0,0 +1,24 @@ +package com.esotericsoftware.kryonet.examples.unreliable; + +public class UnreliableClient { + private final UnreliableConnection client = new UnreliableConnection("Client"); + + private UnreliableClient() throws Exception { + client.start(); + client.connect(5000, UnreliableConnection.localhost, UnreliableConnection.serverTcpPort, UnreliableConnection.serverUdpPort); + + client.addListener(null); + for (int i=0; i<100; i++) { + String object = String.format("#%02d", i); + client.sendUDP(object); + Thread.sleep(10); + } + } + public static void main(String[] args) { + try { + new UnreliableClient(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableConnection.java b/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableConnection.java new file mode 100644 index 00000000..70c41dd6 --- /dev/null +++ b/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableConnection.java @@ -0,0 +1,49 @@ +package com.esotericsoftware.kryonet.examples.unreliable; + +import com.esotericsoftware.kryonet.Client; +import com.esotericsoftware.kryonet.Connection; +import com.esotericsoftware.kryonet.Listener; + +public class UnreliableConnection extends Client { + static final int serverTcpPort = 54555; + static final int serverUdpPort = 54777; + static final String localhost = "127.0.0.1"; + + static final int lagMillisMin = 100; + static final int lagMillisMax = 250; + static final float lossPercentage = 0.1f; + static final float duplicationPercentage = 0.03f; + + private final String name; + + UnreliableConnection(final String name) { + this.name = name; + + } + + @Override + public void addListener(final Listener listener) { + super.addListener(new Listener.UnreliableListener( + UnreliableConnection.lagMillisMin, UnreliableConnection.lagMillisMax, + UnreliableConnection.lossPercentage, UnreliableConnection.duplicationPercentage, + new Listener() { + @Override + public void received(Connection connection, Object object) { + System.out.println("["+name+"]: recving "+object); + if (listener != null) { + listener.received(connection, object); + } + } + })); + } + + + @Override + public int sendUDP(Object object) { + System.out.println("["+name+"]: sending "+object); + return super.sendUDP(object); + } + + + +} diff --git a/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableServer.java b/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableServer.java new file mode 100644 index 00000000..8139d423 --- /dev/null +++ b/examples/com/esotericsoftware/kryonet/examples/unreliable/UnreliableServer.java @@ -0,0 +1,35 @@ +package com.esotericsoftware.kryonet.examples.unreliable; + +import com.esotericsoftware.kryonet.Connection; +import com.esotericsoftware.kryonet.Listener; +import com.esotericsoftware.kryonet.Server; + +public class UnreliableServer { + private final Server server = new Server() { + @Override + protected Connection newConnection() { + return new UnreliableConnection("Server"); + } + }; + + private UnreliableServer() throws Exception { + server.addListener(new Listener() { + @Override + public void received(Connection connection, Object object) { + connection.sendUDP(object); + } + }); + + server.start(); + server.bind(UnreliableConnection.serverTcpPort, UnreliableConnection.serverUdpPort); + } + + public static void main(String[] args) { + try { + new UnreliableServer(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/com/esotericsoftware/kryonet/Listener.java b/src/com/esotericsoftware/kryonet/Listener.java index fd14c6be..35c33d82 100644 --- a/src/com/esotericsoftware/kryonet/Listener.java +++ b/src/com/esotericsoftware/kryonet/Listener.java @@ -140,7 +140,7 @@ public void queue (Runnable runnable) { * separate thread after a delay. Note that only incoming objects are delayed. To delay outgoing objects, use a LagListener at * the other end of the connection. */ static public class LagListener extends QueuedListener { - private final ScheduledExecutorService threadPool; + protected final ScheduledExecutorService threadPool; private final int lagMillisMin, lagMillisMax; final LinkedList runnables = new LinkedList(); @@ -151,11 +151,14 @@ public LagListener (int lagMillisMin, int lagMillisMax, Listener listener) { threadPool = Executors.newScheduledThreadPool(1); } + protected int calculateLag() { + return lagMillisMin + (int)(Math.random() * (lagMillisMax - lagMillisMin)); + } + public void queue (Runnable runnable) { synchronized (runnables) { runnables.addFirst(runnable); } - int lag = lagMillisMin + (int)(Math.random() * (lagMillisMax - lagMillisMin)); threadPool.schedule(new Runnable() { public void run () { Runnable runnable; @@ -164,7 +167,39 @@ public void run () { } runnable.run(); } - }, lag, TimeUnit.MILLISECONDS); + }, calculateLag(), TimeUnit.MILLISECONDS); + } + } + + /** + * Delays, reorders and does not make guarantees to the delivery of incoming objects + * to the wrapped listener (in order to simulate lag, jitter, package loss and + * package duplication). + * Notification events are likely processed on a separate thread after a delay. + * Note that only the delivery of incoming objects is modified. To modify the delivery + * of outgoing objects, use a UnreliableListener at the other end of the connection. + */ + static public class UnreliableListener extends LagListener { + private final float lossPercentage; + private final float duplicationPercentage; + + public UnreliableListener (int lagMillisMin, int lagMillisMax, float lossPercentage, + float duplicationPercentage, Listener listener) { + super(lagMillisMin, lagMillisMax, listener); + this.lossPercentage = lossPercentage; + this.duplicationPercentage = duplicationPercentage; + } + + public void queue (final Runnable runnable) { + do { + if (Math.random() >= lossPercentage) { + threadPool.schedule(new Runnable() { + public void run () { + runnable.run(); + } + }, calculateLag(), TimeUnit.MILLISECONDS); + } + } while (Math.random() < duplicationPercentage); } } }