Amélioration librairie réseau et ajout de packet pour PandacubeWeb

This commit is contained in:
Marc Baloup 2016-07-26 02:16:10 +02:00
parent 55748b0d5e
commit 5f10f1d55f
8 changed files with 273 additions and 54 deletions

View File

@ -7,10 +7,10 @@ import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import org.apache.commons.lang.builder.ToStringBuilder;
import fr.pandacube.java.Pandacube; import fr.pandacube.java.Pandacube;
import fr.pandacube.java.util.Log; import fr.pandacube.java.util.Log;
@ -18,6 +18,7 @@ import fr.pandacube.java.util.network.packet.Packet;
import fr.pandacube.java.util.network.packet.PacketClient; import fr.pandacube.java.util.network.packet.PacketClient;
import fr.pandacube.java.util.network.packet.PacketException; import fr.pandacube.java.util.network.packet.PacketException;
import fr.pandacube.java.util.network.packet.PacketServer; import fr.pandacube.java.util.network.packet.PacketServer;
import fr.pandacube.java.util.network.packet.packets.global.PacketServerException;
public class TCPClient extends Thread implements Closeable { public class TCPClient extends Thread implements Closeable {
@ -40,7 +41,11 @@ public class TCPClient extends Thread implements Closeable {
socket.connect(a); socket.connect(a);
addr = a; addr = a;
listener = l; listener = l;
listener.onConnect(this); try {
listener.onConnect(this);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onConnect()", e);
}
} }
@Override @Override
@ -61,28 +66,35 @@ public class TCPClient extends Thread implements Closeable {
byte[] packetData = ByteBuffer.allocate(1 + 4 + size).put(code).put(sizeB).put(content).array(); byte[] packetData = ByteBuffer.allocate(1 + 4 + size).put(code).put(sizeB).put(content).array();
try { try {
if (listener == null) throw new InvalidServerMessage(
"Le serveur ne peut actuellement pas prendre en charge de nouvelles requêtes. Les listeners n'ont pas encore été définis");
Packet p = Packet.constructPacket(packetData); Packet p = Packet.constructPacket(packetData);
if (!(p instanceof PacketServer)) throw new InvalidServerMessage( if (!(p instanceof PacketServer))
"Le type de packet reçu n'est pas un packet attendu : " + p.getClass().getCanonicalName()); throw new PacketException(p.getClass().getCanonicalName() + " is not a subclass of PacketServer");
if (p instanceof PacketServerException) {
try {
listener.onServerException(this, ((PacketServerException)p).getExceptionString());
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onPacketReceive()", e);
}
}
PacketServer ps = (PacketServer) p; PacketServer ps = (PacketServer) p;
listener.onPacketReceive(this, ps); try {
} catch (PacketException | InvalidServerMessage e) { listener.onPacketReceive(this, ps);
Log.getLogger().log(Level.SEVERE, "Message du serveur mal formé", e); } catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onPacketReceive()", e);
}
} catch (Exception e) { } catch (Exception e) {
Log.getLogger().log(Level.SEVERE, "Erreur lors de la prise en charge du message par le serveur", e); Log.severe("Exception while handling packet from server", e);
} }
} }
} catch (SocketTimeoutException e) {
System.err.println("Le serveur a prit trop de temps à répondre");
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Log.severe(e);
} }
close(); close();
} }
@ -110,10 +122,15 @@ public class TCPClient extends Thread implements Closeable {
if (isClosed.get()) return; if (isClosed.get()) return;
socket.close(); socket.close();
isClosed.set(true); isClosed.set(true);
listener.onDisconnect(this);
try {
listener.onDisconnect(this);
} catch (Exception e) {
Log.severe("Exception while calling TCPClientListener.onDisconnect()", e);
}
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.warning(e);
} }
} }
@ -130,13 +147,12 @@ public class TCPClient extends Thread implements Closeable {
public boolean isClosed() { public boolean isClosed() {
return isClosed.get() || socket.isClosed(); return isClosed.get() || socket.isClosed();
} }
public static class InvalidServerMessage extends RuntimeException { @Override
private static final long serialVersionUID = 1L; public String toString() {
return new ToStringBuilder(this)
public InvalidServerMessage(String message) { .append("thread", getName())
super(message); .append("socket", socket.getRemoteSocketAddress()).toString();
}
} }
} }

View File

@ -1,13 +1,42 @@
package fr.pandacube.java.util.network.client; package fr.pandacube.java.util.network.client;
import fr.pandacube.java.util.Log;
import fr.pandacube.java.util.network.packet.PacketServer; import fr.pandacube.java.util.network.packet.PacketServer;
public interface TCPClientListener { public interface TCPClientListener {
/**
* Called when a connection is opened
* @param connection the connection which is opening
*/
public void onConnect(TCPClient connection); 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); public void onPacketReceive(TCPClient connection, PacketServer packet);
/**
* Called before the connection closed
* @param connection the connection which is closing
*/
public void onDisconnect(TCPClient connection); public void onDisconnect(TCPClient connection);
} }

View File

@ -6,9 +6,31 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import fr.pandacube.java.Pandacube; import fr.pandacube.java.Pandacube;
import fr.pandacube.java.util.Log;
import fr.pandacube.java.util.network.packet.bytebuffer.ByteBuffer; import fr.pandacube.java.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.java.util.network.packet.bytebuffer.ByteSerializable; import fr.pandacube.java.util.network.packet.bytebuffer.ByteSerializable;
import fr.pandacube.java.util.network.packet.packets.global.PacketServerException;
import fr.pandacube.java.util.network.packet.packets.web.PacketClientWebRequest;
import fr.pandacube.java.util.network.packet.packets.web.PacketServerWebResponse;
/** <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)
* - 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 { public abstract class Packet implements ByteSerializable {
private final byte code; private final byte code;
@ -34,7 +56,8 @@ public abstract class Packet implements ByteSerializable {
public static Packet constructPacket(byte[] data) { public static Packet constructPacket(byte[] data) {
if (!packetTypes.containsKey(data[0])) if (!packetTypes.containsKey(data[0]))
throw new PacketException("l'identifiant du packet ne correspond à aucun type de packet : " + 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 { try {
Packet p = packetTypes.get(data[0]).newInstance(); Packet p = packetTypes.get(data[0]).newInstance();
@ -42,27 +65,28 @@ public abstract class Packet implements ByteSerializable {
p.deserializeFromByteBuffer(dataBuffer); p.deserializeFromByteBuffer(dataBuffer);
return p; return p;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); throw new PacketException("Error while constructing packet", e);
throw new PacketException("erreur lors de la construction du packet");
} }
} }
@SuppressWarnings("unused")
private static <T extends Packet> void addPacket(Class<T> packetClass) { private static <T extends Packet> void addPacket(Class<T> packetClass) {
try { try {
Packet p = packetClass.newInstance(); Packet p = packetClass.newInstance();
packetTypes.put(p.code, packetClass); packetTypes.put(p.code, packetClass);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Log.severe(e);
} }
} }
static { static {
/* /*
* Ajout des types de packets (client + serveur) * Ajout des types de packets (client + serveur)
*/ */
// addPacket(PacketToto.class); addPacket(PacketClientWebRequest.class);
addPacket(PacketServerWebResponse.class);
addPacket(PacketServerException.class);
} }
} }

View File

@ -1,5 +1,12 @@
package fr.pandacube.java.util.network.packet; package fr.pandacube.java.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 { public class PacketException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -0,0 +1,39 @@
package fr.pandacube.java.util.network.packet.packets.global;
import java.io.PrintWriter;
import java.io.StringWriter;
import fr.pandacube.java.util.network.packet.PacketServer;
import fr.pandacube.java.util.network.packet.bytebuffer.ByteBuffer;
public class PacketServerException extends PacketServer {
private String exception;
public PacketServerException() {
super((byte)0xE0);
}
@Override
public void serializeToByteBuffer(ByteBuffer buffer) {
buffer.putString(exception);
}
@Override
public void deserializeFromByteBuffer(ByteBuffer buffer) {
// TODO Auto-generated method stub
}
public void setException(Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
exception = sw.toString();
}
public String getExceptionString() {
return exception;
}
}

View File

@ -0,0 +1,43 @@
package fr.pandacube.java.util.network.packet.packets.web;
import fr.pandacube.java.util.network.packet.PacketClient;
import fr.pandacube.java.util.network.packet.bytebuffer.ByteBuffer;
public class PacketClientWebRequest extends PacketClient {
private String password;
private String jsonData;
public PacketClientWebRequest() {
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

@ -0,0 +1,32 @@
package fr.pandacube.java.util.network.packet.packets.web;
import fr.pandacube.java.util.network.packet.PacketServer;
import fr.pandacube.java.util.network.packet.bytebuffer.ByteBuffer;
public class PacketServerWebResponse extends PacketServer {
private String jsonData;
public PacketServerWebResponse() {
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

@ -17,12 +17,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import org.apache.commons.lang.builder.ToStringBuilder;
import fr.pandacube.java.Pandacube; import fr.pandacube.java.Pandacube;
import fr.pandacube.java.util.Log; import fr.pandacube.java.util.Log;
import fr.pandacube.java.util.network.packet.Packet; import fr.pandacube.java.util.network.packet.Packet;
import fr.pandacube.java.util.network.packet.PacketClient; import fr.pandacube.java.util.network.packet.PacketClient;
import fr.pandacube.java.util.network.packet.PacketException;
import fr.pandacube.java.util.network.packet.PacketServer; import fr.pandacube.java.util.network.packet.PacketServer;
import fr.pandacube.java.util.network.packet.bytebuffer.ByteBuffer; import fr.pandacube.java.util.network.packet.bytebuffer.ByteBuffer;
import fr.pandacube.java.util.network.packet.packets.global.PacketServerException;
/** /**
* *
@ -50,7 +54,11 @@ public class TCPServer extends Thread implements Closeable {
socket.setPerformancePreferences(0, 2, 1); socket.setPerformancePreferences(0, 2, 1);
socket.bind(new InetSocketAddress(port)); socket.bind(new InetSocketAddress(port));
listener = l; listener = l;
listener.onSocketOpen(this); try {
listener.onSocketOpen(this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onSocketOpen()", e);
}
socketName = sckName; socketName = sckName;
} }
@ -67,7 +75,6 @@ public class TCPServer extends Thread implements Closeable {
TCPServerClientConnection co = new TCPServerClientConnection(socketClient, TCPServerClientConnection co = new TCPServerClientConnection(socketClient,
connectionCounterId.getAndIncrement()); connectionCounterId.getAndIncrement());
clients.add(co); clients.add(co);
listener.onClientConnect(this, co);
co.start(); co.start();
} catch (IOException e) { } catch (IOException e) {
Log.getLogger().log(Level.SEVERE, "Connexion impossible avec " + socketClient.getInetAddress()); Log.getLogger().log(Level.SEVERE, "Connexion impossible avec " + socketClient.getInetAddress());
@ -91,7 +98,11 @@ public class TCPServer extends Thread implements Closeable {
in = socket.getInputStream(); in = socket.getInputStream();
out = socket.getOutputStream(); out = socket.getOutputStream();
address = new InetSocketAddress(socket.getInetAddress(), socket.getPort()); address = new InetSocketAddress(socket.getInetAddress(), socket.getPort());
listener.onClientConnect(TCPServer.this, this); try {
listener.onClientConnect(TCPServer.this, this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onClientConnect()", e);
}
outThread = new TCPServerConnectionOutputThread(coId); outThread = new TCPServerConnectionOutputThread(coId);
outThread.start(); outThread.start();
} }
@ -102,7 +113,7 @@ public class TCPServer extends Thread implements Closeable {
byte[] code = new byte[1]; byte[] code = new byte[1];
while (!socket.isClosed() && in.read(code) != -1) { while (!socket.isClosed() && in.read(code) != -1) {
byte[] sizeB = new byte[4]; byte[] sizeB = new byte[4];
if (in.read(sizeB) != 4) throw new IOException("Socket " + address + " fermé"); if (in.read(sizeB) != 4) throw new IOException("Socket " + address + " closed");
int size = new ByteBuffer(sizeB, Packet.CHARSET).getInt(); int size = new ByteBuffer(sizeB, Packet.CHARSET).getInt();
@ -117,17 +128,16 @@ public class TCPServer extends Thread implements Closeable {
try { try {
interpreteReceivedMessage(this, packetData); interpreteReceivedMessage(this, packetData);
} catch (InvalidClientMessage e) {
Log.getLogger().log(Level.SEVERE, "Erreur protocole de : ", e);
} catch (Exception e) { } catch (Exception e) {
Log.getLogger().log(Level.SEVERE, "Erreur lors de la prise en charge du message par le serveur", Log.severe("Exception while handling packet. This exception will be sent to the client with PacketServerException packet.", e);
e); PacketServerException packet = new PacketServerException();
e.printStackTrace(); packet.setException(e);
send(packet);
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.getLogger().log(Level.SEVERE, "Fermeture de la connexion de " + address, e); Log.severe("Closing connection " + address, e);
} }
close(); close();
@ -149,7 +159,11 @@ public class TCPServer extends Thread implements Closeable {
public void close() { public void close() {
if (socket.isClosed()) return; if (socket.isClosed()) return;
listener.onClientDisconnect(TCPServer.this, this); try {
listener.onClientDisconnect(TCPServer.this, this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onClientDisconnect()", e);
}
clients.remove(this); clients.remove(this);
try { try {
@ -163,9 +177,7 @@ public class TCPServer extends Thread implements Closeable {
}); });
// provoque une exception dans le thread de sortie, et la // provoque une exception dans le thread de sortie, et la
// termine // termine
} catch (IOException e) { } catch (IOException e) { }
e.printStackTrace();
}
} }
private class TCPServerConnectionOutputThread extends Thread { private class TCPServerConnectionOutputThread extends Thread {
@ -200,18 +212,30 @@ public class TCPServer extends Thread implements Closeable {
} }
@Override
public String toString() {
return new ToStringBuilder(this)
.append("thread", getName())
.append("socket", socket).toString();
}
} }
private void interpreteReceivedMessage(TCPServerClientConnection co, byte[] data) { private void interpreteReceivedMessage(TCPServerClientConnection co, byte[] data) {
Packet p = Packet.constructPacket(data); Packet p = Packet.constructPacket(data);
if (!(p instanceof PacketClient)) throw new InvalidClientMessage( if (!(p instanceof PacketClient))
"Le type de packet reçu n'est pas un packet attendu : " + p.getClass().getCanonicalName()); throw new PacketException(p.getClass().getCanonicalName() + " is not an instanceof PacketClient");
PacketClient pc = (PacketClient) p; PacketClient pc = (PacketClient) p;
listener.onPacketReceive(this, co, pc); try {
listener.onPacketReceive(this, co, pc);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onPacketReceive()", e);
}
} }
@Override @Override
@ -223,20 +247,25 @@ public class TCPServer extends Thread implements Closeable {
socket.close(); socket.close();
isClosed.set(true); isClosed.set(true);
listener.onSocketClose(this); try {
listener.onSocketClose(this);
} catch(Exception e) {
Log.severe("Exception while calling TCPServerListener.onSocketClose()", e);
}
} catch (IOException e) {} } catch (IOException e) {}
} }
public boolean isClosed() { public boolean isClosed() {
return isClosed.get() || socket.isClosed(); return isClosed.get() || socket.isClosed();
} }
public static class InvalidClientMessage extends RuntimeException {
private static final long serialVersionUID = 1L; @Override
public String toString() {
public InvalidClientMessage(String message) { return new ToStringBuilder(this)
super(message); .append("thread", getName())
} .append("socket", socket).toString();
} }
} }