/** * This software is GPLv2. * Take a look at the LICENSE file for more info. */ package de.tu.dresden.dud.dc; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.Iterator; import java.util.LinkedList; import org.apache.log4j.Logger; import de.tu.dresden.dud.dc.InfoService.InfoService; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageAccepted4Service; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageKThxBye; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageLeaveWorkCycle; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageRegisterAtService; import de.tu.dresden.dud.dc.ManagementMessage.ManagementMessageWelcome2WorkCycle; import de.tu.dresden.dud.dc.WorkCycle.WorkCycle; import de.tu.dresden.dud.dc.WorkCycle.WorkCycleManager; /** * @author klobs * * Multi-threaded server. Ideas taken from * http://tutorials.jenkov.com/java-multithreaded-servers/multithreaded-server.html * * A multi-threaded server is chosen, instead of a thread-pooled, because normally we have * long duration connections. * */ public class Server implements Runnable { // Logging private static Logger log = Logger.getLogger(Server.class); private int symbollength = 1024; private LinkedList<Connection> aspConns = new LinkedList<Connection>(); private InfoService info = null; private boolean isStopped = false; private KeyExchangeManager keyExManager = new KeyExchangeManager(KeyExchangeManager.KEX_FULLY_AUTOMATIC); private ParticipantManager participantManager = new ParticipantManager(); private int port = 6867; private WorkCycleManager workCycleManager = null; private ServerSocket serverSocket = null; private int timeout = 0; /** * @param listenPort specifies the port on which the DC Service shall be provided. */ public Server(int listenPort, short keyGenerationMethod, short keyExchangeMethod, short individualMessageLengths) { this.port = listenPort; info = new InfoService(); info.setServer(this); workCycleManager = new WorkCycleManager( keyGenerationMethod, 0 /*Better: Long.MIN_VALUE */, symbollength, individualMessageLengths, WorkCycleManager.EARLY_QUIT_RESTART_WC); workCycleManager.setServer(this); workCycleManager.setAssocParticipantManager(participantManager); } public void accept4Service(Connection c, ManagementMessageRegisterAtService m){ if(! aspConns.contains(c)){ // QUESTION do we need aspirants? Probably not. log.info(c.getClientSocket().toString() + "Wanted to register at the service, but is not in the list of aspirants"); return; } // TODO install decision process (oracle) here, when new clients should be rejected. c.accept4Service(ManagementMessageAccepted4Service.ACCEPTED); aspConns.remove(c); // Associate the connection with a participant. Participant p = new Participant(m.getParticipantID(), m.getUsername(), m.getSignature(), m.getDHPublicPart(), m.getDHPublicSig()); c.setParticipant(p); c.setAssocParticipantManager(participantManager); c.setAssocWorkCycleManager(workCycleManager); participantManager.addParticipant(p); ParticipantMgmntInfo pmi = participantManager.getParticipantMgmntInfoFor(p); pmi.setAssocConnection(c); pmi.setPassive(true); } public void activateConnection(Connection c){ participantManager.setParticipantActive(c.getAssociatedParticipant()); this.workCycleManager.addExpectedConnection(c); c.setAssocWorkCycleManager(workCycleManager); } private void deactivateConnection(Connection c, long workcycle){ participantManager.unsetParticipantActiveAfterWorkCycle(c.getAssociatedParticipant(), workcycle); c.setExpectedLeavingWorkCycle(workcycle); this.workCycleManager.addLeavingConnection(c); } public LinkedList<Connection> getActiveConnections(){ LinkedList<Connection> c = new LinkedList<Connection>(); LinkedList<ParticipantMgmntInfo> p = participantManager.getActivePartMgmtInfo(); Iterator<ParticipantMgmntInfo> i = p.iterator(); ParticipantMgmntInfo pmi = null; while(i.hasNext()){ pmi = i.next(); c.add(pmi.getAssocConnection()); } return c; } /** * Aspirants are participants, that can gather information, but do * not take part in work cycles, yet. * @return a list of all waiting participants. */ public LinkedList<Connection> getAspirants(){ return this.aspConns; } /** * @return Returns the list with all connections of the running server */ public LinkedList<Connection> getConnections(){ LinkedList<Connection> c = new LinkedList<Connection>(); LinkedList<ParticipantMgmntInfo> p = participantManager.getPassivePartMgmtInfo(); Iterator<ParticipantMgmntInfo> i = p.iterator(); ParticipantMgmntInfo pmi = null; while(i.hasNext()){ pmi = i.next(); c.add(pmi.getAssocConnection()); } return c; } /** * * @return the one and only info service */ public InfoService getInfoService(){ return this.info; } public KeyExchangeManager getKeyExchangeManager(){ return keyExManager; } public ParticipantManager getParticipantManager(){ return participantManager; } public WorkCycleManager getWorkCycleManager(){ return workCycleManager; } /** * * @return Returns the status, whether server is running, or not. */ private boolean isStopped() { return this.isStopped; } /** * This method decides whether a certain connection is allowed to participate in work cycles. * @param c the {@link Connection} that belongs to the {@link Participant}, that wants to join the {@link WorkCycle}s. */ public void joinWorkCycleRequested(Connection c){ if (!(getConnections().contains(c))) { log.info(c.getClientSocket().toString() + "Wanted to join the work cycles, but is not in the list of passive connections. We can not allow that."); return; } // install accept / reject oracle here c.welcome2WorkCycle(ManagementMessageWelcome2WorkCycle.ACCEPTED, workCycleManager.getEntryWorkCycleNumber(), timeout); this.activateConnection(c); } /** * This method prepares everything on server side, to make a participant ready to leave a work cycle. * * @param c */ public void leaveWorkCycleRequested(Connection c, ManagementMessageLeaveWorkCycle m){ if (!(getActiveConnections().contains(c))) { log.info(c.getClientSocket().toString() + "Wanted to leave the work cycle, but is not in the list of active connections. We can not allow that."); return; } if (workCycleManager.getCurrentWorkCycleNumber() >= m.geWorkCycleNumber()){ log.info( c.getClientSocket().toString() + "Wanted to leave the work cycle, but it is too soon. We can not allow that."); return; } this.deactivateConnection(c, m.geWorkCycleNumber()); } private void openServerSocket(){ try { log.info("Opening ServerSocket on port " + String.valueOf(port)); this.serverSocket = new ServerSocket(this.port); } catch (IOException e) { log.error( "Could not bind to port " + String.valueOf(port)); log.error(e.toString()); } } public void quitServiceRequest(Connection c){ if (participantManager.getParticipantMgmntInfoFor(c) == null){ aspConns.remove(c); c.tellGoodByeFromService(ManagementMessageKThxBye.QUITOK_ALL_OK); return; } ParticipantMgmntInfo pmi = participantManager.getParticipantMgmntInfoFor(c); if (pmi.isActive()){ c.tellGoodByeFromService(ManagementMessageKThxBye.QUITOK_LEAVE_WC_FIRST); return; } // if participant is not active, it should be easy to remove him... if (!pmi.isActive()){ pmi = participantManager.getParticipantMgmntInfoFor(c); participantManager.removeParticipant(pmi); } c.tellGoodByeFromService(ManagementMessageKThxBye.QUITOK_ALL_OK); } /** * Start the server */ @Override public void run(){ openServerSocket(); while(! isStopped()){ Socket clientSocket; try { clientSocket = null; clientSocket = this.serverSocket.accept(); Connection c = new Connection(this, clientSocket, this); c.setAssocWorkCycleManager(workCycleManager); log.debug("New connection arrived from " + clientSocket.toString()); aspConns.add(c); new Thread(c, "connectionServerSide").start(); } catch (IOException e) { if(isStopped()) { log.info("Server Stopped: " + e.toString()); for(Connection d : getConnections()){ d.stop(true); } return; } throw new RuntimeException( "Error accepting client connection", e); } finally { } } log.info("Server Stopped."); } /** * Sets the symbol length for the server * @param symbolLength in bytes */ public void setSymbolLength(int symbolLength) { this.symbollength = symbolLength; } /** * stop the server */ public void stop(){ this.isStopped = true; try { this.serverSocket.close(); } catch (IOException e) { throw new RuntimeException("Error closing server", e); } } public String toString(){ return "server"; } }