Renderer/src/main/java/electrosphere/net/server/ServerConnectionHandler.java
austin 5563b4366f
Some checks reported errors
studiorailgun/Renderer/pipeline/head Something is wrong with the build of this commit
thread manager
2024-08-22 21:24:44 -04:00

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);
}
}