mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-30 04:00:50 +01:00
extensions: Switch to NIO sockets & cleanup
* We're now using non-blocking sockets with extensions. This along with direct buffers should remove the lag suffered when using extensions. Signed-off-by: Eduardo Alonso <edu@error404software.com>
This commit is contained in:
parent
8fe3f8f7ec
commit
cdf98ebd5e
@ -5,7 +5,9 @@ import gearth.protocol.HPacket;
|
||||
import gearth.ui.extensions.Extensions;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -23,8 +25,8 @@ public abstract class Extension implements IExtension{
|
||||
void act(String[] args);
|
||||
}
|
||||
|
||||
protected boolean canLeave; // can you disconnect the ext
|
||||
protected boolean canDelete; // can you delete the ext (will be false for some built-in extensions)
|
||||
private boolean canLeave; // can you disconnect the ext
|
||||
private boolean canDelete; // can you delete the ext (will be false for some built-in extensions)
|
||||
|
||||
private String[] args;
|
||||
private boolean isCorrupted = false;
|
||||
@ -32,7 +34,8 @@ public abstract class Extension implements IExtension{
|
||||
private static final String[] FILE_FLAG = {"--filename", "-f"};
|
||||
private static final String[] COOKIE_FLAG = {"--auth-token", "-c"}; // don't add a cookie or filename when debugging
|
||||
|
||||
private OutputStream out = null;
|
||||
private SocketChannel gEarthExtensionServer = null;
|
||||
|
||||
private final Map<Integer, List<MessageListener>> incomingMessageListeners = new HashMap<>();
|
||||
private final Map<Integer, List<MessageListener>> outgoingMessageListeners = new HashMap<>();
|
||||
private FlagsCheckListener flagRequestCallback = null;
|
||||
@ -87,37 +90,29 @@ public abstract class Extension implements IExtension{
|
||||
String file = getArgument(args, FILE_FLAG);
|
||||
String cookie = getArgument(args, COOKIE_FLAG);
|
||||
|
||||
Socket gEarthExtensionServer = null;
|
||||
try {
|
||||
gEarthExtensionServer = new Socket("127.0.0.1", port);
|
||||
gEarthExtensionServer.setTcpNoDelay(true);
|
||||
InputStream in = gEarthExtensionServer.getInputStream();
|
||||
DataInputStream dIn = new DataInputStream(in);
|
||||
out = gEarthExtensionServer.getOutputStream();
|
||||
gEarthExtensionServer = SocketChannel.open();
|
||||
gEarthExtensionServer.connect(new InetSocketAddress("127.0.0.1", port));
|
||||
gEarthExtensionServer.socket().setTcpNoDelay(true);
|
||||
|
||||
while (!gEarthExtensionServer.isClosed()) {
|
||||
ByteBuffer bbLength = ByteBuffer.allocateDirect(4);
|
||||
|
||||
while (gEarthExtensionServer.isOpen()) {
|
||||
int length;
|
||||
try {
|
||||
length = dIn.readInt();
|
||||
}
|
||||
catch(EOFException exception) {
|
||||
//g-earth closed the extension
|
||||
|
||||
if (gEarthExtensionServer.read(bbLength) == -1)
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] headerandbody = new byte[length + 4];
|
||||
length = bbLength.getInt(0);
|
||||
bbLength.flip();
|
||||
|
||||
int amountRead = 0;
|
||||
ByteBuffer headerAndBody = ByteBuffer.allocateDirect(4 + length);
|
||||
headerAndBody.putInt(0); // this will be the length
|
||||
gEarthExtensionServer.read(headerAndBody);
|
||||
|
||||
while (amountRead < length) {
|
||||
amountRead += dIn.read(headerandbody, 4 + amountRead, Math.min(dIn.available(), length - amountRead));
|
||||
}
|
||||
|
||||
HPacket packet = new HPacket(headerandbody);
|
||||
HPacket packet = new HPacket(headerAndBody);
|
||||
packet.fixLength();
|
||||
|
||||
|
||||
if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.INFOREQUEST) {
|
||||
ExtensionInfo info = getInfoAnnotations();
|
||||
|
||||
@ -132,7 +127,9 @@ public abstract class Extension implements IExtension{
|
||||
.appendString(cookie == null ? "" : cookie)
|
||||
.appendBoolean(canLeave)
|
||||
.appendBoolean(canDelete);
|
||||
writeToStream(response.toBytes());
|
||||
|
||||
ByteBuffer extInfo = ByteBuffer.wrap(response.toBytes());
|
||||
gEarthExtensionServer.write(extInfo);
|
||||
}
|
||||
else if (packet.headerId() == Extensions.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) {
|
||||
String host = packet.readString();
|
||||
@ -204,7 +201,6 @@ public abstract class Extension implements IExtension{
|
||||
response.appendLongString(habboMessage.stringify());
|
||||
|
||||
writeToStream(response.toBytes());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +209,7 @@ public abstract class Extension implements IExtension{
|
||||
// e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
if (gEarthExtensionServer != null && !gEarthExtensionServer.isClosed()) {
|
||||
if (gEarthExtensionServer != null && gEarthExtensionServer.isOpen()) {
|
||||
try {
|
||||
gEarthExtensionServer.close();
|
||||
} catch (IOException e) {
|
||||
@ -225,7 +221,7 @@ public abstract class Extension implements IExtension{
|
||||
|
||||
private void writeToStream(byte[] bytes) throws IOException {
|
||||
synchronized (this) {
|
||||
out.write(bytes);
|
||||
gEarthExtensionServer.write(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,13 @@ public class HPacket implements StringifyAble {
|
||||
public HPacket(byte[] packet) {
|
||||
packetInBytes = packet.clone();
|
||||
}
|
||||
|
||||
public HPacket(ByteBuffer packet) {
|
||||
packetInBytes = new byte[packet.capacity()];
|
||||
for (int i = 0; i < packetInBytes.length; i++)
|
||||
packetInBytes[i] = packet.get(i);
|
||||
}
|
||||
|
||||
public HPacket(HPacket packet) {
|
||||
packetInBytes = packet.packetInBytes.clone();
|
||||
isEdited = packet.isEdited;
|
||||
|
@ -36,7 +36,7 @@ import java.util.*;
|
||||
* Why? Because Habbo relies on the TCP protocol, which ENSURES that packets get received in the right order, so we will not be fucking that up.
|
||||
* That means that all packets following the packet you're manipulating in your extension will be blocked from being sent untill you're done.
|
||||
* TIP: If you're trying to replace a packet in your extension but you know it will take time, just block the packet, end the method, and let something asynchronous send
|
||||
* the editted packet when you're done.
|
||||
* the edited packet when you're done.
|
||||
*
|
||||
*
|
||||
* You may ignore everything beneath this line if you're extending the abstract Extension class we provide in Java.
|
||||
@ -44,13 +44,13 @@ import java.util.*;
|
||||
*
|
||||
* (0. We recommend to use a cross-platform language for your extension)
|
||||
*
|
||||
* 1. An extension will run as a seperate process on your device and has to be called with the flag "-p <PORT>",
|
||||
* 1. An extension will run as a separate process on your device and has to be called with the flag "-p <PORT>",
|
||||
* where <PORT> is a random port where the G-Earth local extension server will run on. Your extension has to connect with this server.
|
||||
*
|
||||
* 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the exension.
|
||||
* 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the extension.
|
||||
* Same story goes for closing the connection between the program and G-Earth, only once (on uninstall or close of G-Earth).
|
||||
*
|
||||
* You may also run your extension completely seperate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth
|
||||
* You may also run your extension completely separate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth
|
||||
* (but you have to configure the port yourself, which will be displayed in the extension page)
|
||||
*
|
||||
* 3. Once a connection is made, your extension will have to deal with the following incoming & outgoing messages as described (follows the same protocol structure as Habbo communication does):
|
||||
@ -209,7 +209,6 @@ public class Extensions extends SubForm {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
extension.addOnReceiveMessageListener(respondCallback);
|
||||
@ -250,7 +249,6 @@ public class Extensions extends SubForm {
|
||||
|
||||
|
||||
HashMap<GEarthExtension, GEarthExtension.ReceiveMessageListener> messageListeners = new HashMap<>();
|
||||
try {
|
||||
extensionsRegistrer = new GEarthExtensionsRegistrer(new GEarthExtensionsRegistrer.ExtensionRegisterObserver() {
|
||||
@Override
|
||||
public void onConnect(GEarthExtension extension) {
|
||||
@ -302,6 +300,7 @@ public class Extensions extends SubForm {
|
||||
extension.onRemoveClick(observable -> {
|
||||
try {
|
||||
extension.getConnection().close();
|
||||
extension.disconnect();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -324,9 +323,6 @@ public class Extensions extends SubForm {
|
||||
Platform.runLater(extension::delete);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
producer.setPort(extensionsRegistrer.getPort());
|
||||
ext_port.setText(extensionsRegistrer.getPort()+"");
|
||||
|
@ -3,11 +3,10 @@ package gearth.ui.extensions;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import gearth.protocol.HPacket;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import gearth.ui.extensions.authentication.Authenticator;
|
||||
import java.net.Socket;
|
||||
import java.io.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -29,57 +28,12 @@ public class GEarthExtension {
|
||||
private String fileName;
|
||||
private String cookie;
|
||||
|
||||
private Socket connection;
|
||||
private OnDisconnectedCallback disconnectedCallback;
|
||||
|
||||
//calls callback when the extension is creatd
|
||||
static void create(Socket connection, OnCreatedCallback callback, OnDisconnectedCallback onDisconnectedCallback) {
|
||||
private SocketChannel connection;
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
synchronized (connection) {
|
||||
connection.getOutputStream().write((new HPacket(Extensions.OUTGOING_MESSAGES_IDS.INFOREQUEST)).toBytes());
|
||||
}
|
||||
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
DataInputStream dIn = new DataInputStream(inputStream);
|
||||
|
||||
while (!connection.isClosed()) {
|
||||
|
||||
int length = dIn.readInt();
|
||||
byte[] headerandbody = new byte[length + 4];
|
||||
|
||||
int amountRead = 0;
|
||||
while (amountRead < length) {
|
||||
amountRead += dIn.read(headerandbody, 4 + amountRead, Math.min(dIn.available(), length - amountRead));
|
||||
}
|
||||
|
||||
HPacket packet = new HPacket(headerandbody);
|
||||
packet.fixLength();
|
||||
|
||||
if (packet.headerId() == Extensions.INCOMING_MESSAGES_IDS.EXTENSIONINFO) {
|
||||
GEarthExtension gEarthExtension = new GEarthExtension(
|
||||
packet,
|
||||
connection,
|
||||
onDisconnectedCallback
|
||||
);
|
||||
|
||||
if (Authenticator.evaluate(gEarthExtension)) {
|
||||
callback.act(gEarthExtension);
|
||||
}
|
||||
else {
|
||||
gEarthExtension.closeConnection(); //you shall not pass...
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException ignored) {}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
private GEarthExtension(HPacket extensionInfo, Socket connection, OnDisconnectedCallback onDisconnectedCallback) {
|
||||
//calls callback when the extension is created
|
||||
GEarthExtension(HPacket extensionInfo, SocketChannel connection, OnDisconnectedCallback onDisconnectedCallback) {
|
||||
this.title = extensionInfo.readString();
|
||||
this.author = extensionInfo.readString();
|
||||
this.version = extensionInfo.readString();
|
||||
@ -94,52 +48,23 @@ public class GEarthExtension {
|
||||
this.deleteButtonVisible = extensionInfo.readBoolean();
|
||||
|
||||
this.connection = connection;
|
||||
|
||||
GEarthExtension selff = this;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
DataInputStream dIn = new DataInputStream(inputStream);
|
||||
|
||||
while (!connection.isClosed()) {
|
||||
int length = dIn.readInt();
|
||||
byte[] headerandbody = new byte[length + 4];
|
||||
|
||||
int amountRead = 0;
|
||||
while (amountRead < length) {
|
||||
amountRead += dIn.read(headerandbody, 4 + amountRead, Math.min(dIn.available(), length - amountRead));
|
||||
this.disconnectedCallback = onDisconnectedCallback;
|
||||
}
|
||||
|
||||
HPacket packet = new HPacket(headerandbody);
|
||||
packet.fixLength();
|
||||
|
||||
public void act(HPacket packet) {
|
||||
synchronized (receiveMessageListeners) {
|
||||
for (int i = receiveMessageListeners.size() - 1; i >= 0; i--) {
|
||||
receiveMessageListeners.get(i).act(packet);
|
||||
packet.setReadIndex(6);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// An extension disconnected, which is OK
|
||||
} finally {
|
||||
onDisconnectedCallback.act(selff);
|
||||
if (!connection.isClosed()) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
|
||||
void disconnect() {
|
||||
disconnectedCallback.act(this);
|
||||
}
|
||||
|
||||
public Socket getConnection() {
|
||||
public SocketChannel getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@ -187,7 +112,7 @@ public class GEarthExtension {
|
||||
public boolean sendMessage(HPacket message) {
|
||||
try {
|
||||
synchronized (this) {
|
||||
connection.getOutputStream().write(message.toBytes());
|
||||
connection.write(ByteBuffer.wrap(message.toBytes()));
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
|
@ -1,19 +1,28 @@
|
||||
package gearth.ui.extensions;
|
||||
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.ui.extensions.authentication.Authenticator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 21/06/18.
|
||||
*/
|
||||
public class GEarthExtensionsRegistrer {
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
private ServerSocketChannel serverSocket;
|
||||
private Selector selector;
|
||||
|
||||
GEarthExtensionsRegistrer(ExtensionRegisterObserver observer) throws IOException {
|
||||
GEarthExtensionsRegistrer(ExtensionRegisterObserver observer) {
|
||||
|
||||
// serverSocket = new ServerSocket(0);
|
||||
int port = 9092;
|
||||
boolean serverSetup = false;
|
||||
while (!serverSetup) {
|
||||
@ -21,20 +30,86 @@ public class GEarthExtensionsRegistrer {
|
||||
port++;
|
||||
}
|
||||
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
while (!serverSocket.isClosed()) {
|
||||
Socket extensionSocket = serverSocket.accept();
|
||||
GEarthExtension.create(extensionSocket, observer::onConnect, observer::onDisconnect);
|
||||
ByteBuffer lenBuf = ByteBuffer.allocateDirect(4);
|
||||
while (selector.isOpen()) {
|
||||
selector.select();
|
||||
|
||||
Iterator keys = selector.selectedKeys().iterator();
|
||||
while (keys.hasNext()) {
|
||||
SelectionKey key = (SelectionKey) keys.next();
|
||||
keys.remove();
|
||||
|
||||
if (!key.isValid()) continue;
|
||||
|
||||
if (key.isAcceptable())
|
||||
accept(key);
|
||||
else if (key.isReadable()) {
|
||||
SocketChannel channel = (SocketChannel) key.channel();
|
||||
if (!channel.isOpen())
|
||||
continue;
|
||||
|
||||
if(channel.read(lenBuf) == -1) {
|
||||
channel.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
ByteBuffer headerAndBody = ByteBuffer.allocateDirect(4 + lenBuf.getInt(0));
|
||||
lenBuf.flip();
|
||||
headerAndBody.putInt(0);// this will be the length
|
||||
|
||||
channel.read(headerAndBody);
|
||||
|
||||
HPacket packet = new HPacket(headerAndBody);
|
||||
packet.fixLength();
|
||||
|
||||
if (packet.headerId() == Extensions.INCOMING_MESSAGES_IDS.EXTENSIONINFO) {
|
||||
GEarthExtension gEarthExtension = new GEarthExtension(
|
||||
packet,
|
||||
channel,
|
||||
observer::onDisconnect
|
||||
);
|
||||
key.attach(gEarthExtension);
|
||||
|
||||
if (Authenticator.evaluate(gEarthExtension)) {
|
||||
observer.onConnect(gEarthExtension);
|
||||
} else {
|
||||
gEarthExtension.closeConnection(); //you shall not pass...
|
||||
}
|
||||
} else if (key.attachment() != null)
|
||||
((GEarthExtension)key.attachment()).act(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {e.printStackTrace();}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void accept(SelectionKey key) {
|
||||
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
|
||||
try {
|
||||
SocketChannel channel = serverChannel.accept();
|
||||
channel.configureBlocking(false);
|
||||
channel.register(selector, SelectionKey.OP_READ);
|
||||
|
||||
|
||||
ByteBuffer arr = ByteBuffer.wrap((new HPacket(Extensions.OUTGOING_MESSAGES_IDS.INFOREQUEST)).toBytes());
|
||||
channel.write(arr);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean createServer(int port) {
|
||||
try {
|
||||
serverSocket = new ServerSocket(port);
|
||||
serverSocket = ServerSocketChannel.open();
|
||||
serverSocket.bind(new InetSocketAddress(port));
|
||||
selector = Selector.open();
|
||||
serverSocket.configureBlocking(false);
|
||||
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
@ -42,7 +117,12 @@ public class GEarthExtensionsRegistrer {
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return serverSocket.getLocalPort();
|
||||
try {
|
||||
return ((InetSocketAddress) serverSocket.getLocalAddress()).getPort();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ExtensionRegisterObserver {
|
||||
|
Loading…
Reference in New Issue
Block a user