Added MSI (master slave instancer) to create child D2GS processes

Added MSI (master slave instancer) to create child D2GS processes
This is intended to act as a temp structure for testing until support for actual servers are added where an API can generate proper instances
This commit is contained in:
Collin Smith 2019-11-30 20:02:36 -08:00
parent 115790f51a
commit 9f5d134eee
11 changed files with 356 additions and 6 deletions

View File

@ -2,6 +2,7 @@
<configuration default="false" name="desktop (networking)" type="CompoundRunConfigurationType" factoryName="Compound Run Configuration">
<toRun type="Application" name="BNLS" />
<toRun type="Application" name="MCP" />
<toRun type="Application" name="MSI" />
<toRun type="Application" name="desktop (debug) (854x480)" />
<method />
</configuration>

View File

@ -0,0 +1,39 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.msi;
import java.nio.*;
import java.lang.*;
import java.util.*;
import com.google.flatbuffers.*;
@SuppressWarnings("unused")
public final class MSI extends Table {
public static MSI getRootAsMSI(ByteBuffer _bb) { return getRootAsMSI(_bb, new MSI()); }
public static MSI getRootAsMSI(ByteBuffer _bb, MSI obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
public MSI __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
public byte dataType() { int o = __offset(4); return o != 0 ? bb.get(o + bb_pos) : 0; }
public Table data(Table obj) { int o = __offset(6); return o != 0 ? __union(obj, o) : null; }
public static int createMSI(FlatBufferBuilder builder,
byte data_type,
int dataOffset) {
builder.startObject(2);
MSI.addData(builder, dataOffset);
MSI.addDataType(builder, data_type);
return MSI.endMSI(builder);
}
public static void startMSI(FlatBufferBuilder builder) { builder.startObject(2); }
public static void addDataType(FlatBufferBuilder builder, byte dataType) { builder.addByte(0, dataType, 0); }
public static void addData(FlatBufferBuilder builder, int dataOffset) { builder.addOffset(1, dataOffset, 0); }
public static int endMSI(FlatBufferBuilder builder) {
int o = builder.endObject();
return o;
}
public static void finishMSIBuffer(FlatBufferBuilder builder, int offset) { builder.finish(offset); }
public static void finishSizePrefixedMSIBuffer(FlatBufferBuilder builder, int offset) { builder.finishSizePrefixed(offset); }
}

View File

@ -0,0 +1,14 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.msi;
public final class MSIData {
private MSIData() { }
public static final byte NONE = 0;
public static final byte StartInstance = 1;
public static final String[] names = { "NONE", "StartInstance", };
public static String name(int e) { return names[e]; }
}

View File

@ -0,0 +1,14 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.msi;
public final class Result {
private Result() { }
public static final byte SUCCESS = 0;
public static final byte FAILURE = 1;
public static final String[] names = { "SUCCESS", "FAILURE", };
public static String name(int e) { return names[e]; }
}

View File

@ -0,0 +1,41 @@
// automatically generated by the FlatBuffers compiler, do not modify
package com.riiablo.net.packet.msi;
import java.nio.*;
import java.lang.*;
import java.util.*;
import com.google.flatbuffers.*;
@SuppressWarnings("unused")
public final class StartInstance extends Table {
public static StartInstance getRootAsStartInstance(ByteBuffer _bb) { return getRootAsStartInstance(_bb, new StartInstance()); }
public static StartInstance getRootAsStartInstance(ByteBuffer _bb, StartInstance obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
public void __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; }
public StartInstance __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }
public byte result() { int o = __offset(4); return o != 0 ? bb.get(o + bb_pos) : 0; }
public int ip() { int o = __offset(6); return o != 0 ? bb.getInt(o + bb_pos) : 0; }
public short port() { int o = __offset(8); return o != 0 ? bb.getShort(o + bb_pos) : 0; }
public static int createStartInstance(FlatBufferBuilder builder,
byte result,
int ip,
short port) {
builder.startObject(3);
StartInstance.addIp(builder, ip);
StartInstance.addPort(builder, port);
StartInstance.addResult(builder, result);
return StartInstance.endStartInstance(builder);
}
public static void startStartInstance(FlatBufferBuilder builder) { builder.startObject(3); }
public static void addResult(FlatBufferBuilder builder, byte result) { builder.addByte(0, result, 0); }
public static void addIp(FlatBufferBuilder builder, int ip) { builder.addInt(1, ip, 0); }
public static void addPort(FlatBufferBuilder builder, short port) { builder.addShort(2, port, 0); }
public static int endStartInstance(FlatBufferBuilder builder) {
int o = builder.endObject();
return o;
}
}

View File

@ -1,6 +1,7 @@
package com.riiablo.net;
import com.riiablo.net.packet.mcp.CreateGame;
import com.riiablo.net.packet.msi.StartInstance;
public class GameSession {
public String name;
@ -28,6 +29,12 @@ public class GameSession {
desc = builder.desc;
}
public GameSession setConnectInfo(StartInstance info) {
ip = info.ip();
port = info.port();
return this;
}
@Override
public String toString() {
return name;

View File

@ -0,0 +1,13 @@
include "StartInstance.fbs";
namespace com.riiablo.net.packet.msi;
union MSIData {
StartInstance,
}
table MSI {
data:MSIData;
}
root_type MSI;

View File

@ -0,0 +1,15 @@
namespace com.riiablo.net.packet.msi;
enum Result : byte {
SUCCESS = 0,
FAILURE = 1,
}
table StartInstance {
// request
// response
result:Result;
ip:int32;
port:int16;
}

View File

@ -23,6 +23,7 @@ task dist(type: Jar) {
manifest {
attributes 'Server-Class': project.mainClassName
attributes 'Main-Class': project.mainClassName
}
}

View File

@ -21,9 +21,13 @@ import com.riiablo.net.packet.mcp.JoinGame;
import com.riiablo.net.packet.mcp.ListGames;
import com.riiablo.net.packet.mcp.MCPData;
import com.riiablo.net.packet.mcp.Result;
import com.riiablo.net.packet.msi.MSI;
import com.riiablo.net.packet.msi.MSIData;
import com.riiablo.net.packet.msi.StartInstance;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
@ -245,20 +249,38 @@ public class MCP extends ApplicationAdapter {
}
private boolean CreateGame(Socket socket, com.riiablo.net.packet.mcp.MCP packet) throws IOException {
CreateGame createGame = (CreateGame) packet.data(new CreateGame());
String gameName = createGame.gameName();
final CreateGame createGame = (CreateGame) packet.data(new CreateGame());
final String gameName = createGame.gameName();
Gdx.app.debug(TAG, "Attempting to create " + gameName + " for " + socket.getRemoteAddress());
FlatBufferBuilder builder = new FlatBufferBuilder();
final FlatBufferBuilder builder = new FlatBufferBuilder();
CreateGame.startCreateGame(builder);
if (sessions.containsKey(gameName)) {
CreateGame.addResult(builder, Result.ALREADY_EXISTS);
} else if (sessions.size() >= 4) {
CreateGame.addResult(builder, Result.SERVER_DOWN);
} else {
CreateGame.addResult(builder, Result.SUCCESS);
sessions.put(gameName, new GameSession(createGame));
Gdx.app.debug(TAG, "Created session " + gameName);
final GameSession session = new GameSession(createGame);
StartInstance(createGame, new ResponseListener() {
@Override
public void handleResponse(MSI msi) {
StartInstance startInstance = (StartInstance) msi.data(new StartInstance());
switch (startInstance.result()) {
case Result.SUCCESS:
sessions.put(gameName, session.setConnectInfo(startInstance));
CreateGame.addResult(builder, Result.SUCCESS);
Gdx.app.debug(TAG, "Created session " + gameName);
break;
default:
CreateGame.addResult(builder, Result.SERVER_DOWN);
}
}
@Override
public void failed(Throwable t) {
Gdx.app.error(TAG, t.getMessage(), t);
}
});
}
int createGameOffset = CreateGame.endCreateGame(builder);
@ -304,6 +326,46 @@ public class MCP extends ApplicationAdapter {
return false;
}
interface ResponseListener {
void handleResponse(MSI msi);
void failed(Throwable t);
}
private void StartInstance(CreateGame createGame, ResponseListener listener) {
Gdx.app.debug(TAG, "Requesting game instance for " + createGame);
FlatBufferBuilder builder = new FlatBufferBuilder();
StartInstance.startStartInstance(builder);
int startInstanceOffset = StartInstance.endStartInstance(builder);
int id = MSI.createMSI(builder, MSIData.StartInstance, startInstanceOffset);
builder.finish(id);
ByteBuffer data = builder.dataBuffer();
Socket socket = null;
try {
socket = Gdx.net.newClientSocket(Net.Protocol.TCP, "localhost", com.riiablo.server.mcp.MSI.PORT, null);
OutputStream out = socket.getOutputStream();
WritableByteChannel channelOut = Channels.newChannel(out);
channelOut.write(data);
buffer.clear();
buffer.mark();
InputStream in = socket.getInputStream();
ReadableByteChannel channelIn = Channels.newChannel(in);
channelIn.read(buffer);
buffer.limit(buffer.position());
buffer.reset();
MSI packet = MSI.getRootAsMSI(buffer);
Gdx.app.log(TAG, "packet type " + MCPData.name(packet.dataType()));
listener.handleResponse(packet);
} catch (Throwable t) {
listener.failed(t);
} finally {
if (socket != null) socket.dispose();
}
}
static String generateClientName() {
return String.format("Client-%08X", MathUtils.random(1, Integer.MAX_VALUE - 1));
}

View File

@ -0,0 +1,143 @@
package com.riiablo.server.mcp;
import com.google.flatbuffers.FlatBufferBuilder;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.backends.headless.HeadlessApplication;
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
import com.badlogic.gdx.net.ServerSocket;
import com.badlogic.gdx.net.Socket;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BufferUtils;
import com.riiablo.net.packet.msi.MSIData;
import com.riiablo.net.packet.msi.Result;
import com.riiablo.net.packet.msi.StartInstance;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.text.DateFormat;
import java.util.Calendar;
public class MSI extends ApplicationAdapter {
private static final String TAG = "MSI";
static final int PORT = 6112;
public static void main(String[] args) {
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
new HeadlessApplication(new MSI(), config);
}
ServerSocket server;
ByteBuffer buffer;
final Array<Process> instances = new Array<>();
MSI() {}
@Override
public void create() {
Gdx.app.setLogLevel(Application.LOG_DEBUG);
final Calendar calendar = Calendar.getInstance();
DateFormat format = DateFormat.getDateTimeInstance();
Gdx.app.log(TAG, format.format(calendar.getTime()));
try {
InetAddress address = InetAddress.getLocalHost();
Gdx.app.log(TAG, "IP Address: " + address.getHostAddress() + ":" + PORT);
Gdx.app.log(TAG, "Host Name: " + address.getHostName());
} catch (UnknownHostException e) {
Gdx.app.error(TAG, e.getMessage(), e);
}
Gdx.app.log(TAG, "Starting server...");
server = Gdx.net.newServerSocket(Net.Protocol.TCP, PORT, null);
buffer = BufferUtils.newByteBuffer(4096);
}
@Override
public void render() {
Socket socket = null;
try {
Gdx.app.log(TAG, "waiting...");
socket = server.accept(null);
Gdx.app.log(TAG, "connection from " + socket.getRemoteAddress());
buffer.clear();
buffer.mark();
ReadableByteChannel in = Channels.newChannel(socket.getInputStream());
in.read(buffer);
buffer.limit(buffer.position());
buffer.reset();
com.riiablo.net.packet.msi.MSI packet = com.riiablo.net.packet.msi.MSI.getRootAsMSI(buffer);
Gdx.app.log(TAG, "packet type " + MSIData.name(packet.dataType()));
process(socket, packet);
} catch (Throwable t) {
if (socket != null) socket.dispose();
}
}
private void process(Socket socket, com.riiablo.net.packet.msi.MSI packet) throws IOException {
switch (packet.dataType()) {
case MSIData.StartInstance:
StartInstance(socket, packet);
break;
default:
Gdx.app.error(TAG, "Unknown packet type: " + packet.dataType());
}
}
private boolean StartInstance(Socket socket, com.riiablo.net.packet.msi.MSI packet) throws IOException {
Gdx.app.debug(TAG, "Starting instance...");
try {
File outFile = new File("D2GS.tmp");
ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", "server/d2gs/build/libs/d2gs-1.0.jar");
processBuilder.redirectOutput(ProcessBuilder.Redirect.to(outFile));
processBuilder.redirectError(ProcessBuilder.Redirect.to(outFile));
Process process = processBuilder.start();
instances.add(process);
} catch (Throwable t) {
Gdx.app.error(TAG, t.getMessage(), t);
}
int ip = 2130706433; // 127.0.0.1
short port = 6114;
FlatBufferBuilder builder = new FlatBufferBuilder();
StartInstance.startStartInstance(builder);
StartInstance.addResult(builder, Result.SUCCESS);
StartInstance.addIp(builder, ip);
StartInstance.addPort(builder, port);
int startInstanceOffset = StartInstance.endStartInstance(builder);
int id = com.riiablo.net.packet.msi.MSI.createMSI(builder, MSIData.StartInstance, startInstanceOffset);
builder.finish(id);
ByteBuffer data = builder.dataBuffer();
OutputStream out = socket.getOutputStream();
WritableByteChannel channel = Channels.newChannel(out);
channel.write(data);
Gdx.app.debug(TAG, "Returning instance at " + InetAddress.getByAddress(ByteBuffer.allocate(4).putInt(ip).array()));
return true;
}
@Override
public void dispose() {
Gdx.app.log(TAG, "Shutting down...");
server.dispose();
for (Process process : instances) {
process.destroy();
}
}
}