Full refactor of network API + deprecation of old API

This commit is contained in:
Marc Baloup 2020-02-08 15:43:19 +01:00
parent c510c0197e
commit 0c3e1425fd
44 changed files with 684 additions and 1595 deletions

View File

@ -1,8 +1,8 @@
package fr.pandacube.util.network.packet.bytebuffer;
package fr.pandacube.util.net;
import java.util.Arrays;
public class Array8Bit implements ByteSerializable {
public class Array8Bit {
public static final int BIT_COUNT = Byte.SIZE;
@ -63,15 +63,4 @@ public class Array8Bit implements ByteSerializable {
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
fromByte(buffer.getByte());
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putByte(toByte());
}
}

View File

@ -1,27 +1,32 @@
package fr.pandacube.util.network.packet.bytebuffer;
package fr.pandacube.util.net;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ByteBuffer implements Cloneable {
import fr.pandacube.Pandacube;
public final class ByteBuffer implements Cloneable {
private java.nio.ByteBuffer buff;
private Charset charset;
public ByteBuffer(Charset c) {
this(16, c);
public ByteBuffer() {
this(16);
}
public ByteBuffer(int initSize, Charset c) {
public ByteBuffer(int initSize) {
buff = java.nio.ByteBuffer.allocate(initSize);
charset = c;
}
public ByteBuffer(byte[] data, Charset c) {
buff = java.nio.ByteBuffer.wrap(Arrays.copyOf(data, data.length));
charset = c;
/**
* Create a ByteBuffer that is initially <b>backed</b> by the provided byte array.
* The position of this buffer will be 0.
* If this ByteBuffer needs a biffer array, the provided array is replaced by a new one,
* making the provided array not related to this ByteBuffer anymore.
* @param data array of byte that serve as a backend for this ByteBuffer.
*/
public ByteBuffer(byte[] data) {
buff = java.nio.ByteBuffer.wrap(data);
}
private void askForBufferExtension(int needed) {
@ -32,9 +37,12 @@ public class ByteBuffer implements Cloneable {
}
}
/**
* This clone method also clone the underlying array.
*/
@Override
public ByteBuffer clone() {
return new ByteBuffer(Arrays.copyOf(buff.array(), buff.array().length), charset);
return new ByteBuffer(Arrays.copyOf(buff.array(), buff.array().length));
}
/**
@ -215,7 +223,7 @@ public class ByteBuffer implements Cloneable {
if (s == null) {
return putInt(-1);
}
return putSizedByteArray(s.getBytes(charset));
return putSizedByteArray(s.getBytes(Pandacube.NETWORK_CHARSET));
}
/**
@ -224,51 +232,7 @@ public class ByteBuffer implements Cloneable {
*/
public String getString() {
byte[] binaryString = getSizedByteArray();
return (binaryString == null) ? null : new String(binaryString, charset);
}
/**
* The objet will be serialized and the data put in the current buffer
*
* @param obj the object to serialize. Can't be null.
* @return the current buffer
*/
public ByteBuffer putObject(ByteSerializable obj) {
obj.serializeToByteBuffer(this);
return this;
}
/**
* Ask to object passed as argument to deserialize data in buffer and fill
* the object content. ByteSerializable object are never null.
*
* @param <T>
* @param clazz the class wich will be instanciated with his no-argument Constructor
* before filled by using {@link ByteSerializable#deserializeFromByteBuffer(ByteBuffer)}
* @return obj a reference to the filled object
*/
public <T extends ByteSerializable> T getObject(Class<T> clazz) {
try {
T obj = clazz.newInstance();
obj.deserializeFromByteBuffer(this);
return obj;
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("A ByteSerializable must have a no-argument Constructor", e);
}
}
/**
*
* @param list The list itself can be null, but not the values.
* @return
*/
public ByteBuffer putListObject(List<ByteSerializable> list) {
if (list.stream().anyMatch(e -> e == null))
throw new IllegalArgumentException("List of object can't contains any null value");
putInt(list.size());
for (ByteSerializable obj : list)
putObject(obj);
return this;
return (binaryString == null) ? null : new String(binaryString, Pandacube.NETWORK_CHARSET);
}
/**
@ -286,25 +250,10 @@ public class ByteBuffer implements Cloneable {
return this;
}
/**
*
* @param clazz
* @return Can be null. If not, there is no null element inside.
*/
public <T extends ByteSerializable> List<T> getListObject(Class<T> clazz) {
int size = getInt();
if (size < 0)
return null;
List<T> list = new ArrayList<>();
for (int i = 0; i < size; i++)
list.add(getObject(clazz));
return list;
}
/**
* @return a List of String. The list can be null, and any element can be null too.
*/
public <T extends ByteSerializable> List<String> getListOfString() {
public List<String> getListOfString() {
int size = getInt();
if (size < 0)
return null;

View File

@ -0,0 +1,57 @@
package fr.pandacube.util.net;
import java.util.Arrays;
public class PPacket {
public String name;
/* package */ int id;
public byte[] content;
/**
* Construct a new PPacket based on the content of the provided buffer before his position.
* @param n the name of the packet.
* @param buff the buffer where the data comes from. Only the content before {@link ByteBuffer#getPosition()} is copied.
*/
public PPacket(String n, ByteBuffer buff) {
this(n, Arrays.copyOf(buff.array(), buff.getPosition()));
}
public PPacket(String n, byte[] c) {
name = n;
content = c;
}
/* package */ PPacket(String n, int i, byte[] c) {
this(n, c);
id = i;
}
public ByteBuffer getContentAsBuffer() {
return new ByteBuffer(content);
}
public static PPacket buildSingleStringContentPacket(String name, String content) {
return new PPacket(name, new ByteBuffer().putString(content));
}
/* package */ static PPacket buildLoginPacket(String password) {
return buildSingleStringContentPacket("login", password);
}
/* package */ static PPacket buildBadFormatPacket(String message) {
return buildSingleStringContentPacket("bad_format", message);
}
/* package */ static PPacket buildLoginBadPacket() {
return new PPacket("login_bad", new byte[0]);
}
}

View File

@ -0,0 +1,44 @@
package fr.pandacube.util.net;
import java.util.Arrays;
public class PPacketAnswer extends PPacket {
/* package */ int answer;
/**
* Construct a new PPacketAnswer based on the content of the provided buffer before his position.
* @param n the name of the packet.
* @param buff the buffer where the data comes from. Only the content before {@link ByteBuffer#getPosition()} is copied.
*/
public PPacketAnswer(PPacket answered, String n, ByteBuffer buff) {
this(answered, n, Arrays.copyOf(buff.array(), buff.getPosition()));
}
public PPacketAnswer(PPacket answered, String n, byte[] c) {
super(n, c);
answer = answered.id;
}
/* package */ PPacketAnswer(String n, int i, int a, byte[] c) {
super(n, i, c);
answer = a;
}
public static PPacketAnswer buildSingleStringContentPacketAnswer(PPacket answered, String name, String content) {
ByteBuffer pwBuff = new ByteBuffer().putString(content);
return new PPacketAnswer(answered, name, Arrays.copyOf(pwBuff.array(), pwBuff.getPosition()));
}
/* package */ static PPacketAnswer buildLoginOkPacket(PPacket loginPacket) {
return new PPacketAnswer(loginPacket, "login_ok", new byte[0]);
}
/* package */ static PPacketAnswer buildExceptionPacket(PPacket answered, String message) {
return buildSingleStringContentPacketAnswer(answered, "exception", message);
}
}

View File

@ -0,0 +1,13 @@
package fr.pandacube.util.net;
@FunctionalInterface
public interface PPacketListener<P extends PPacket> {
/**
* Called when we receive a packet (except responses)
* @param connection the connection from where the packet comes
* @param packet the received packet
*/
public void onPacketReceive(PSocket connection, P packet);
}

View File

@ -0,0 +1,165 @@
package fr.pandacube.util.net;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang.builder.ToStringBuilder;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
public class PServer extends Thread implements Closeable {
private static AtomicInteger connectionCounterId = new AtomicInteger(0);
private int port;
private ServerSocket socket;
private String socketName;
private List<TCPServerClientConnection> clients = Collections.synchronizedList(new ArrayList<>());
private AtomicBoolean isClosed = new AtomicBoolean(false);
private List<PPacketListener<PPacket>> globalPacketListeners = Collections.synchronizedList(new ArrayList<>());
private List<PSocketConnectionListener> clientConnectionListeners = Collections.synchronizedList(new ArrayList<>());
private String password;
public PServer(int port, String sckName, String password) throws IOException {
super("PServer " + sckName);
setDaemon(true);
if (port <= 0 || port > 65535) throw new IllegalArgumentException("le numéro de port est invalide");
socketName = sckName;
this.port = port;
this.password = password;
}
@Override
public void run() {
try {
socket = new ServerSocket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setPerformancePreferences(0, 1, 0);
socket.bind(new InetSocketAddress(port));
while (true) {
Socket socketClient = socket.accept();
socketClient.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socketClient.setSoTimeout(Pandacube.NETWORK_TIMEOUT);
try {
@SuppressWarnings("resource")
TCPServerClientConnection co = new TCPServerClientConnection(socketClient,
connectionCounterId.getAndIncrement());
co.start();
} catch (IOException e) {
Log.severe("Connexion impossible avec " + socketClient.getInetAddress());
}
}
} catch(SocketException e) {
} catch (Exception e) {
Log.warning("Plus aucune connexion ne peux être acceptée", e);
}
}
public void addPacketListener(PPacketListener<PPacket> l) {
globalPacketListeners.add(l);
}
public boolean removePacketListener(PPacketListener<PPacket> l) {
return globalPacketListeners.remove(l);
}
public void addConnectionListener(PSocketConnectionListener l) {
clientConnectionListeners.add(l);
}
public void removeConnectionListener(PSocketConnectionListener l) {
clientConnectionListeners.remove(l);
}
protected class TCPServerClientConnection extends PSocket {
boolean loggedIn;
private TCPServerClientConnection(Socket s, int coId) throws IOException {
super(s, "Conn#" + coId + " via TCPSv " + socketName, password);
addConnectionListener(new PSocketConnectionListener() {
@Override
public void onDisconnect(PSocket connection) {
try {
clientConnectionListeners.forEach(l -> l.onDisconnect(connection));
} finally {
clients.remove((TCPServerClientConnection)connection);
}
}
@Override
public void onConnect(PSocket connection) {
clients.add((TCPServerClientConnection)connection);
clientConnectionListeners.forEach(l -> l.onConnect(connection));
}
});
addPacketListener((conn, packet) -> {
globalPacketListeners.forEach(l -> {
try {
l.onPacketReceive(conn, packet);
} catch (Exception e) {
Log.severe("Exception while calling PPacketListener.onPacketReceive().", e);
sendSilently(PPacketAnswer.buildExceptionPacket(packet, e.toString()));
}
});
});
}
}
@Override
public void close() {
try {
if (isClosed.get()) return;
isClosed.set(true);
clients.forEach(PSocket::close);
socket.close();
} catch (IOException e) {}
}
public boolean isClosed() {
return isClosed.get() || socket.isClosed();
}
public List<PSocket> getClients() {
synchronized (clients) {
return new ArrayList<>(clients);
}
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("thread", getName())
.append("socket", socket).toString();
}
}

View File

@ -0,0 +1,351 @@
package fr.pandacube.util.net;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang.builder.ToStringBuilder;
import com.google.common.base.Objects;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
/**
* A wrapper for a {@link Socket}. The connection must point to a software using {@link PServer}
* as wrapper for the target {@link ServerSocket}.
* <br>
* This class provides a simple way to exchange data between client and server :
* <li>Maintained connection with the server</li>
* <li>Login with a password (send in the first packet)</li>
* <li>Named packets</li>
* <li>Binary data</li>
* <li>Input stream in a separate Thread</li>
*
*/
public class PSocket extends Thread implements Closeable {
private boolean server = false;
private Socket socket;
private SocketAddress addr;
private DataInputStream in;
private DataOutputStream out;
private Object outSynchronizer = new Object();
private String password;
private AtomicBoolean isClosed = new AtomicBoolean(false);
private List<PPacketListener<PPacket>> packetListeners = Collections.synchronizedList(new ArrayList<>());
private List<PSocketConnectionListener> connectionListeners = Collections.synchronizedList(new ArrayList<>());
private Map<Integer, PPacketListener<PPacketAnswer>> answersCallbacks = Collections.synchronizedMap(new HashMap<>());
private int nextSendId = 0;
/**
* Create a new PSocket that will connect to the specified SocketAddress.
* @param a The target server to connect to
* @param connName the name of the connection, used to name the Thread used to receive the packet.
* @param the password to send to the server.
*/
public PSocket(SocketAddress a, String connName, String pass) {
super("PSocket " + connName);
setDaemon(true);
if (a == null) throw new IllegalArgumentException("les arguments ne peuvent pas être null");
addr = a;
}
/* package */ PSocket(Socket s, String connName, String pass) {
this(s.getRemoteSocketAddress(), connName, pass);
socket = s;
server = true;
}
@Override
public void run() {
try {
if (socket == null) {
socket = new Socket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setSoTimeout(10000); // initial timeout before login
socket.connect(addr);
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
}
// password check
if (server) {
PPacket packet = readPacket();
if (packet == null || packet instanceof PPacketAnswer || !"login".equals(packet.name)) {
send(PPacket.buildLoginBadPacket());
close();
return;
}
try {
String receivedPassword = new ByteBuffer(packet.content).getString();
if (!Objects.equal(receivedPassword, password)) {
send(PPacket.buildLoginBadPacket());
close();
return;
}
} catch(Exception e) {
send(PPacket.buildLoginBadPacket());
close();
return;
}
send(PPacketAnswer.buildLoginOkPacket(packet));
// login ok at this point
password = null;
}
else {
send(PPacket.buildLoginPacket(password));
PPacket packet = readPacket();
if (packet == null) {
Log.severe("bad packet received from server. Disconnecting.");
close();
return;
}
if (packet.name.equals("login_bad")) {
Log.severe("Wrong password to connect to server. Disconnecting.");
close();
return;
}
if (!packet.name.equals("login_ok")) {
Log.severe("Unexpected packet from server. Disconnecting.");
close();
return;
}
// login ok at this point
password = null;
}
socket.setSoTimeout(Pandacube.NETWORK_TIMEOUT);
Log.info(getName() + " connected.");
connectionListeners.forEach(l -> {
try {
l.onConnect(this);
} catch (Exception e) {
Log.severe("Exception while calling PSocketConnectionListener.onConnect().", e);
}
});
while (!socket.isClosed()) {
PPacket packet = readPacket();
if (packet == null) {
send(PPacket.buildBadFormatPacket("Bad format for the last packet received. Closing connection."));
break;
}
if (packet instanceof PPacketAnswer) {
try {
answersCallbacks.remove(((PPacketAnswer)packet).answer).onPacketReceive(this, (PPacketAnswer)packet);
} catch (Exception e) {
Log.severe("Exception while calling PPacketListener.onPacketReceive().", e);
send(PPacketAnswer.buildExceptionPacket(packet, e.toString()));
}
}
else {
packetListeners.forEach(l -> {
try {
l.onPacketReceive(this, packet);
} catch (Exception e) {
Log.severe("Exception while calling PPacketListener.onPacketReceive().", e);
sendSilently(PPacketAnswer.buildExceptionPacket(packet, e.toString()));
}
});
}
}
} catch (Exception e) {
Log.severe(e);
}
close();
}
/**
* Return the packet read in the socket, or null if the packet is in a bad format.
* @return the packet
* @throws IOException
*
*/
private PPacket readPacket() throws IOException {
byte nSize = in.readByte();
if (nSize == 0) {
return null;
}
boolean answer = nSize < 0;
if (answer)
nSize *= -1;
byte[] nBytes = new byte[nSize];
in.readFully(nBytes);
String name = new String(nBytes, Pandacube.NETWORK_CHARSET);
int packetId = in.readInt();
int answerId = (answer) ? in.readInt() : -1;
int cSize = in.readInt();
if (cSize < 0 || cSize > 0xFFFFFF) { // can't be more that 16 MiB
return null;
}
byte[] content = new byte[cSize];
in.readFully(content);
return answer ? new PPacketAnswer(name, packetId, answerId, content) : new PPacket(name, packetId, content);
}
/**
* Send the provided packet, without waiting for an answer.
* @param packet
* @throws IOException
*/
public void send(PPacket packet) throws IOException {
if (packet == null)
throw new IllegalArgumentException("packet can't be null");
if (packet.name == null)
throw new IllegalArgumentException("packet.name can't be null");
if (packet.content == null)
throw new IllegalArgumentException("packet.content can't be null");
byte[] nameBytes = packet.name.getBytes(Pandacube.NETWORK_CHARSET);
if (nameBytes.length > 127)
throw new IllegalArgumentException("packet.name must take fewer than 128 bytes when converted to UTF-8");
byte nameSize = (byte)nameBytes.length;
boolean answer = packet instanceof PPacketAnswer;
if (answer) nameSize *= -1;
synchronized (outSynchronizer) {
int packetId = nextSendId++;
packet.id = packetId;
out.write(new byte[] {nameSize});
out.write(nameBytes);
out.write(packetId);
if (answer)
out.write(((PPacketAnswer)packet).answer);
out.write(packet.content.length);
out.write(packet.content);
out.flush();
}
}
public void sendSilently(PPacket packet) {
try {
send(packet);
} catch (IOException e) {}
}
public void send(PPacket packet, PPacketListener<PPacketAnswer> answerCallback) throws IOException {
synchronized (answersCallbacks) {
/*
* This synch block ensure that the callback will be put in the listeners Map before
* we receve the answer (in case this is really really fast)
*/
send(packet);
answersCallbacks.put(packet.id, answerCallback);
}
}
public void addPacketListener(PPacketListener<PPacket> l) {
packetListeners.add(l);
}
public boolean removePacketListener(PPacketListener<PPacket> l) {
return packetListeners.remove(l);
}
public void addConnectionListener(PSocketConnectionListener l) {
connectionListeners.add(l);
}
public void removeConnectionListener(PSocketConnectionListener l) {
connectionListeners.remove(l);
}
@Override
public void close() {
try {
synchronized (outSynchronizer) {
if (isClosed.get()) return;
Log.info(getName() + " closing...");
connectionListeners.forEach(l -> {
try {
l.onDisconnect(this);
} catch (Exception e) {
Log.severe("Exception while calling PSocketConnectionListener.onDisconnect().", e);
}
});
socket.close();
isClosed.set(true);
}
} catch (IOException e) {
Log.warning(e);
}
}
public SocketAddress getRemoteAddress() {
return addr;
}
public boolean isClosed() {
return isClosed.get() || socket.isClosed();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("thread", getName())
.append("socket", socket.getRemoteSocketAddress()).toString();
}
}

View File

@ -0,0 +1,17 @@
package fr.pandacube.util.net;
public interface PSocketConnectionListener {
/**
* Called when a socket is connected
* @param connection the connection
*/
public void onConnect(PSocket connection);
/**
* Called just before a socket is disconnected
* @param connection the connection
*/
public void onDisconnect(PSocket connection);
}

View File

@ -1,224 +0,0 @@
package fr.pandacube.util.network.client;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.javatuples.Pair;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.Packet;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.PacketException;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.ResponseCallback;
import fr.pandacube.util.network.packet.packets.global.PacketD0ServerException;
public class TCPClient extends Thread implements Closeable {
private Socket socket;
private SocketAddress addr;
private TCPClientListener listener;
private InputStream in;
private OutputStream out;
private Object outSynchronizer = new Object();
private List<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> callbacks = Collections.synchronizedList(new ArrayList<>());
private List<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> callbacksAvoidListener = Collections.synchronizedList(new ArrayList<>());
private AtomicBoolean isClosed = new AtomicBoolean(false);
public TCPClient(InetSocketAddress a, String connName, TCPClientListener l) throws IOException {
super("TCPCl " + connName);
setDaemon(true);
if (a == null || l == null) throw new IllegalArgumentException("les arguments ne peuvent pas être null");
socket = new Socket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setSoTimeout(Pandacube.NETWORK_TIMEOUT);
socket.connect(a);
addr = a;
listener = l;
try {
listener.onConnect(this);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onConnect()", e);
}
}
@Override
public void run() {
try {
byte[] code = new byte[1];
while (!socket.isClosed() && in.read(code) != -1) {
byte[] sizeB = new byte[4];
if (in.read(sizeB) != 4) throw new IOException("Socket " + addr + " fermé");
int size = ByteBuffer.wrap(sizeB).getInt();
byte[] content = new byte[size];
forceReadBytes(content);
byte[] packetData = ByteBuffer.allocate(1 + 4 + size).put(code).put(sizeB).put(content).array();
try {
Packet p = Packet.constructPacket(packetData);
if (!(p instanceof PacketServer))
throw new PacketException(p.getClass().getCanonicalName() + " is not a subclass of PacketServer");
if (p instanceof PacketD0ServerException) {
try {
listener.onServerException(this, ((PacketD0ServerException)p).getExceptionString());
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onServerException()", e);
}
}
PacketServer ps = (PacketServer) p;
boolean callbackExecuted = executeCallbacks(ps, callbacksAvoidListener);
try {
if (!callbackExecuted)
listener.onPacketReceive(this, ps);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onPacketReceive()", e);
}
executeCallbacks(ps, callbacks);
} catch (Exception e) {
Log.severe("Exception while handling packet from server", e);
}
}
} catch (Exception e) {
Log.severe(e);
}
close();
}
private boolean executeCallbacks(PacketServer ps, List<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> callbacks) {
boolean executedOne = false;
synchronized (callbacks) {
for(Iterator<Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>>> it = callbacks.iterator(); it.hasNext();) {
Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>> c = it.next();
try {
if (c.getValue0().test(ps)) {
it.remove();
c.getValue1().call(ps);
executedOne = true;
}
} catch (Exception e) {
Log.severe("Exception while executing callback", e);
}
}
}
return executedOne;
}
private void forceReadBytes(byte[] buff) throws IOException {
int pos = 0;
do {
int nbR = in.read(buff, pos, buff.length - pos);
if (nbR == -1) throw new IOException("Can't read required amount of byte");
pos += nbR;
} while (pos < buff.length);
}
public void send(PacketClient packet) throws IOException {
synchronized (outSynchronizer) {
out.write(packet.getFullSerializedPacket());
out.flush();
}
}
public void sendAndGetResponse(PacketClient packet, Predicate<PacketServer> responseCondition, ResponseCallback<PacketServer> callback, boolean avoidListener) throws IOException {
Pair<Predicate<PacketServer>, ResponseCallback<PacketServer>> p = new Pair<>(responseCondition, callback);
if (avoidListener)
callbacksAvoidListener.add(p);
else
callbacks.add(p);
send(packet);
}
public PacketServer sendAndWaitForResponse(PacketClient packet, Predicate<PacketServer> responseCondition) throws IOException, InterruptedException {
AtomicReference<PacketServer> psStorage = new AtomicReference<>(null);
synchronized (psStorage) {
sendAndGetResponse(packet, responseCondition, packetServer -> {
synchronized (psStorage) {
psStorage.set(packetServer);
psStorage.notifyAll();
}
}, true);
psStorage.wait();
return psStorage.get();
}
}
@Override
public void close() {
try {
synchronized (outSynchronizer) {
if (isClosed.get()) return;
socket.close();
isClosed.set(true);
try {
listener.onDisconnect(this);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onDisconnect()", e);
}
}
} catch (IOException e) {
Log.warning(e);
}
}
public void sendSilently(PacketClient packet) {
try {
send(packet);
} catch (IOException e) {}
}
public SocketAddress getServerAddress() {
return addr;
}
public boolean isClosed() {
return isClosed.get() || socket.isClosed();
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("thread", getName())
.append("socket", socket.getRemoteSocketAddress()).toString();
}
}

View File

@ -1,42 +0,0 @@
package fr.pandacube.util.network.client;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.PacketServer;
public interface TCPClientListener {
/**
* Called when a connection is opened
* @param connection the connection which is opening
*/
public void onConnect(TCPClient connection);
/**
* Called when the server send us a PacketServerException when an exception is thrown
* server side when handling our packets or eventually when the client is concerned
* with this error.<br/>
* The default implementation of this method just call the internal Logger of PandacubeUtil
* to print the stacktrace of the Exception.
* @param connection the connection which just received the error
* @param exceptionString a string representation of the exception. If the server
* use this Java library, it may be a full representation of
* {@link Throwable#printStackTrace()}.
*/
public default void onServerException(TCPClient connection, String exceptionString) {
Log.severe("Exception thrown by server through " + connection + " : \n"+exceptionString);
}
/**
* Called when the server send us a packet
* @param connection the connection where the packet come from
* @param packet the packet
*/
public void onPacketReceive(TCPClient connection, PacketServer packet);
/**
* Called before the connection closed
* @param connection the connection which is closing
*/
public void onDisconnect(TCPClient connection);
}

View File

@ -1,118 +0,0 @@
package fr.pandacube.util.network.packet;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.util.network.packet.bytebuffer.ByteSerializable;
import fr.pandacube.util.network.packet.packets.core_slave.Packet30ClientDeclareProcess;
import fr.pandacube.util.network.packet.packets.core_slave.Packet31ClientClose;
import fr.pandacube.util.network.packet.packets.core_slave.Packet32ClientProcessQueryResponse;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB0ServerClose;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB1ServerProcessDeclarationConfirm;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB2ServerConnectSuccess;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB3ServerProcessInput;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB4ServerProcessQuery;
import fr.pandacube.util.network.packet.packets.global.Packet50ClientAuthenticate;
import fr.pandacube.util.network.packet.packets.global.Packet51ClientLogRecord;
import fr.pandacube.util.network.packet.packets.global.PacketD0ServerException;
import fr.pandacube.util.network.packet.packets.global.PacketD1ServerCantAuthenticate;
import fr.pandacube.util.network.packet.packets.web.Packet00ClientWebRequest;
import fr.pandacube.util.network.packet.packets.web.Packet80ServerWebResponse;
/** <pre>
* Identification des packets réseaux
* byte (xxxxxxxx)
* client : server :
* clt / sv (x-------) (0-------) (1-------)
* 0x00 - 0x7F 0x80 - 0xFF
* use case (-xxx----)
* - web (-000----) 0x00 - 0x0F 0x80 - 0x8F (client is Apache, server is PandacubeCore master (PandacubeWeb))
* - spigot (-001----) 0x10 - 0x1F 0x90 - 0x9F (client is PandacubeSpigot, server is PandacubeCore master)
* - bungee (-010----) 0x20 - 0x2F 0xA0 - 0xAF (client is PandacubeBungee, server is PandacubeCore master)
* -coreslave(-011----) 0x30 - 0x3F 0xB0 - 0xBF (client is PandacubeCore slave, sv is PandacubeCore master)
* - global (-101----) 0x50 - 0x5F 0xD0 - 0xDF
*
* - reserved if not enough packet id in certain use case
* (-11x----) 0x60 - 0x7F 0xE0 - 0xFF
*
* packet id (----xxxx)
* </pre>
*/
public abstract class Packet implements ByteSerializable {
private final byte code;
public Packet(byte c) {
code = c;
}
public byte getCode() {
return code;
}
public byte[] getFullSerializedPacket() {
ByteBuffer internal = new ByteBuffer(CHARSET).putObject(this);
byte[] data = Arrays.copyOfRange(internal.array(), 0, internal.getPosition());
return new ByteBuffer(5 + data.length, CHARSET).putByte(code).putInt(data.length).putByteArray(data).array();
}
public static final Charset CHARSET = Pandacube.NETWORK_CHARSET;
private static Map<Byte, Class<? extends Packet>> packetTypes = new HashMap<>();
public static Packet constructPacket(byte[] data) {
if (!packetTypes.containsKey(data[0]))
throw new PacketException("Packet identifier not recognized: 0x" + String.format("%02X", data[0])
+ ". Maybe this packet is not registered with Packet.addPacket()");
try {
Packet p = packetTypes.get(data[0]).newInstance();
ByteBuffer dataBuffer = new ByteBuffer(Arrays.copyOfRange(data, 5, data.length), CHARSET);
p.deserializeFromByteBuffer(dataBuffer);
return p;
} catch (Exception e) {
throw new PacketException("Error while constructing packet", e);
}
}
private static <T extends Packet> void addPacket(Class<T> packetClass) {
try {
Packet p = packetClass.newInstance();
packetTypes.put(p.code, packetClass);
} catch (Exception e) {
Log.severe(e);
}
}
static {
/*
* Ajout des types de packets (client + serveur)
*/
addPacket(Packet31ClientClose.class);
addPacket(Packet30ClientDeclareProcess.class);
addPacket(Packet32ClientProcessQueryResponse.class);
addPacket(PacketB0ServerClose.class);
addPacket(PacketB2ServerConnectSuccess.class);
addPacket(PacketB1ServerProcessDeclarationConfirm.class);
addPacket(PacketB3ServerProcessInput.class);
addPacket(PacketB4ServerProcessQuery.class);
addPacket(Packet50ClientAuthenticate.class);
addPacket(Packet51ClientLogRecord.class);
addPacket(PacketD1ServerCantAuthenticate.class);
addPacket(PacketD0ServerException.class);
addPacket(Packet00ClientWebRequest.class);
addPacket(Packet80ServerWebResponse.class);
}
}

View File

@ -1,13 +0,0 @@
package fr.pandacube.util.network.packet;
/**
* On attend d'un instance de {@link PacketClient} qu'il soit envoyé depuis
* une connexion Client vers une connexion Serveur (d'un point de vue TCP)
*/
public abstract class PacketClient extends Packet {
public PacketClient(byte c) {
super(c);
}
}

View File

@ -1,24 +0,0 @@
package fr.pandacube.util.network.packet;
/**
*
* Thrown when there is a problem when constructing, sending or handling a packet.
*
* Only the server may send a string representation of an exception to the client, not the reverse
*
*/
public class PacketException extends RuntimeException {
private static final long serialVersionUID = 1L;
public PacketException(String m) {
super(m);
}
public PacketException(String m, Throwable t) {
super(m, t);
}
public PacketException(Throwable t) {
super(t);
}
}

View File

@ -1,13 +0,0 @@
package fr.pandacube.util.network.packet;
/**
* On attend d'un instance de {@link PacketServer} qu'il soit envoyé depuis
* une connexion Serveur vers une connexion Client (d'un point de vue TCP)
*/
public abstract class PacketServer extends Packet {
public PacketServer(byte c) {
super(c);
}
}

View File

@ -1,8 +0,0 @@
package fr.pandacube.util.network.packet;
@FunctionalInterface
public interface ResponseCallback<T extends Packet> {
public void call(T packet);
}

View File

@ -1,19 +0,0 @@
package fr.pandacube.util.network.packet.bytebuffer;
/**
* Cette interface permet à un {@link ByteBuffer} de sérialiser sous forme de
* données binaire
* les attributs de la classe courante.<br/>
* <br/>
* Les classes concrètes implémentant cette interface doivent avoir un
* constructeur vide, utilisé
* lors de la désérialisation
*
*/
public interface ByteSerializable {
public void serializeToByteBuffer(ByteBuffer buffer);
public void deserializeFromByteBuffer(ByteBuffer buffer);
}

View File

@ -1,39 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet30ClientDeclareProcess extends PacketClient {
private String processName;
private String type;
public Packet30ClientDeclareProcess() {
super((byte)0x30);
}
public void setProcessName(String pN) {
processName = pN;
}
public void setType(String t) {
type = t;
}
public String getProcessName() { return processName; }
public String getType() { return type; }
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(processName);
buffer.putString(type);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
processName = buffer.getString();
type = buffer.getString();
}
}

View File

@ -1,22 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet31ClientClose extends PacketClient {
public Packet31ClientClose() {
super((byte)0x31);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
// no data
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
// no data
}
}

View File

@ -1,61 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.util.network.packet.packets.core_slave.PacketB4ServerProcessQuery.QueryType;
public class Packet32ClientProcessQueryResponse extends PacketClient {
private QueryType type;
private int queryId;
private byte[] responseData = null;
public Packet32ClientProcessQueryResponse() {
super((byte)0x32);
}
public QueryType getType() { return type; }
public int getQueryId() { return queryId; }
public byte[] getResponseData() { return responseData; }
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putInt(type.ordinal());
buffer.putInt(queryId);
buffer.putSizedByteArray(responseData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
type = QueryType.values()[buffer.getInt()];
queryId = buffer.getInt();
responseData = buffer.getSizedByteArray();
}
public static Packet32ClientProcessQueryResponse destroyResponse(int queryId) {
Packet32ClientProcessQueryResponse q = new Packet32ClientProcessQueryResponse();
q.type = QueryType.DESTROY;
q.queryId = queryId;
return q;
}
public static Packet32ClientProcessQueryResponse isAliveResponse(int queryId, boolean resp) {
Packet32ClientProcessQueryResponse q = new Packet32ClientProcessQueryResponse();
q.type = QueryType.IS_ALIVE;
q.queryId = queryId;
q.responseData = new byte[] {(byte)(resp ? 1 : 0)};
return q;
}
public static Packet32ClientProcessQueryResponse exitStatusResponse(int queryId, int resp) {
Packet32ClientProcessQueryResponse q = new Packet32ClientProcessQueryResponse();
q.type = QueryType.EXIT_STATUS;
q.queryId = queryId;
q.responseData = new ByteBuffer(4, CHARSET).putInt(resp).array();
return q;
}
}

View File

@ -1,22 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB0ServerClose extends PacketServer {
public PacketB0ServerClose() {
super((byte)0xB0);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
// no data
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
// no data
}
}

View File

@ -1,32 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB1ServerProcessDeclarationConfirm extends PacketServer {
private String serverName;
public PacketB1ServerProcessDeclarationConfirm() {
super((byte)0xB1);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(serverName);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
serverName = buffer.getString();
}
public String getServerName() {
return serverName;
}
public void setProcessName(String name) {
serverName = name;
}
}

View File

@ -1,22 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB2ServerConnectSuccess extends PacketServer {
public PacketB2ServerConnectSuccess() {
super((byte)0xB2);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
// no data
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
// no data
}
}

View File

@ -1,42 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB3ServerProcessInput extends PacketServer {
private String serverName;
private byte[] dataToSend;
public PacketB3ServerProcessInput() {
super((byte)0xB3);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(serverName);
buffer.putSizedByteArray(dataToSend);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
serverName = buffer.getString();
dataToSend = buffer.getSizedByteArray();
}
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public byte[] getDataToSend() {
return dataToSend;
}
public void setDataToSend(byte[] dataToSend) {
this.dataToSend = dataToSend;
}
}

View File

@ -1,73 +0,0 @@
package fr.pandacube.util.network.packet.packets.core_slave;
import fr.pandacube.util.RandomUtil;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketB4ServerProcessQuery extends PacketServer {
private String processName;
private QueryType type;
private int queryId = RandomUtil.rand.nextInt();
private byte[] queryData = null;
public PacketB4ServerProcessQuery() {
super((byte)0xB4);
}
public String getProcessName() { return processName; }
public QueryType getType() { return type; }
public int getQueryId() { return queryId; }
public byte[] getQueryData() { return queryData; }
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putInt(type.ordinal());
buffer.putInt(queryId);
buffer.putSizedByteArray(queryData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
type = QueryType.values()[buffer.getInt()];
queryId = buffer.getInt();
queryData = buffer.getSizedByteArray();
}
public static PacketB4ServerProcessQuery startQuery(String processName) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.START;
return q;
}
public static PacketB4ServerProcessQuery destroyQuery(String processName, boolean wait) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.DESTROY;
q.queryData = new byte[] {(byte)(wait ? 1 : 0)};
return q;
}
public static PacketB4ServerProcessQuery isAliveQuery(String processName) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.IS_ALIVE;
return q;
}
public static PacketB4ServerProcessQuery exitStatusQuery(String processName) {
PacketB4ServerProcessQuery q = new PacketB4ServerProcessQuery();
q.processName = processName;
q.type = QueryType.EXIT_STATUS;
return q;
}
public enum QueryType {
START, DESTROY, IS_ALIVE, EXIT_STATUS;
}
}

View File

@ -1,35 +0,0 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet50ClientAuthenticate extends PacketClient {
private String password;
private String additionalData = "";
public Packet50ClientAuthenticate() {
super((byte)0x50);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(password);
buffer.putString(additionalData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
password = buffer.getString();
additionalData = buffer.getString();
}
public String getPassword() { return password; }
public void setPassword(String p) { password = p; }
public String getAdditionalData() { return additionalData; }
public void setAdditionalData(String data) { additionalData = data; }
}

View File

@ -1,76 +0,0 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet51ClientLogRecord extends PacketClient {
private long time;
private String level;
private String threadName;
private String message;
private String throwable;
public Packet51ClientLogRecord() {
super((byte)0x51);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
time = buffer.getLong();
level = buffer.getString();
threadName = buffer.getString();
message = buffer.getString();
throwable = buffer.getString();
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putLong(time);
buffer.putString(level);
buffer.putString(threadName);
buffer.putString(message);
buffer.putString(throwable);
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getThrowable() {
return throwable;
}
public void setThrowable(String throwable) {
this.throwable = throwable;
}
}

View File

@ -1,38 +0,0 @@
package fr.pandacube.util.network.packet.packets.global;
import java.io.PrintWriter;
import java.io.StringWriter;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketD0ServerException extends PacketServer {
private String exception;
public PacketD0ServerException() {
super((byte)0xD0);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(exception);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
exception = buffer.getString();
}
public void setException(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
exception = sw.toString();
}
public String getExceptionString() {
return exception;
}
}

View File

@ -1,20 +0,0 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketD1ServerCantAuthenticate extends PacketServer {
public PacketD1ServerCantAuthenticate() {
super((byte)0xD1);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
}
}

View File

@ -1,30 +0,0 @@
package fr.pandacube.util.network.packet.packets.global;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class PacketD2ServerCommand extends PacketServer {
private String command;
private boolean async;
private boolean returnResult;
public PacketD2ServerCommand() {
super((byte)0xD2);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(command);
buffer.putByte((byte) (async ? 1 : 0));
buffer.putByte((byte) (returnResult ? 1 : 0));
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
command = buffer.getString();
async = buffer.getByte() != 0;
returnResult = buffer.getByte() != 0;
}
}

View File

@ -1,43 +0,0 @@
package fr.pandacube.util.network.packet.packets.web;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet00ClientWebRequest extends PacketClient {
private String password;
private String jsonData;
public Packet00ClientWebRequest() {
super((byte)0x00);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(password);
buffer.putString(jsonData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
password = buffer.getString();
jsonData = buffer.getString();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getJsonData() {
return jsonData;
}
public void setJsonData(String jsonData) {
this.jsonData = jsonData;
}
}

View File

@ -1,32 +0,0 @@
package fr.pandacube.util.network.packet.packets.web;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
public class Packet80ServerWebResponse extends PacketServer {
private String jsonData;
public Packet80ServerWebResponse() {
super((byte)0x80);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(jsonData);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
jsonData = buffer.getString();
}
public String getJsonData() {
return jsonData;
}
public void setJsonData(String jsonData) {
this.jsonData = jsonData;
}
}

View File

@ -1,56 +0,0 @@
package fr.pandacube.util.network.server;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import fr.pandacube.util.network.server.TCPServer.TCPServerClientConnection;
public class BandwidthCalculation {
private List<PacketStat> packetHistory = new LinkedList<>();
public synchronized void addPacket(TCPServerClientConnection co, boolean in, long size) {
packetHistory.add(new PacketStat(co, in, size));
}
/**
* Get the instant bandwith in byte/s
*
* @param input true if getting input bw, false if getting output, null if
* getting input + output
* @param co
* @return
*/
public synchronized long getBandWidth(Boolean input, TCPServerClientConnection co) {
long currentTime = System.currentTimeMillis();
Iterator<PacketStat> it = packetHistory.iterator();
long sum = 0;
while (it.hasNext()) {
PacketStat el = it.next();
if (el.time < currentTime - 1000) {
it.remove();
continue;
}
if (input != null && el.input != input.booleanValue()) continue;
if (co != null && !co.equals(el.connection)) continue;
sum += el.packetSize;
}
return sum;
}
private class PacketStat {
public final long time;
public final long packetSize;
public final boolean input;
public final TCPServerClientConnection connection;
public PacketStat(TCPServerClientConnection co, boolean input, long size) {
time = System.currentTimeMillis();
packetSize = size;
this.input = input;
connection = co;
}
}
}

View File

@ -1,355 +0,0 @@
package fr.pandacube.util.network.server;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.logging.Level;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.javatuples.Pair;
import fr.pandacube.Pandacube;
import fr.pandacube.util.Log;
import fr.pandacube.util.network.packet.Packet;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.packet.PacketException;
import fr.pandacube.util.network.packet.PacketServer;
import fr.pandacube.util.network.packet.ResponseCallback;
import fr.pandacube.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.util.network.packet.packets.global.PacketD0ServerException;
/**
*
* @author Marc Baloup
*
*/
public class TCPServer extends Thread implements Closeable {
private static AtomicInteger connectionCounterId = new AtomicInteger(0);
private ServerSocket socket;
private TCPServerListener listener;
private String socketName;
private List<TCPServerClientConnection> clients = Collections.synchronizedList(new ArrayList<>());
private AtomicBoolean isClosed = new AtomicBoolean(false);
public final BandwidthCalculation bandwidthCalculation = new BandwidthCalculation();
public TCPServer(int port, String sckName, TCPServerListener l) throws IOException {
super("TCPSv " + sckName);
setDaemon(true);
if (port <= 0 || port > 65535) throw new IllegalArgumentException("le numéro de port est invalide");
socket = new ServerSocket();
socket.setReceiveBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socket.setPerformancePreferences(0, 1, 0);
socket.bind(new InetSocketAddress(port));
listener = l;
try {
listener.onSocketOpen(this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onSocketOpen()", e);
}
socketName = sckName;
}
@Override
public void run() {
try {
while (true) {
Socket socketClient = socket.accept();
socketClient.setSendBufferSize(Pandacube.NETWORK_TCP_BUFFER_SIZE);
socketClient.setSoTimeout(Pandacube.NETWORK_TIMEOUT);
try {
TCPServerClientConnection co = new TCPServerClientConnection(socketClient,
connectionCounterId.getAndIncrement());
clients.add(co);
co.start();
} catch (IOException e) {
Log.getLogger().log(Level.SEVERE, "Connexion impossible avec " + socketClient.getInetAddress());
}
}
} catch(SocketException e) {
} catch (Exception e) {
Log.warning("Plus aucune connexion ne peux être acceptée", e);
}
}
public class TCPServerClientConnection extends Thread {
private Socket cSocket;
private InputStream in;
private OutputStream out;
private SocketAddress address;
private TCPServerConnectionOutputThread outThread;
private List<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> callbacks = Collections.synchronizedList(new ArrayList<>());
private List<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> callbacksAvoidListener = Collections.synchronizedList(new ArrayList<>());
public TCPServerClientConnection(Socket s, int coId) throws IOException {
super("TCPSv " + socketName + " Conn#" + coId + " In");
setDaemon(true);
cSocket = s;
in = cSocket.getInputStream();
out = cSocket.getOutputStream();
address = new InetSocketAddress(cSocket.getInetAddress(), cSocket.getPort());
try {
listener.onClientConnect(TCPServer.this, this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onClientConnect()", e);
}
outThread = new TCPServerConnectionOutputThread(coId);
outThread.start();
}
@Override
public void run() {
try {
byte[] code = new byte[1];
while (!cSocket.isClosed() && in.read(code) != -1) {
byte[] sizeB = new byte[4];
if (in.read(sizeB) != 4) throw new IOException("Socket " + address + " closed");
int size = new ByteBuffer(sizeB, Packet.CHARSET).getInt();
byte[] content = new byte[size];
forceReadBytes(content);
byte[] packetData = new ByteBuffer(1 + 4 + size, Packet.CHARSET).putByteArray(code).putByteArray(sizeB)
.putByteArray(content).array();
bandwidthCalculation.addPacket(this, true, packetData.length);
try {
Packet p = Packet.constructPacket(packetData);
if (!(p instanceof PacketClient))
throw new PacketException(p.getClass().getCanonicalName() + " is not an instanceof PacketClient");
PacketClient pc = (PacketClient) p;
boolean oneCallbackExecuted = executeCallbacks(pc, callbacksAvoidListener);
if (!oneCallbackExecuted)
listener.onPacketReceive(TCPServer.this, this, pc);
executeCallbacks(pc, callbacks);
} catch (Throwable e) {
Log.severe("Exception while handling packet. This exception will be sent to the client with PacketServerException packet.", e);
PacketD0ServerException packet = new PacketD0ServerException();
packet.setException(e);
send(packet);
if (e instanceof InterruptedException || e instanceof Error)
throw e;
}
}
} catch(SocketException e) {
} catch (Exception e) {
Log.severe("Closing connection " + address, e);
}
close();
}
private boolean executeCallbacks(PacketClient pc, List<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> callbacks) {
boolean executedOne = false;
synchronized (callbacks) {
for(Iterator<Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>>> it = callbacks.iterator(); it.hasNext();) {
Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>> c = it.next();
try {
if (c.getValue0().test(pc)) {
it.remove();
c.getValue1().call(pc);
executedOne = true;
}
} catch (Exception e) {
Log.severe("Exception while executing callback", e);
}
}
}
return executedOne;
}
public void send(PacketServer p) {
outThread.addPacket(p);
}
public void sendAndGetResponse(PacketServer packet, Predicate<PacketClient> responseCondition, ResponseCallback<PacketClient> callback, boolean avoidListener) {
Pair<Predicate<PacketClient>, ResponseCallback<PacketClient>> p = new Pair<>(responseCondition, callback);
if (avoidListener)
callbacksAvoidListener.add(p);
else
callbacks.add(p);
send(packet);
}
/**
*
* @param packet the packet to send
* @param responseCondition {@link Predicate} that check each received packet to know which
* is the expected one as a response of the sended packet.
* @param avoidListener
* @param timeout
* @return
* @throws InterruptedException
*/
public PacketClient sendAndWaitForResponse(PacketServer packet, Predicate<PacketClient> responseCondition, boolean avoidListener, long timeout) throws InterruptedException {
AtomicReference<PacketClient> pcStorage = new AtomicReference<>(null);
synchronized (pcStorage) {
sendAndGetResponse(packet, responseCondition, packetClient -> {
synchronized (pcStorage) {
pcStorage.set(packetClient);
pcStorage.notifyAll();
}
}, true);
pcStorage.wait(timeout);
return pcStorage.get();
}
}
private void forceReadBytes(byte[] buff) throws IOException {
int pos = 0;
do {
int nbR = in.read(buff, pos, buff.length - pos);
if (nbR == -1) throw new IOException("Can't read required amount of byte");
pos += nbR;
} while (pos < buff.length);
}
public void close() {
if (cSocket.isClosed()) return;
try {
listener.onClientDisconnect(TCPServer.this, this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onClientDisconnect()", e);
}
clients.remove(this);
try {
Thread.sleep(200);
cSocket.close();
if (!Thread.currentThread().equals(outThread)) send(new PacketServer((byte) 0) {
@Override public void serializeToByteBuffer(ByteBuffer buffer) {}
@Override public void deserializeFromByteBuffer(ByteBuffer buffer) {}
});
// provoque une exception dans le thread de sortie, et la
// termine
} catch (Exception e) { }
}
private class TCPServerConnectionOutputThread extends Thread {
private BlockingQueue<PacketServer> packetQueue = new LinkedBlockingDeque<>();
public TCPServerConnectionOutputThread(int coId) {
super("TCPSv " + socketName + " Conn#" + coId + " Out");
setDaemon(true);
}
private void addPacket(PacketServer packet) {
packetQueue.add(packet);
}
@Override
public void run() {
try {
while (!cSocket.isClosed()) {
PacketServer packet = packetQueue.poll(1, TimeUnit.SECONDS);
byte[] data;
if (packet != null) {
try {
data = packet.getFullSerializedPacket();
bandwidthCalculation.addPacket(TCPServerClientConnection.this, false, data.length);
out.write(data);
out.flush();
} catch (IOException e) {
throw e;
} catch (Exception e) {
Log.severe("Can't send packet "+packet.getClass(), e);
}
}
}
} catch (InterruptedException|IOException e) {}
close();
}
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("thread", getName())
.append("socket", cSocket).toString();
}
}
@Override
public void close() {
try {
if (isClosed.get()) return;
isClosed.set(true);
clients.forEach(el -> el.close());
try {
listener.onSocketClose(this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onSocketClose()", e);
}
socket.close();
} catch (IOException e) {}
}
public boolean isClosed() {
return isClosed.get() || socket.isClosed();
}
public List<TCPServerClientConnection> getClients() {
synchronized (clients) {
return new ArrayList<>(clients);
}
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("thread", getName())
.append("socket", socket).toString();
}
}

View File

@ -1,19 +0,0 @@
package fr.pandacube.util.network.server;
import fr.pandacube.util.network.packet.PacketClient;
import fr.pandacube.util.network.server.TCPServer.TCPServerClientConnection;
public interface TCPServerListener {
public void onSocketOpen(TCPServer svConnection);
public void onClientConnect(TCPServer svConnection, TCPServerClientConnection clientConnection);
public void onPacketReceive(TCPServer svConnection, TCPServerClientConnection clientConnection,
PacketClient packet);
public void onClientDisconnect(TCPServer svConnection, TCPServerClientConnection clientConnection);
public void onSocketClose(TCPServer svConnection);
}

View File

@ -2,6 +2,7 @@ package fr.pandacube.util.network_api.client;
import java.io.PrintStream;
@Deprecated
public abstract class AbstractRequest {
private final String pass;

View File

@ -5,6 +5,7 @@ import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.Socket;
@Deprecated
public class NetworkAPISender {
public static ResponseAnalyser sendRequest(InetSocketAddress cible, AbstractRequest request) throws IOException {

View File

@ -5,6 +5,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
@Deprecated
public class ResponseAnalyser {
/**
* Indique si la requête s'est bien exécutée (l'entête de la réponse est

View File

@ -7,6 +7,7 @@ import java.net.Socket;
import fr.pandacube.util.Log;
@Deprecated
public abstract class AbstractRequestExecutor {
public final String command;

View File

@ -1,14 +0,0 @@
package fr.pandacube.util.network_api.server;
/**
* Interface permettant de gérer l'exécution asynchrone d'un PacketExecutor.
*
* @author Marc Baloup
*
*/
@FunctionalInterface
public interface NAPIExecutionHandler {
public void handleRun(Runnable executor);
}

View File

@ -5,6 +5,7 @@ import java.net.ServerSocket;
import java.util.Arrays;
import java.util.HashMap;
@Deprecated
public class NetworkAPIListener implements Runnable {
private int port = 0;
@ -12,7 +13,6 @@ public class NetworkAPIListener implements Runnable {
private ServerSocket serverSocket;
private HashMap<String, AbstractRequestExecutor> requestExecutors = new HashMap<>();
private String name;
private NAPIExecutionHandler nAPIExecutionHandler;
/**
* Instencie le côté serveur du NetworkAPI
@ -23,11 +23,10 @@ public class NetworkAPIListener implements Runnable {
* @param peh PacketExecutionHandler permettant de prendre en charge
* l'exécution asynchrone d'une requête reçu pas un client
*/
public NetworkAPIListener(String n, int p, String pa, NAPIExecutionHandler peh) {
public NetworkAPIListener(String n, int p, String pa) {
port = p;
pass = pa;
name = n;
nAPIExecutionHandler = peh;
}
@Override
@ -46,7 +45,9 @@ public class NetworkAPIListener implements Runnable {
try {
// réception des connexion client
while (!serverSocket.isClosed()) {
nAPIExecutionHandler.handleRun(new PacketExecutor(serverSocket.accept(), this));
Thread t = new Thread(new PacketExecutor(serverSocket.accept(), this));
t.setDaemon(true);
t.start();
}
} catch (IOException e) {}

View File

@ -16,6 +16,7 @@ import fr.pandacube.util.network_api.server.RequestAnalyser.BadRequestException;
* @author Marc Baloup
*
*/
@Deprecated
public class PacketExecutor implements Runnable {
private Socket socket;
private NetworkAPIListener networkAPIListener;

View File

@ -5,6 +5,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
@Deprecated
public class RequestAnalyser {
private NetworkAPIListener networkAPIListener;

View File

@ -2,6 +2,7 @@ package fr.pandacube.util.network_api.server;
import java.io.PrintStream;
@Deprecated
public class Response {
public boolean good = true;
public String data = "";

View File

@ -1,12 +0,0 @@
package fr.pandacube.util.network_api.server;
public class ThreadNAPIExecutionHandler implements NAPIExecutionHandler {
@Override
public void handleRun(Runnable executor) {
Thread t = new Thread(executor);
t.setDaemon(true);
t.start();
}
}