Renderer/src/main/java/electrosphere/net/server/Server.java

193 lines
6.6 KiB
Java

package electrosphere.net.server;
import electrosphere.engine.Globals;
import electrosphere.engine.threads.LabeledThread.ThreadLabel;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.NetUtils;
import electrosphere.net.parser.net.message.NetworkMessage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;
/**
* Lowest level networking class for the server
*/
public class Server implements Runnable {
//tracks whether the server is open or not
private boolean isOpen = false;
//the port the server is running on
int port;
//the socket for the server
ServerSocket serverSocket;
//Used to synchronize additions/subtractions to the connections stored by this server
Semaphore connectListLock = new Semaphore(1);
//map of socket->connection
Map<Socket,ServerConnectionHandler> socketConnectionMap = new HashMap<Socket,ServerConnectionHandler>();
//the list of active connections
List<ServerConnectionHandler> activeConnections = new LinkedList<ServerConnectionHandler>();
//The list of connections to clean up
List<ServerConnectionHandler> connectionsToCleanup = new CopyOnWriteArrayList<ServerConnectionHandler>();
/**
* Inits the server
*/
void initServer(){
// clientMap = new HashMap<String,ServerConnectionHandler>();
}
/**
* Constructor
* @param port The port to run the server on
*/
public Server(int port){
this.port = port;
}
@Override
public void run() {
initServer();
try {
serverSocket = new ServerSocket(port);
//if we set port to 0, java searches for any available port to open
//This then explicitly alerts NetUtils of the real port
if(port == 0){
NetUtils.setPort(serverSocket.getLocalPort());
}
this.isOpen = true;
} catch(BindException ex){
LoggerInterface.loggerNetworking.ERROR("Failed to bind server socket!",ex);
} catch (IOException ex) {
LoggerInterface.loggerNetworking.ERROR("Failed to start server socket!",ex);
}
while(Globals.threadManager.shouldKeepRunning() && !serverSocket.isClosed()){
Socket newSocket;
try {
newSocket = serverSocket.accept();
connectListLock.acquireUninterruptibly();
ServerConnectionHandler newClient = new ServerConnectionHandler(newSocket);
// clientMap.put(newSocket.getInetAddress().getHostAddress(), newClient);
socketConnectionMap.put(newSocket, newClient);
activeConnections.add(newClient);
Globals.threadManager.start(ThreadLabel.NETWORKING_SERVER, new Thread(newClient));
connectListLock.release();
} catch (SocketException ex){
LoggerInterface.loggerNetworking.DEBUG("Server Socket closed!",ex);
} catch (IOException ex) {
LoggerInterface.loggerNetworking.ERROR("Socket error on client socket!",ex);
}
}
this.isOpen = false;
LoggerInterface.loggerNetworking.WARNING("Server socket thread ended");
}
/**
* Synchronously handles queued packets for each client connection
*/
public void synchronousPacketHandling(){
connectListLock.acquireUninterruptibly();
for(ServerConnectionHandler connectionHandler : activeConnections){
connectionHandler.handleSynchronousPacketQueue();
}
connectListLock.release();
}
/**
* Closes the server socket
*/
public void close(){
try {
if(serverSocket != null){
serverSocket.close();
}
this.isOpen = false;
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Broadcasts a message to all clients
* @param message The message to broadcast
*/
public void broadcastMessage(NetworkMessage message){
connectListLock.acquireUninterruptibly();
for(ServerConnectionHandler client : activeConnections){
if(Globals.clientPlayer == null || client.playerID != Globals.clientPlayer.getId()){
client.addMessagetoOutgoingQueue(message);
}
}
connectListLock.release();
}
/**
* Adds a connection created manually by two streams instead of receiving the streams from the server socket
* @param serverInputStream The input stream
* @param serverOutputStream The output stream
* @return The connection object for the provided streams
*/
public ServerConnectionHandler addLocalPlayer(InputStream serverInputStream, OutputStream serverOutputStream){
connectListLock.acquireUninterruptibly();
ServerConnectionHandler newClient = new ServerConnectionHandler(serverInputStream,serverOutputStream);
activeConnections.add(newClient);
Globals.threadManager.start(ThreadLabel.NETWORKING_SERVER, new Thread(newClient));
connectListLock.release();
return newClient;
}
/**
* Adds a client to the queue of connections to cleanup
* @param serverConnectionHandler The connection
*/
public void addClientToCleanup(ServerConnectionHandler serverConnectionHandler){
this.connectListLock.acquireUninterruptibly();
this.connectionsToCleanup.add(serverConnectionHandler);
this.connectListLock.release();
}
/**
* Cleans up dead connections on the server
*/
public void cleanupDeadConnections(){
this.connectListLock.acquireUninterruptibly();
for(ServerConnectionHandler connection : this.connectionsToCleanup){
//tell all clients to destroy the entity
ServerEntityUtils.destroyEntity(connection.getPlayer().getPlayerEntity());
this.activeConnections.remove(connection);
if(connection.getSocket() != null){
this.socketConnectionMap.remove(connection.getSocket());
}
}
this.connectListLock.release();
}
/**
* Gets whether the server is open or not
* @return true if is open, false otherwise
*/
public boolean isOpen(){
return isOpen;
}
}