Some checks reported errors
studiorailgun/Renderer/pipeline/head Something is wrong with the build of this commit
418 lines
14 KiB
Java
418 lines
14 KiB
Java
package electrosphere.net.server;
|
|
|
|
import electrosphere.entity.types.creature.CreatureTemplate;
|
|
import electrosphere.engine.Globals;
|
|
import electrosphere.logger.LoggerInterface;
|
|
import electrosphere.net.parser.net.message.AuthMessage;
|
|
import electrosphere.net.parser.net.message.NetworkMessage;
|
|
import electrosphere.net.parser.net.message.ServerMessage;
|
|
import electrosphere.net.parser.net.raw.NetworkParser;
|
|
import electrosphere.net.server.player.Player;
|
|
import electrosphere.util.CodeUtils;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.Socket;
|
|
import java.net.SocketException;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.concurrent.Semaphore;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* A connection to the server
|
|
*/
|
|
public class ServerConnectionHandler implements Runnable {
|
|
|
|
//the player id associated with this connection
|
|
static int playerIdIncrementer = 0;
|
|
|
|
//local carrier variables
|
|
boolean local = false;
|
|
|
|
//socket carrier variables
|
|
Socket socket;
|
|
|
|
//the streams for the connection
|
|
// CryptoInputStream inputStream;
|
|
// CryptoOutputStream outputStream;
|
|
/**
|
|
* The input stream for packets
|
|
*/
|
|
InputStream inputStream;
|
|
/**
|
|
* The output stream for packets
|
|
*/
|
|
OutputStream outputStream;
|
|
|
|
//the network parser for the streams
|
|
NetworkParser networkParser;
|
|
|
|
//initialized status
|
|
boolean initialized;
|
|
//authentication status
|
|
boolean isAuthenticated = false;
|
|
|
|
//the player id
|
|
int playerID;
|
|
//the player's entity id
|
|
int playerEntityID;
|
|
|
|
/**
|
|
* Tracks whether this connection is still communicating with the client
|
|
*/
|
|
boolean isConnected = true;
|
|
|
|
//the creature template associated with this player
|
|
CreatureTemplate currentCreatureTemplate;
|
|
|
|
//the server protocol object associated with this player
|
|
MessageProtocol messageProtocol;
|
|
|
|
//thresholds for determining when to send pings and when a client has disconnected
|
|
static final long SEND_PING_THRESHOLD = 3000;
|
|
static final long PING_DISCONNECT_THRESHOLD = 20000;
|
|
//used to keep track of ping/pong messages with client
|
|
long lastPingTime = 0;
|
|
long lastPongTime = 0;
|
|
//flag to disconnect due to pipe break
|
|
boolean socketException = false;
|
|
|
|
|
|
//debug netmonitor stuff
|
|
String netMonitorHandle;
|
|
//Used to copy messages from network parser to NetMonitor
|
|
List<NetworkMessage> netMonitorCache = new LinkedList<NetworkMessage>();
|
|
|
|
//the lock used for synchronizing the synchronous message queue
|
|
Semaphore synchronousMessageLock = new Semaphore(1);
|
|
//the queue of synchonous network messages
|
|
List<NetworkMessage> synchronousMessageQueue = new CopyOnWriteArrayList<NetworkMessage>();
|
|
|
|
/**
|
|
* Constructs a connection from a socket
|
|
* @param socket the socket
|
|
*/
|
|
public ServerConnectionHandler(Socket socket) {
|
|
this.socket = socket;
|
|
playerID = getNewPlayerID();
|
|
LoggerInterface.loggerNetworking.INFO("Player ID: " + playerID);
|
|
this.messageProtocol = new MessageProtocol(this);
|
|
}
|
|
|
|
/**
|
|
* Constructs a connection from an arbitrary input and output stream
|
|
* @param serverInputStream the input stream
|
|
* @param serverOutputStream the output stream
|
|
*/
|
|
public ServerConnectionHandler(InputStream serverInputStream, OutputStream serverOutputStream){
|
|
this.local = true;
|
|
playerID = getNewPlayerID();
|
|
LoggerInterface.loggerNetworking.INFO("Player ID: " + playerID);
|
|
inputStream = serverInputStream;
|
|
outputStream = serverOutputStream;
|
|
this.messageProtocol = new MessageProtocol(this);
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
LoggerInterface.loggerNetworking.INFO("ServerConnectionHandler start");
|
|
initialized = false;
|
|
|
|
|
|
///
|
|
///
|
|
/// SETUP
|
|
///
|
|
///
|
|
|
|
|
|
//startup is different whether need to set socket streams or received in construction of object (ie if creating local "connection")
|
|
if(this.local){
|
|
//run if serverconnectionHandler is created by passing in input/output streams
|
|
networkParser = new NetworkParser(inputStream,outputStream);
|
|
messageProtocol = new MessageProtocol(this);
|
|
} else {
|
|
//run if ServerConnectionHandler is created by passing in a socket
|
|
try {
|
|
socket.setSoTimeout(100);
|
|
} catch (SocketException ex) {
|
|
ex.printStackTrace();
|
|
}
|
|
|
|
//TODO: use this commented block of code as a reference for implementing encryption on top of the game connection
|
|
// final SecretKeySpec key = new SecretKeySpec(("1234567890123456").getBytes(),"AES");
|
|
// final Properties properties = new Properties();
|
|
// final RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(4096, BigInteger.probablePrime(4000, new Random()));
|
|
// try {
|
|
// inputStream = new CryptoInputStream("AES/ECB/PKCS5Padding",properties,socket.getInputStream(),key,spec);
|
|
// } catch (IOException ex) {
|
|
// ex.printStackTrace();
|
|
// System.exit(1);
|
|
// }
|
|
// try {
|
|
// outputStream = new CryptoOutputStream("AES/ECB/PKCS5Padding",properties,socket.getOutputStream(),key,spec);
|
|
// } catch (IOException ex) {
|
|
// ex.printStackTrace();
|
|
// System.exit(1);
|
|
// }
|
|
try {
|
|
inputStream = socket.getInputStream();
|
|
outputStream = socket.getOutputStream();
|
|
networkParser = new NetworkParser(inputStream,outputStream);
|
|
messageProtocol = new MessageProtocol(this);
|
|
} catch (IOException ex) {
|
|
ex.printStackTrace();
|
|
LoggerInterface.loggerNetworking.ERROR("", ex);
|
|
}
|
|
}
|
|
|
|
NetworkMessage pingMessage = ServerMessage.constructPingMessage();
|
|
NetworkMessage authRequestMessage = AuthMessage.constructAuthRequestMessage();
|
|
|
|
//net monitor registration
|
|
if(Globals.netMonitor != null){
|
|
this.netMonitorHandle = Globals.netMonitor.registerConnection();
|
|
Globals.netMonitor.logMessage(netMonitorHandle, pingMessage, false);
|
|
Globals.netMonitor.logMessage(netMonitorHandle, authRequestMessage, false);
|
|
}
|
|
|
|
networkParser.addOutgoingMessage(pingMessage);
|
|
networkParser.addOutgoingMessage(authRequestMessage);
|
|
|
|
|
|
|
|
|
|
///
|
|
///
|
|
/// MAIN LOOP
|
|
///
|
|
///
|
|
|
|
|
|
initialized = true;
|
|
while(Globals.threadManager.shouldKeepRunning() && this.isConnected == true){
|
|
|
|
//
|
|
// Main Loop
|
|
//
|
|
//parse messages both incoming and outgoing
|
|
try {
|
|
parseMessages();
|
|
} catch (SocketException e) {
|
|
//if we get a SocketException broken pipe (basically the client dc'd without telling us)
|
|
//set flag to disconnect client
|
|
//TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious
|
|
socketException = true;
|
|
LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage());
|
|
this.disconnect();
|
|
break;
|
|
} catch (IOException e){
|
|
//if we get a SocketException broken pipe (basically the client dc'd without telling us)
|
|
//set flag to disconnect client
|
|
//TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious
|
|
socketException = true;
|
|
LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage());
|
|
this.disconnect();
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Pings
|
|
//
|
|
//ping logic
|
|
long currentTime = System.currentTimeMillis();
|
|
//basically if we haven't sent a ping in a while, send one
|
|
if(currentTime - lastPingTime > SEND_PING_THRESHOLD){
|
|
addMessagetoOutgoingQueue(ServerMessage.constructPingMessage());
|
|
lastPingTime = currentTime;
|
|
if(lastPongTime == 0){
|
|
lastPongTime = lastPingTime;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Disconnections
|
|
//
|
|
//check if we meet disconnection criteria
|
|
//has it been too long since the last ping?
|
|
//have we had a socket exception?
|
|
if(lastPingTime - lastPongTime > PING_DISCONNECT_THRESHOLD || this.socketException == true){
|
|
//disconnected from the server
|
|
LoggerInterface.loggerNetworking.WARNING("Client disconnected");
|
|
//run disconnect routine
|
|
disconnect();
|
|
break;
|
|
}
|
|
}
|
|
|
|
LoggerInterface.loggerNetworking.WARNING("Server connection thread ended");
|
|
}
|
|
|
|
/**
|
|
* Had to wrap the message parsing block in a function to throw a SocketException
|
|
* without my linter freaking out
|
|
* @throws SocketException
|
|
*/
|
|
void parseMessages() throws SocketException, IOException {
|
|
//
|
|
//Read in messages
|
|
//
|
|
//attempt poll incoming messages
|
|
networkParser.readMessagesIn();
|
|
|
|
|
|
//
|
|
//Net monitor (debug)
|
|
//
|
|
//net monitor
|
|
if(Globals.netMonitor != null){
|
|
//incoming
|
|
netMonitorCache.clear();
|
|
networkParser.copyIncomingMessages(netMonitorCache);
|
|
for(NetworkMessage message : netMonitorCache){
|
|
Globals.netMonitor.logMessage(netMonitorHandle, message, true);
|
|
}
|
|
}
|
|
|
|
//
|
|
//Parse messages
|
|
//
|
|
//ponder incoming messages
|
|
while(networkParser.hasIncomingMessaage()){
|
|
NetworkMessage message = networkParser.popIncomingMessage();
|
|
this.messageProtocol.handleAsyncMessage(message);
|
|
}
|
|
|
|
|
|
//
|
|
//Net monitor (debug)
|
|
//
|
|
if(Globals.netMonitor != null){
|
|
//outgoing
|
|
netMonitorCache.clear();
|
|
networkParser.copyOutgoingMessages(netMonitorCache);
|
|
for(NetworkMessage message : netMonitorCache){
|
|
Globals.netMonitor.logMessage(netMonitorHandle, message, false);
|
|
}
|
|
}
|
|
|
|
//
|
|
//Send out messages
|
|
//
|
|
//push outgoing message
|
|
networkParser.pushMessagesOut();
|
|
try {
|
|
//sleep
|
|
TimeUnit.MILLISECONDS.sleep(1);
|
|
} catch (InterruptedException ex) {
|
|
CodeUtils.todo(ex, "Handle sleep interrupt on server connection");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles synchronous packets in the queue
|
|
*/
|
|
public void handleSynchronousPacketQueue(){
|
|
this.messageProtocol.handleSyncMessages();
|
|
}
|
|
|
|
public void setPlayerId(int id){
|
|
playerID = id;
|
|
}
|
|
|
|
public int getPlayerId(){
|
|
return playerID;
|
|
}
|
|
|
|
static int getNewPlayerID(){
|
|
playerIdIncrementer++;
|
|
return playerIdIncrementer;
|
|
}
|
|
|
|
public void setPlayerEntityId(int id){
|
|
LoggerInterface.loggerNetworking.DEBUG("Set player(" + this.playerID + ")'s entity ID to be " + id);
|
|
playerEntityID = id;
|
|
}
|
|
|
|
public int getPlayerEntityId(){
|
|
return playerEntityID;
|
|
}
|
|
|
|
/**
|
|
* Gets the player associated with this connection
|
|
* @return The player object
|
|
*/
|
|
public Player getPlayer(){
|
|
return Globals.playerManager.getPlayerFromId(playerID);
|
|
}
|
|
|
|
/**
|
|
* Gets the ip address of the connection
|
|
* @return The ip address
|
|
*/
|
|
public String getIPAddress(){
|
|
if(local){
|
|
return "127.0.0.1";
|
|
} else {
|
|
return socket.getRemoteSocketAddress().toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the socket of the connection, if it is a socket based connection. Otherwise returns null.
|
|
* @return The socket if it exists, null otherwise
|
|
*/
|
|
public Socket getSocket(){
|
|
return this.socket;
|
|
}
|
|
|
|
public void addMessagetoOutgoingQueue(NetworkMessage message){
|
|
networkParser.addOutgoingMessage(message);
|
|
}
|
|
|
|
boolean isConnectionPlayerEntity(int id){
|
|
return id == this.playerEntityID;
|
|
}
|
|
|
|
/**
|
|
* Returns true if running both client and server in same instance of the app and this is the player for that instance of the app
|
|
* @return
|
|
*/
|
|
boolean isServerClient(){
|
|
return Globals.RUN_SERVER && Globals.RUN_CLIENT && Globals.clientPlayer != null && this.playerID == Globals.clientPlayer.getId();
|
|
}
|
|
|
|
public void setCreatureTemplate(CreatureTemplate currentCreatureTemplate){
|
|
this.currentCreatureTemplate = currentCreatureTemplate;
|
|
}
|
|
|
|
public CreatureTemplate getCurrentCreatureTemplate(){
|
|
return this.currentCreatureTemplate;
|
|
}
|
|
|
|
public void markReceivedPongMessage(){
|
|
lastPongTime = System.currentTimeMillis();
|
|
}
|
|
|
|
/**
|
|
* Routine to run when the client disconnects
|
|
*/
|
|
private void disconnect(){
|
|
//close socket
|
|
if(socket != null && socket.isConnected()){
|
|
try {
|
|
socket.close();
|
|
} catch (IOException e) {
|
|
LoggerInterface.loggerNetworking.ERROR("Error closing socket", e);
|
|
}
|
|
}
|
|
this.isConnected = false;
|
|
//add connection to server list of connections to cleanup
|
|
Globals.server.addClientToCleanup(this);
|
|
}
|
|
|
|
}
|