package gdwNet.server; import gdwNet.NETCONSTANTS; import gdwNet.RESPONCECODES; import gdwUtils.DefaultCharSet; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Random; import java.util.concurrent.ConcurrentLinkedQueue; public abstract class BasicServer { // *threads* private MulticastresponceThread mThread; private final BroadcastresponseThread bThread; private final IncomingConnectionHandlerThread iThread; // *attributes* /** maximum allowed clients on server */ protected int maxPlayer; /** current number of connected clienst */ private int currentConnections; /** hashmap from playerId to client */ private final HashMap<Integer, BasicClientConnection> clientConnections; /** counter for playerIds */ private int idCounter; /** current */ protected String infoText; /** random long for serverId */ protected final long serverId; /** bounded tcp Port */ private final int tcpPort; /** the byte message */ private ByteBuffer broadcastResponce; /** the byte message attached to the broadcastResponce buffer */ private ByteBuffer broadcastResponceAttachment; // *to work with* /** queue that has all the joinrequest since last update*/ private final ConcurrentLinkedQueue<JoinRequestWrapper> joinRequests; /** */ private final ConcurrentLinkedQueue<ReconnectRequestWrapper> recoRequests; /** older first */ private final ConcurrentLinkedQueue<LeaverDataWrapper> leaverStack; // *flags* // let clients reconnect after a disconnect/timeout private boolean blockReconnector; // drop all joinrequest private boolean blockNewconncector; // a internal logic flag for no playerlimit private boolean noPlayerLimit; public BasicServer(int maxPlayer) throws IOException { this(maxPlayer, NETCONSTANTS.DEFAULT_INFOTEXT, false, false); } public BasicServer(int maxPlayer, String infoText, boolean blockReconnector, boolean blockNewconnector) throws IOException { // setting up this.maxPlayer = maxPlayer; currentConnections = 0; this.clientConnections = new HashMap<Integer, BasicClientConnection>(); this.idCounter = 0; this.infoText = infoText; this.serverId = new Random().nextLong(); this.broadcastResponce = ByteBuffer .allocate(NETCONSTANTS.BROADCAST_PACKET_LENGTH); this.recoRequests = new ConcurrentLinkedQueue<ReconnectRequestWrapper>(); this.leaverStack = new ConcurrentLinkedQueue<LeaverDataWrapper>(); // set flags this.blockReconnector = blockReconnector; this.blockNewconncector = blockNewconnector; this.noPlayerLimit = maxPlayer < 1; // to work with this.joinRequests = new ConcurrentLinkedQueue<JoinRequestWrapper>(); GDWServerLogger.logMSG("Server loaded"); // start Threads this.iThread = new IncomingConnectionHandlerThread(this); GDWServerLogger.logMSG("TCP thread started on port " + this.iThread.getBoundPort()); this.tcpPort = this.iThread.getBoundPort(); updateBroadcastMessage(updatePart.ALL); this.bThread = new BroadcastresponseThread(this); try { this.mThread = new MulticastresponceThread(this); } catch (IOException e) { GDWServerLogger .logMSG("Es kann kein MulticastSocket erstellt werden, nicht weiter schlimm"); this.mThread = null; } GDWServerLogger.logMSG("UDP thread started on port " + this.bThread.getBoundedPort()); } private static enum updatePart { ALL, CUR_PLAYER, MAX_PLAYER, INFO_TEXT, TCP_PORT, SERVER_ID, CUSTOM_ATTACHMENT } final int CUR_PLAYER_POS = 0; final int MAX_PLAYER_POS = CUR_PLAYER_POS + Integer.SIZE; final int TCP_PORT_POS = MAX_PLAYER_POS + Integer.SIZE; final int SERVER_ID_POS = TCP_PORT_POS + Integer.SIZE; final int INFO_TEXT_POS = SERVER_ID_POS + Long.SIZE; protected void updateBroadcastMessage(updatePart part) { switch (part) { case ALL: { this.broadcastResponce.position(0); this.broadcastResponce.putInt(this.currentConnections); this.broadcastResponce.putInt(this.maxPlayer); this.broadcastResponce.putInt(this.tcpPort); this.broadcastResponce.putLong(this.serverId); byte[] arr = this.infoText.getBytes(DefaultCharSet .getDefaultCharset()); this.broadcastResponce.put((byte) arr.length); this.broadcastResponce.put(arr); } break; case CUR_PLAYER: { this.broadcastResponce.position(CUR_PLAYER_POS); this.broadcastResponce.putInt(this.currentConnections); } break; case MAX_PLAYER: { this.broadcastResponce.position(MAX_PLAYER_POS); this.broadcastResponce.putInt(this.maxPlayer); } break; case INFO_TEXT: { this.broadcastResponce.position(INFO_TEXT_POS); byte[] arr = this.infoText.getBytes(DefaultCharSet .getDefaultCharset()); this.broadcastResponce.put((byte) arr.length); this.broadcastResponce.put(arr); //net to add custom attachment if(this.broadcastResponceAttachment == null) { this.broadcastResponce.limit(this.broadcastResponceAttachment.position()); return; } this.broadcastResponce.put(this.broadcastResponceAttachment); this.broadcastResponce.limit(this.broadcastResponce.position()); } break; case TCP_PORT: { this.broadcastResponce.position(TCP_PORT_POS); this.broadcastResponce.putInt(this.tcpPort); } break; case SERVER_ID: { this.broadcastResponce.position(SERVER_ID_POS); this.broadcastResponce.putLong(this.serverId); } break; case CUSTOM_ATTACHMENT: { //get position to write on int pos = INFO_TEXT_POS + 1 + this.infoText.getBytes(DefaultCharSet.getDefaultCharset()).length; // postion from infotext + size info of followed bytes + byte for text if(this.broadcastResponceAttachment == null) { this.broadcastResponce.limit(pos); return; } if((pos - this.broadcastResponce.capacity()) < this.broadcastResponceAttachment.limit()) { throw new IndexOutOfBoundsException("Your attachment is to big to fit in message length of broadcastresponceMessage"); } this.broadcastResponce.position(pos); this.broadcastResponce.put(this.broadcastResponceAttachment); }break; default: break; } this.broadcastResponce.position(0); } protected void addJoinRequest(ConnectionInfo info, ByteBuffer data) { if(blockNewconncector) { sendErrorCodeToRequestAndClose(RESPONCECODES.CONNECT_REFUSE, info); }else { if((!noPlayerLimit)&&(this.currentConnections == maxPlayer)) { sendErrorCodeToRequestAndClose(RESPONCECODES.SERVER_FULL, info); }else { joinRequests.add(new JoinRequestWrapper(info,data)); } } } protected void addReconnectRequest(ConnectionInfo info) { if(blockReconnector) { sendErrorCodeToRequestAndClose(RESPONCECODES.CONNECT_REFUSE, info); }else { if((!noPlayerLimit)&&(this.currentConnections == maxPlayer)) { sendErrorCodeToRequestAndClose(RESPONCECODES.SERVER_FULL, info); }else { recoRequests.add(new ReconnectRequestWrapper(info)); } } } public int getAmountOfConnections() { return currentConnections; } private void proccedReconnecor() { //handle while(!this.recoRequests.isEmpty()) { ReconnectRequestWrapper rec = this.recoRequests.poll(); LeaverDataWrapper found = null; Iterator<LeaverDataWrapper> iter = this.leaverStack.iterator(); while (iter.hasNext()) { LeaverDataWrapper temp = iter.next(); if(temp.compareWithReco(rec)) { found = temp; break; } } //you send me shit! if(found == null) { sendErrorCodeToRequestAndClose(RESPONCECODES.DATA_CORRUPTED, rec.info); }else { found.client.revive(rec.info); sendOKAndDataToRequest(rec.info,rec.info.id); //highlevel playerReconnected(found.client); //low level this.currentConnections++; this.leaverStack.remove(found); } } this.updateBroadcastMessage(updatePart.CUR_PLAYER); } private void proccedJoinerRequests() { { //send to all that is full while(!this.joinRequests.isEmpty()) { JoinRequestWrapper req = this.joinRequests.poll(); //highlevel BasicClientConnection client = incomingConnection(req.info, req.data); if(client != null) { //lowlevel ++this.currentConnections; client.id = this.idCounter++; this.clientConnections.put(client.getId(),client); sendOKAndDataToRequest(req.info,client.id); } } this.updateBroadcastMessage(updatePart.CUR_PLAYER); } } private void forgetOldLeaver() { long ttl = System.currentTimeMillis() + NETCONSTANTS.DISPOSE_LEAVERDATA_TTL; while (!this.leaverStack.isEmpty()) { if(this.leaverStack.peek().timestamp > ttl) { this.leaverStack.remove(); }else { break; } } } private void proccedIncoming() { //handle reconnector and leaver cleanup if(!blockReconnector) { if(!this.recoRequests.isEmpty()) { proccedReconnecor(); } if(!this.leaverStack.isEmpty()) { forgetOldLeaver(); } }else { this.joinRequests.clear(); } //handle joinrequests if((!blockNewconncector)&&(!this.joinRequests.isEmpty())) { proccedJoinerRequests(); }else { this.joinRequests.clear(); } } protected void sendErrorCodeToRequestAndClose(byte code, ConnectionInfo info) { sendErrorCodeToRequest(code, info.tcpConnection); info.closeOpenSockets(); } private void sendErrorCodeToRequest(byte code, SocketChannel socket) { ByteBuffer send = ByteBuffer.allocate(1); send.put(code); send.flip(); try { socket.write(send); } catch (IOException e) { } } private void sendOKAndDataToRequest(ConnectionInfo info, int id) { ByteBuffer buf = ByteBuffer.allocate(Byte.SIZE+Integer.SIZE+Integer.SIZE+Integer.SIZE); buf.put(RESPONCECODES.OK); buf.putInt(info.udpConnection.socket().getLocalPort()); buf.putInt(id); buf.putInt(info.sharedSecret); buf.flip(); try { info.tcpConnection.write(buf); } catch (IOException e) { info.closeOpenSockets(); } } public void shutMeDown() { if (this.mThread != null) this.mThread.interrupt(); this.bThread.interrupt(); this.iThread.interrupt(); this.leaverStack.clear(); clearAllRequests(); disconnetAll(); } private void disconnetAll() { this.currentConnections = 0; Iterator<Integer> iter = this.clientConnections.keySet().iterator(); while (iter.hasNext()) { this.clientConnections.get(iter.next()).disconnect(); } this.clientConnections.clear(); this.idCounter = 0; } private void clearAllRequests() { while(!this.joinRequests.isEmpty()) { sendErrorCodeToRequestAndClose(RESPONCECODES.CONNECT_REFUSE, this.joinRequests.poll().info); } while(!this.recoRequests.isEmpty()) { sendErrorCodeToRequestAndClose(RESPONCECODES.CONNECT_REFUSE, this.recoRequests.poll().info); } } private void clearRecoRequests() { while(!this.recoRequests.isEmpty()) { sendErrorCodeToRequestAndClose(RESPONCECODES.CONNECT_REFUSE, this.recoRequests.poll().info); } } private void clearJoinRequest() { while(!this.joinRequests.isEmpty()) { sendErrorCodeToRequestAndClose(RESPONCECODES.CONNECT_REFUSE, this.joinRequests.poll().info); } } public ByteBuffer getMessageBuffer() { ByteBuffer buf = ByteBuffer.allocate(NETCONSTANTS.PACKAGELENGTH); buf.put(NETCONSTANTS.MESSAGE); return buf; } public void proccedInputData() { this.proccedIncoming(); Iterator<Integer> iter = this.clientConnections.keySet().iterator(); LinkedList<Integer> toLeave = new LinkedList<Integer>(); while (iter.hasNext()) { Integer pos = iter.next(); BasicClientConnection client = this.clientConnections.get(pos); if (client.checkForDisconnect(System.currentTimeMillis())) { toLeave.add(pos); } } while (!toLeave.isEmpty()) { disconnectPlayer(this.clientConnections.get(toLeave.pop())); } iter = this.clientConnections.keySet().iterator(); while (iter.hasNext()) { this.clientConnections.get(iter.next()).pollInput(); } } protected abstract void playerDisconnected(BasicClientConnection client); protected abstract void playerReconnected(BasicClientConnection client); protected abstract BasicClientConnection incomingConnection( ConnectionInfo info, ByteBuffer data); private void disconnectPlayer(BasicClientConnection client) { client.disconnect(); playerDisconnected(client); //should be saved for reconnect if(!blockReconnector) { this.leaverStack.add(new LeaverDataWrapper(client)); }else { this.clientConnections.remove(client.id); } --this.currentConnections; } public void sendToAll(ByteBuffer buf, boolean reliable) { Iterator<Integer> iter = this.clientConnections.keySet().iterator(); while (iter.hasNext()) { BasicClientConnection client = this.clientConnections.get(iter.next()); if(!client.isDisconnectFlaged()) client.sendMSG(buf, reliable); } } public int getMaxPlayer() { return maxPlayer; } public void setMaxPlayer(int maxPlayer) { this.maxPlayer = maxPlayer; this.updateBroadcastMessage(updatePart.MAX_PLAYER); this.noPlayerLimit = maxPlayer <1; } public boolean isBlockReconnector() { return blockReconnector; } public void setBlockReconnector(boolean blockReconnector) { this.blockReconnector = blockReconnector; if(blockReconnector) { this.clearRecoRequests(); this.leaverStack.clear(); } } public boolean isBlockNewconncector() { return blockNewconncector; } public void setBlockNewconncector(boolean blockNewconncector) { this.blockNewconncector = blockNewconncector; if(blockNewconncector) { this.clearJoinRequest(); } } public long getServerId() { return serverId; } public boolean isNoPlayerLimit() { return noPlayerLimit; } public String getInfoText() { return infoText; } public void setInfoText(String infoText) { this.infoText = infoText; this.updateBroadcastMessage(updatePart.INFO_TEXT); } public ByteBuffer getBroadcastResponceAttachment() { return broadcastResponceAttachment; } /** * Adds the given ByteBuffer on broadcast Messages. Note the ByteBuffer will rewinded in this * methode. Null will be delete the attachment. * * @param broadcastResponceAttachment the ByteBuffer to attache on brodcast messages * * @throws IndexOutOfBoundsException When given Buffer ist too big to fit in. */ public void setBroadcastResponceAttachment( ByteBuffer broadcastResponceAttachment) { broadcastResponceAttachment.flip(); this.broadcastResponceAttachment = broadcastResponceAttachment; try { this.updateBroadcastMessage(updatePart.CUSTOM_ATTACHMENT); }catch (IndexOutOfBoundsException e) { this.broadcastResponceAttachment = null; this.updateBroadcastMessage(updatePart.CUSTOM_ATTACHMENT); throw e; } } public BasicClientConnection getPlayerByID(int id) { return this.clientConnections.get(id); } /** * Finger weg von der Methode und fasst den broadcastResponce nicht direkt * an. Siehst du das ist druchgestrichen!!! * * Dont use this methode or broadcastResponce. * * @return */ @Deprecated protected ByteBuffer getBroadcastResponce() { return broadcastResponce; } }