From 03ba295570995741aefea39be9839708cdaf36ec Mon Sep 17 00:00:00 2001 From: Josh Larson Date: Sun, 3 Jan 2016 04:02:32 -0600 Subject: [PATCH] Added 1s heartbeat and 5s timeout for the connection --- src/com/projectswg/ClientConnection.java | 23 +++++++- src/com/projectswg/ClientReceiver.java | 13 ++++- src/com/projectswg/ClientSender.java | 2 +- .../projectswg/networking/swg/HeartBeat.java | 52 +++++++++++++++++++ 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/com/projectswg/networking/swg/HeartBeat.java diff --git a/src/com/projectswg/ClientConnection.java b/src/com/projectswg/ClientConnection.java index b483315..d06c731 100644 --- a/src/com/projectswg/ClientConnection.java +++ b/src/com/projectswg/ClientConnection.java @@ -1,18 +1,25 @@ package com.projectswg; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import com.projectswg.ClientReceiver.ClientReceiverCallback; import com.projectswg.ClientReceiver.ConnectionState; import com.projectswg.networking.NetInterceptor; +import com.projectswg.networking.swg.HeartBeat; public class ClientConnection { + private ScheduledExecutorService pinger; private ClientSender sender; private ClientReceiver receiver; private ClientCallback callback; private NetInterceptor interceptor; + private boolean connected; public ClientConnection(int loginPort, int zonePort) { - this.callback = null; + callback = null; + connected = false; interceptor = new NetInterceptor(); sender = new ClientSender(interceptor, loginPort, zonePort); receiver = new ClientReceiver(interceptor); @@ -31,9 +38,12 @@ public class ClientConnection { public void onConnected() { ClientConnection.this.onConnected(); } public void onConnectionChanged(ConnectionState state) { ClientConnection.this.onConnectionStateChanged(state); } }); + pinger = Executors.newSingleThreadScheduledExecutor(); + pinger.scheduleAtFixedRate(()->ping(), 0, 1000, TimeUnit.MILLISECONDS); } public void stop() { + pinger.shutdownNow(); sender.stop(); receiver.stop(); if (callback != null) @@ -54,6 +64,7 @@ public class ClientConnection { } private void onDisconnected() { + connected = false; if (callback != null) callback.onDisconnected(); sender.reset(); @@ -61,6 +72,7 @@ public class ClientConnection { } private void onConnected() { + connected = true; if (callback != null) callback.onConnected(); sender.reset(); @@ -80,6 +92,15 @@ public class ClientConnection { } } + private void ping() { + if (!connected) + return; + if (receiver.getTimeSinceLastPacket() > 5000) + onDisconnected(); + else + sender.send(new HeartBeat().encode().array()); + } + public interface ClientCallback { void onConnected(); void onDisconnected(); diff --git a/src/com/projectswg/ClientReceiver.java b/src/com/projectswg/ClientReceiver.java index b572b9a..3157ea8 100644 --- a/src/com/projectswg/ClientReceiver.java +++ b/src/com/projectswg/ClientReceiver.java @@ -5,6 +5,7 @@ import java.nio.ByteOrder; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; import com.projectswg.networking.NetInterceptor; import com.projectswg.networking.UDPServer.UDPPacket; @@ -27,6 +28,7 @@ public class ClientReceiver { private static final int MAX_PACKET_SIZE = 496; private final NetInterceptor interceptor; + private final AtomicLong lastPacket; private ExecutorService executor; private ClientSender sender; private ClientReceiverCallback callback; @@ -37,6 +39,7 @@ public class ClientReceiver { public ClientReceiver(NetInterceptor interceptor) { this.interceptor = interceptor; + this.lastPacket = new AtomicLong(0); setConnectionState(ConnectionState.DISCONNECTED); sender = null; callback = null; @@ -65,11 +68,16 @@ public class ClientReceiver { rxSequence = -1; } + public double getTimeSinceLastPacket() { + return (System.nanoTime() - lastPacket.get()) / 1E6; + } + public void onPacket(boolean zone, UDPPacket packet) { if (packet.getData().length < 2) return; if (zone && packet.getData().length == 4) { // Ping sender.sendRaw(packet.getPort(), packet.getAddress(), packet.getData()); + lastPacket.set(System.nanoTime()); return; } ByteBuffer data = ByteBuffer.wrap(packet.getData()).order(ByteOrder.BIG_ENDIAN); @@ -79,10 +87,12 @@ public class ClientReceiver { this.port = packet.getPort(); sender.setZone(zone); sender.setPort(packet.getPort()); + lastPacket.set(System.nanoTime()); process(data); } else { if (packet.getPort() != port || port == 0) return; + lastPacket.set(System.nanoTime()); executor.submit(() -> { process(ByteBuffer.wrap(Encryption.decode(data.array(), 0)).order(ByteOrder.BIG_ENDIAN)); }); @@ -193,11 +203,10 @@ public class ClientReceiver { } private void onOutOfOrder(OutOfOrder ooo) { - System.out.println("OOO " + ooo.getSequence()); + } private void onAcknowledge(Acknowledge ack) { - System.out.println("ACK " + ack.getSequence()); sender.onAcknowledge(ack.getSequence()); } diff --git a/src/com/projectswg/ClientSender.java b/src/com/projectswg/ClientSender.java index 795e3b6..bcb28d2 100644 --- a/src/com/projectswg/ClientSender.java +++ b/src/com/projectswg/ClientSender.java @@ -103,7 +103,7 @@ public class ClientSender { public void disconnect(DisconnectReason reason) { if (connectionId != -1) - sendRaw(new Disconnect(connectionId, reason).encode().array()); + send(new Disconnect(connectionId, reason)); connectionId = -1; } diff --git a/src/com/projectswg/networking/swg/HeartBeat.java b/src/com/projectswg/networking/swg/HeartBeat.java new file mode 100644 index 0000000..99237e7 --- /dev/null +++ b/src/com/projectswg/networking/swg/HeartBeat.java @@ -0,0 +1,52 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package com.projectswg.networking.swg; + +import java.nio.ByteBuffer; + +import com.projectswg.networking.SWGPacket; + +public class HeartBeat extends SWGPacket { + + public static final int CRC = com.projectswg.networking.encryption.CRC.getCrc("HeartBeat"); + + public HeartBeat() { + + } + + public void decode(ByteBuffer data) { + super.decode(data, CRC); + } + + public ByteBuffer encode() { + ByteBuffer data = ByteBuffer.allocate(6); + addShort(data, 1); + addInt( data, CRC); + return data; + } +}