Improved UDP code to improve performance and reduce bandwidth

This commit is contained in:
Josh Larson
2016-01-04 15:44:15 -06:00
parent 03ba295570
commit 892ebd3c86
6 changed files with 373 additions and 125 deletions
+15
View File
@@ -23,6 +23,7 @@ public class ClientConnection {
interceptor = new NetInterceptor();
sender = new ClientSender(interceptor, loginPort, zonePort);
receiver = new ClientReceiver(interceptor);
sender.setClientReceiver(receiver);
receiver.setClientSender(sender);
}
@@ -34,10 +35,12 @@ public class ClientConnection {
sender.setZoneCallback((packet) -> receiver.onPacket(true, packet));
receiver.setReceiverCallback(new ClientReceiverCallback() {
public void onPacket(byte[] data) { ClientConnection.this.onPacket(data); }
public void onUdpRecv(boolean zone, byte[] data) { ClientConnection.this.onUdpRecv(zone, data); }
public void onDisconnected() { ClientConnection.this.onDisconnected(); }
public void onConnected() { ClientConnection.this.onConnected(); }
public void onConnectionChanged(ConnectionState state) { ClientConnection.this.onConnectionStateChanged(state); }
});
sender.setSenderCallback((zone, data) -> onUdpSent(zone, data));
pinger = Executors.newSingleThreadScheduledExecutor();
pinger.scheduleAtFixedRate(()->ping(), 0, 1000, TimeUnit.MILLISECONDS);
}
@@ -63,6 +66,16 @@ public class ClientConnection {
callback.onPacket(data);
}
private void onUdpSent(boolean zone, byte [] data) {
if (callback != null)
callback.onUdpSent(zone, data);
}
private void onUdpRecv(boolean zone, byte [] data) {
if (callback != null)
callback.onUdpRecv(zone, data);
}
private void onDisconnected() {
connected = false;
if (callback != null)
@@ -105,6 +118,8 @@ public class ClientConnection {
void onConnected();
void onDisconnected();
void onPacket(byte [] data);
void onUdpSent(boolean zone, byte [] data);
void onUdpRecv(boolean zone, byte [] data);
}
}
+11 -2
View File
@@ -78,6 +78,8 @@ public class ClientReceiver {
if (zone && packet.getData().length == 4) { // Ping
sender.sendRaw(packet.getPort(), packet.getAddress(), packet.getData());
lastPacket.set(System.nanoTime());
if (callback != null)
callback.onUdpRecv(zone, packet.getData());
return;
}
ByteBuffer data = ByteBuffer.wrap(packet.getData()).order(ByteOrder.BIG_ENDIAN);
@@ -89,12 +91,17 @@ public class ClientReceiver {
sender.setPort(packet.getPort());
lastPacket.set(System.nanoTime());
process(data);
if (callback != null)
callback.onUdpRecv(zone, data.array());
} 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));
ByteBuffer decoded = ByteBuffer.wrap(Encryption.decode(data.array(), 0)).order(ByteOrder.BIG_ENDIAN);
process(decoded);
if (callback != null)
callback.onUdpRecv(zone, decoded.array());
});
}
}
@@ -203,10 +210,11 @@ 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());
}
@@ -221,6 +229,7 @@ public class ClientReceiver {
void onDisconnected();
void onConnectionChanged(ConnectionState state);
void onPacket(byte [] data);
void onUdpRecv(boolean zone, byte [] data);
}
public enum ConnectionState {
+72 -42
View File
@@ -25,7 +25,6 @@ import com.projectswg.networking.soe.Disconnect.DisconnectReason;
public class ClientSender {
private static final InetAddress ADDR = InetAddress.getLoopbackAddress();
private static final int MAX_PACKET_SIZE = 496;
private final NetInterceptor interceptor;
private UDPServer loginServer;
@@ -33,6 +32,8 @@ public class ClientSender {
private Queue<SequencedOutbound> sentPackets;
private Queue<byte []> inboundQueue;
private ExecutorService executor;
private ClientSenderCallback callback;
private ClientReceiver receiver;
private short txSequence;
private int connectionId;
private int port;
@@ -73,6 +74,10 @@ public class ClientSender {
zoneServer.close();
}
public void setClientReceiver(ClientReceiver receiver) {
this.receiver = receiver;
}
public void setLoginCallback(UDPCallback callback) {
loginServer.setCallback(callback);
}
@@ -81,6 +86,10 @@ public class ClientSender {
zoneServer.setCallback(callback);
}
public void setSenderCallback(ClientSenderCallback callback) {
this.callback = callback;
}
public int getZonePort() {
return zoneServer.getPort();
}
@@ -147,6 +156,8 @@ public class ClientSender {
zoneServer.send(port, addr, data);
else
loginServer.send(port, addr, data);
if (callback != null)
callback.onUdpSent(zone, data);
}
public void send(Packet packet) {
@@ -167,49 +178,24 @@ public class ClientSender {
private void outboundRunnable() {
final InetAddress addr = InetAddress.getLoopbackAddress();
double time = 0;
long lastBurst = 0;
double timeSinceBurst = 0;
while (true) {
synchronized (sentPackets) {
int sent = 0;
for (SequencedOutbound packet : sentPackets) {
sendRaw(port, addr, packet.getData());
Thread.yield();
if (++sent >= 500)
break;
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
break;
}
}
}
private void inboundRunnable() {
byte [] data;
Queue<byte []> outbound = new ArrayBlockingQueue<>(32, true);
int size = 0;
while (true) {
size = 6;
synchronized (inboundQueue) {
while (!inboundQueue.isEmpty()) {
data = interceptor.interceptServer(inboundQueue.poll());
if ((size + data.length > MAX_PACKET_SIZE-7 && !outbound.isEmpty()) || outbound.size() == 32) {
createOutboundPacket(outbound, size);
size = 6;
time = receiver.getTimeSinceLastPacket();
timeSinceBurst = (System.nanoTime() - lastBurst) / 1E6;
if (time <= timeSinceBurst || (time >= 100 && timeSinceBurst >= 500)) {
synchronized (sentPackets) {
int sent = 0;
for (SequencedOutbound packet : sentPackets) {
sendRaw(port, addr, packet.getData());
Thread.yield();
if (++sent >= 500)
break;
}
outbound.add(data);
size += data.length + 1 + (data.length >= 0xFF ? 2 : 0);
if (size > MAX_PACKET_SIZE-4) {
createOutboundPacket(outbound, size);
size = 6;
}
Thread.yield();
}
lastBurst = System.nanoTime();
}
if (!outbound.isEmpty())
createOutboundPacket(outbound, size);
size = 0;
try {
Thread.sleep(25);
} catch (InterruptedException e) {
@@ -218,8 +204,48 @@ public class ClientSender {
}
}
private void inboundRunnable() {
final int dataHeaderSize = 8;
byte [] data;
Queue<byte []> outbound = new ArrayBlockingQueue<>(8, true);
int size = 0;
while (true) {
size = dataHeaderSize;
synchronized (inboundQueue) {
while (!inboundQueue.isEmpty()) {
data = interceptor.interceptServer(inboundQueue.poll());
if ((size + getPacketLength(data) >= 496 && !outbound.isEmpty()) || outbound.size() == 8) {
createOutboundPacket(outbound, size);
size = dataHeaderSize;
}
outbound.add(data);
size += getPacketLength(data);
if (size >= 496) {
createOutboundPacket(outbound, size);
size = dataHeaderSize;
}
Thread.yield();
}
}
if (!outbound.isEmpty())
createOutboundPacket(outbound, size);
try {
Thread.sleep(25);
} catch (InterruptedException e) {
break;
}
}
}
private int getPacketLength(byte [] data) {
if (data.length >= 255)
return data.length + 3;
else
return data.length + 1;
}
private void createOutboundPacket(Queue<byte []> outbound, int size) {
if (size <= MAX_PACKET_SIZE-4) { // Fits into single data packet
if (size <= 496) { // Fits into single data packet
DataChannelA channel = new DataChannelA();
channel.setSequence(txSequence++);
while (!outbound.isEmpty())
@@ -227,7 +253,7 @@ public class ClientSender {
send(channel);
} else { // Fragmented
while (!outbound.isEmpty()) {
Fragmented [] frags = Fragmented.encode(ByteBuffer.wrap(outbound.poll()), txSequence, MAX_PACKET_SIZE);
Fragmented [] frags = Fragmented.encode(ByteBuffer.wrap(outbound.poll()), txSequence);
txSequence += frags.length;
for (Fragmented frag : frags) {
send(frag);
@@ -236,6 +262,10 @@ public class ClientSender {
}
}
public interface ClientSenderCallback {
void onUdpSent(boolean zone, byte [] data);
}
private static class SequencedOutbound implements SequencedPacket {
private short sequence;
+153
View File
@@ -0,0 +1,153 @@
package com.projectswg;
import java.net.InetAddress;
import java.util.concurrent.atomic.AtomicLong;
import com.projectswg.ClientConnection.ClientCallback;
import com.projectswg.ServerConnection.ServerCallback;
public class Connections {
private final ServerConnection server;
private final ClientConnection client;
private final AtomicLong tcpRecv;
private final AtomicLong tcpSent;
private final AtomicLong udpRecv;
private final AtomicLong udpSent;
private ConnectionCallback callback;
private InetAddress addr;
private int port;
public Connections() {
server = new ServerConnection();
client = new ClientConnection(44453, 44463);
tcpRecv = new AtomicLong(0);
tcpSent = new AtomicLong(0);
udpRecv = new AtomicLong(0);
udpSent = new AtomicLong(0);
addr = ServerConnection.DEFAULT_ADDR;
port = ServerConnection.DEFAULT_PORT;
callback = null;
setCallbacks();
}
public void initialize() {
client.start();
}
public void terminate() {
server.stop();
client.stop();
}
public void setCallback(ConnectionCallback callback) {
this.callback = callback;
}
public void setRemote(InetAddress addr, int port) {
if (this.addr.equals(addr) && this.port == port)
return;
terminate();
server.setRemoteAddress(addr, port);
initialize();
}
public InetAddress getAddress() {
return addr;
}
public int getPort() {
return port;
}
public long getTcpRecv() {
return tcpRecv.get();
}
public long getTcpSent() {
return tcpSent.get();
}
public long getUdpRecv() {
return udpRecv.get();
}
public long getUdpSent() {
return udpSent.get();
}
private void setCallbacks() {
client.setCallback(new ClientCallback() {
public void onPacket(byte[] data) { onDataSentTcp(data); }
public void onUdpSent(boolean zone, byte[] data) { onDataSentUdp(data); }
public void onUdpRecv(boolean zone, byte[] data) { onDataRecvUdp(data); }
public void onDisconnected() { onClientDisconnected(); }
public void onConnected() { onClientConnected(); }
});
server.setCallback(new ServerCallback() {
public void onData(byte[] data) { onDataRecvTcp(data); }
public void onConnected() { onServerConnected(); }
public void onDisconnected() { onServerDisconnected(); }
});
}
private void onServerConnected() {
if (callback != null)
callback.onServerConnected();
}
private void onServerDisconnected() {
if (callback != null)
callback.onServerDisconnected();
}
private void onClientConnected() {
server.start();
if (callback != null)
callback.onClientConnected();
}
private void onClientDisconnected() {
server.stop();
if (callback != null)
callback.onClientDisconnected();
}
private void onDataRecvTcp(byte [] data) {
tcpRecv.addAndGet(data.length);
client.send(data);
if (callback != null)
callback.onDataRecvTcp(data);
}
private void onDataSentTcp(byte [] data) {
tcpSent.addAndGet(data.length);
server.forward(data);
if (callback != null)
callback.onDataSentTcp(data);
}
private void onDataRecvUdp(byte [] data) {
udpRecv.addAndGet(data.length);
if (callback != null)
callback.onDataRecvUdp(data);
}
private void onDataSentUdp(byte [] data) {
udpSent.addAndGet(data.length);
if (callback != null)
callback.onDataSentUdp(data);
}
public interface ConnectionCallback {
void onServerConnected();
void onServerDisconnected();
void onClientConnected();
void onClientDisconnected();
void onDataRecvTcp(byte [] data);
void onDataSentTcp(byte [] data);
void onDataRecvUdp(byte [] data);
void onDataSentUdp(byte [] data);
}
}
+114 -73
View File
@@ -2,145 +2,186 @@ package com.projectswg;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicLong;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import com.projectswg.ClientConnection.ClientCallback;
import com.projectswg.ServerConnection.ServerCallback;
import com.projectswg.Connections.ConnectionCallback;
public class Forwarder extends Application {
public class Forwarder extends Application implements ConnectionCallback {
private static final String [] DATA_NAMES = new String[]{"B", "KB", "MB", "GB", "TB"};
private static final String SERVER_CONN_STRING = "Server Connection: %s";
private static final String CLIENT_CONN_STRING = "Client Connection: %s";
private static final String SERVER_RX_STRING = "TCP Recv: %s";
private static final String SERVER_TX_STRING = "TCP Sent: %s";
private final ServerConnection server;
private final ClientConnection client;
private final AtomicLong receivedBytes;
private final AtomicLong transmittedBytes;
private final Connections connections;
private final TextField serverIpField;
private final TextField serverPortField;
private final Button serverSetButton;
private final Text serverConnectionText;
private final Text clientConnectionText;
private final Text serverReceivedText;
private final Text serverTransmittedText;
private final Text serverRxText;
private final Text serverTxText;
private final Text clientRxText;
private final Text clientTxText;
public static void main(String [] args) {
launch(args);
}
public Forwarder() {
receivedBytes = new AtomicLong(0);
transmittedBytes = new AtomicLong(0);
connections = new Connections();
serverIpField = new TextField(ServerConnection.DEFAULT_ADDR.getHostAddress());
serverPortField = new TextField(Integer.toString(ServerConnection.DEFAULT_PORT));
serverConnectionText = new Text(String.format(SERVER_CONN_STRING, getConnectionStatus(false)));
clientConnectionText = new Text(String.format(CLIENT_CONN_STRING, getConnectionStatus(false)));
serverReceivedText = new Text(String.format(SERVER_RX_STRING, getByteName(receivedBytes.get())));
serverTransmittedText = new Text(String.format(SERVER_TX_STRING, getByteName(transmittedBytes.get())));
serverSetButton = new Button("Set");
serverConnectionText = new Text(getConnectionStatus(false));
clientConnectionText = new Text(getConnectionStatus(false));
serverRxText = new Text(getByteName(connections.getTcpRecv()));
serverTxText = new Text(getByteName(connections.getTcpSent()));
clientRxText = new Text(getByteName(connections.getUdpRecv()));
clientTxText = new Text(getByteName(connections.getUdpSent()));
updateConnection(serverConnectionText, false);
updateConnection(clientConnectionText, false);
updateServerButton();
EventHandler<KeyEvent> handler = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.ENTER)
updateServerIp();
else
updateServerButton();
}
};
serverIpField.setOnKeyPressed(handler);
serverPortField.setOnKeyPressed(handler);
server = new ServerConnection();
client = new ClientConnection(44453, 44463);
client.setCallback(new ClientCallback() {
@Override
public void onPacket(byte[] data) {
transmittedBytes.addAndGet(data.length);
server.forward(data);
Platform.runLater(() -> serverTransmittedText.setText(String.format(SERVER_TX_STRING, getByteName(transmittedBytes.get()))));
}
@Override
public void onDisconnected() {
Platform.runLater(() -> clientConnectionText.setText(String.format(CLIENT_CONN_STRING, getConnectionStatus(false))));
server.stop();
}
@Override
public void onConnected() {
Platform.runLater(() -> clientConnectionText.setText(String.format(CLIENT_CONN_STRING, getConnectionStatus(true))));
server.start();
}
});
server.setCallback(new ServerCallback() {
@Override
public void onData(byte[] data) {
receivedBytes.addAndGet(data.length);
client.send(data);
Platform.runLater(() -> serverReceivedText.setText(String.format(SERVER_RX_STRING, getByteName(receivedBytes.get()))));
}
@Override
public void onConnected() {
Platform.runLater(() -> serverConnectionText.setText(String.format(SERVER_CONN_STRING, getConnectionStatus(true))));
}
@Override
public void onDisconnected() {
Platform.runLater(() -> serverConnectionText.setText(String.format(SERVER_CONN_STRING, getConnectionStatus(false))));
serverIpField.setOnKeyTyped(handler);
serverPortField.setOnKeyTyped(handler);
serverSetButton.setOnAction(new EventHandler<ActionEvent>(){
public void handle(ActionEvent event) {
updateServerIp();
}
});
connections.setCallback(this);
}
private void initialize() {
client.start();
@Override
public void onServerConnected() {
Platform.runLater(() -> updateConnection(serverConnectionText, true));
}
private void terminate() {
server.stop();
client.stop();
@Override
public void onServerDisconnected() {
Platform.runLater(() -> updateConnection(serverConnectionText, false));
}
@Override
public void onClientConnected() {
Platform.runLater(() -> updateConnection(clientConnectionText, true));
}
@Override
public void onClientDisconnected() {
Platform.runLater(() -> updateConnection(clientConnectionText, false));
}
@Override
public void onDataRecvTcp(byte[] data) {
Platform.runLater(() -> serverRxText.setText(getByteName(connections.getTcpRecv())));
}
@Override
public void onDataSentTcp(byte[] data) {
Platform.runLater(() -> serverTxText.setText(getByteName(connections.getTcpSent())));
}
@Override
public void onDataRecvUdp(byte[] data) {
Platform.runLater(() -> clientRxText.setText(getByteName(connections.getUdpRecv())));
}
@Override
public void onDataSentUdp(byte[] data) {
Platform.runLater(() -> clientTxText.setText(getByteName(connections.getUdpSent())));
}
private void updateConnection(Text t, boolean status) {
t.setText(getConnectionStatus(status));
t.setFill(status ? Color.GREEN : Color.RED);
}
private void updateServerIp() {
try {
InetAddress addr = InetAddress.getByName(serverIpField.getText());
int port = Integer.parseInt(serverPortField.getText());
terminate();
server.setRemoteAddress(addr, port);
initialize();
connections.setRemote(addr, port);
updateServerButton();
} catch (UnknownHostException e) {
e.printStackTrace();
System.err.println("Unknown IP: " + serverIpField.getText());
} catch (NumberFormatException e) {
System.err.println("Invalid Port: " + serverPortField.getText());
}
}
private void updateServerButton() {
try {
InetAddress addr = InetAddress.getByName(serverIpField.getText());
int port = Integer.parseInt(serverPortField.getText());
if (!addr.equals(connections.getAddress()) || port != connections.getPort())
serverSetButton.setDisable(false);
else
serverSetButton.setDisable(true);
} catch (UnknownHostException | NumberFormatException e) {
serverSetButton.setDisable(true);
}
}
@Override
public void start(Stage primaryStage) throws Exception {
initialize();
connections.initialize();
GridPane root = new GridPane();
root.add(serverIpField, 0, 0);
root.add(serverPortField, 1, 0);
root.add(serverConnectionText, 0, 1);
root.add(clientConnectionText, 0, 2);
root.add(serverReceivedText, 0, 3);
root.add(serverTransmittedText, 0, 4);
for (int i = 0; i < 4; i++) {
ColumnConstraints cc = new ColumnConstraints();
cc.setPercentWidth(25);
root.getColumnConstraints().add(cc);
}
root.add(serverIpField, 0, 0, 2, 1);
root.add(serverPortField, 2, 0, 1, 1);
root.add(serverSetButton, 3, 0, 1, 1);
root.add(new Text("Server Connection:"), 0, 1, 2, 1);
root.add(serverConnectionText, 2, 1, 2, 1);
root.add(new Text("Client Connection:"), 0, 2, 2, 1);
root.add(clientConnectionText, 2, 2, 2, 1);
root.add(new Text("Sent"), 1, 3, 1, 1);
root.add(new Text("Recv"), 2, 3, 1, 1);
root.add(new Text("TCP"), 0, 4, 1, 1);
root.add(serverTxText, 1, 4, 1, 1);
root.add(serverRxText, 2, 4, 1, 1);
root.add(new Text("UDP"), 0, 5, 1, 1);
root.add(clientTxText, 1, 5, 1, 1);
root.add(clientRxText, 2, 5, 1, 1);
Scene scene = new Scene(root, 300, 160);
scene.setRoot(root);
primaryStage.setTitle("Holocore Forwarder");
primaryStage.setScene(scene);
primaryStage.setMinWidth(scene.getWidth());
primaryStage.setMinHeight(scene.getHeight());
primaryStage.setMaxWidth(scene.getWidth());
primaryStage.setMaxHeight(scene.getHeight());
root.setOnMouseClicked((event) -> root.requestFocus());
root.requestFocus();
primaryStage.show();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
public void handle(WindowEvent we) {
terminate();
connections.terminate();
primaryStage.close();
}
});
@@ -71,12 +71,12 @@ public class Fragmented extends Packet implements SequencedPacket {
return data;
}
public Fragmented [] encode(int startSequence, int maxSize) {
public Fragmented [] encode(int startSequence) {
packet.position(0);
int ord = 0;
Fragmented [] packets = new Fragmented[(int) Math.ceil((packet.remaining()+4)/(maxSize-7.0))];
Fragmented [] packets = new Fragmented[(int) Math.ceil((packet.remaining()+4)/489.0)];
while (packet.remaining() > 0) {
packets[ord] = createSegment(startSequence++, ord++, maxSize, packet);
packets[ord] = createSegment(startSequence++, ord++, packet);
}
return packets;
}
@@ -108,19 +108,19 @@ public class Fragmented extends Packet implements SequencedPacket {
public short getSequence() { return sequence; }
public int getDatLength() { return length; }
public static final Fragmented [] encode(ByteBuffer data, int startSequence, int maxSize) {
public static final Fragmented [] encode(ByteBuffer data, int startSequence) {
data.position(0);
int ord = 0;
Fragmented [] packets = new Fragmented[(int) Math.ceil((data.remaining()+4)/(maxSize-7.0))];
Fragmented [] packets = new Fragmented[(int) Math.ceil((data.remaining()+4)/489.0)];
while (data.remaining() > 0) {
packets[ord] = createSegment(startSequence++, ord++, maxSize, data);
packets[ord] = createSegment(startSequence++, ord++, data);
}
return packets;
}
private static final Fragmented createSegment(int startSequence, int ord, int maxSize, ByteBuffer packet) {
private static final Fragmented createSegment(int startSequence, int ord, ByteBuffer packet) {
int header = (ord == 0) ? 8 : 4;
ByteBuffer data = ByteBuffer.allocate(Math.min(packet.remaining()+header, maxSize-3));
ByteBuffer data = ByteBuffer.allocate(Math.min(packet.remaining()+header, 493));
addNetShort(data, 0x0D);
addNetShort(data, startSequence);