/** * Copyright (C) 2002-2012 The FreeCol Team * * This file is part of FreeCol. * * FreeCol is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * FreeCol is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with FreeCol. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.freecol.server.networking; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Iterator; import java.util.logging.Logger; import net.sf.freecol.FreeCol; import net.sf.freecol.common.networking.Connection; import net.sf.freecol.common.networking.MessageHandler; import net.sf.freecol.server.FreeColServer; import org.w3c.dom.Element; import android.util.Log; /** * The networking server in which new clients can connect and methods * like <code>sendToAll</code> are kept. * * <br><br> * * When a new client connects to the server a new {@link Connection} * is made, with {@link net.sf.freecol.server.control.UserConnectionHandler} * as the control object. * * @see net.sf.freecol.common.networking */ public final class Server extends Thread { private static Logger logger = Logger.getLogger(Server.class.getName()); /** The public "well-known" socket to which clients may connect. */ private ServerSocket serverSocket; /** A hash of Connection objects, keyed by the Socket they relate to. */ private HashMap<Socket, Connection> connections = new HashMap<Socket, Connection>(); /** * Whether to keep running the main loop that is awaiting new * client connections. */ private boolean running = true; /** The owner of this <code>Server</code>. */ private FreeColServer freeColServer; /** The TCP port that is beeing used for the public socket. */ private int port; /** For information about this variable see the run method. */ private final Object shutdownLock = new Object(); /** * Creates a new network server. Use {@link #run server.start()} to start * listening for new connections. * * @param freeColServer The owner of this <code>Server</code>. * @param port The TCP port to use for the public socket. * @throws IOException if the public socket cannot be created. */ public Server(FreeColServer freeColServer, int port) throws IOException { super(FreeCol.SERVER_THREAD+"Server"); this.freeColServer = freeColServer; this.port = port; //serverSocket = new ServerSocket(port, freeColServer.getMaximumPlayers()); serverSocket = new ServerSocket(port); } /** * Starts the thread's processing. Contains the loop that is * waiting for new connections to the public socket. When a new * client connects to the server a new {@link Connection} is made, * with {@link net.sf.freecol.server.control.UserConnectionHandler} * as the control object. */ public void run() { // This method's entire body is synchronized to shutdownLock. // The reason why this is done is to prevent the shutdown // method from finishing before this thread is finished // working. We have to do this because the // ServerSocket::close method keeps the server alive for // several milliseconds EVEN AFTER THE CLOSE METHOD IS // FINISHED. And because of this a new server can't be created // on the same port as this server right after closing this // server. // // Now that the shutdown method 'hangs' until the entire // server thread is finished you can be certain that the // ServerSocket is REALLY closed after execution of shutdown. synchronized (shutdownLock) { while (running) { Socket clientSocket = null; try { clientSocket = serverSocket.accept(); logger.info("Got client connection from " + clientSocket.getInetAddress().toString()); Log.d("Server", "Client connected: " + clientSocket.getInetAddress().toString()); //Connection connection = new Connection(clientSocket, freeColServer.getUserConnectionHandler(), FreeCol.SERVER_THREAD); //connections.put(clientSocket, connection); } catch (IOException e) { if (running) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); logger.warning(sw.toString()); } } } } } /** * Sends a network message to all connections except * <code>exceptConnection</code> (if the argument is non-null). * * @param element The root element of the message to send. * @param exceptConnection If non-null, the * <code>Connection</code> not to send to. */ public void sendToAll(Element element, Connection exceptConnection) { Iterator<Connection> connectionIterator = getConnectionIterator(); while (connectionIterator.hasNext()) { Connection connection = connectionIterator.next(); if (connection != exceptConnection) { try { connection.sendAndWait(element); } catch (IOException e) { logger.warning("Exception while attempting to send to " + connection); } } } } /** * Sends a network message to all connections. * * @param element The root element of the message to send. */ public void sendToAll(Element element) { sendToAll(element, null); } /** * Gets the TCP port that is beeing used for the public socket. * * @return The TCP port. */ public int getPort() { return port; } /** * Sets the specified <code>MessageHandler</code> to all connections. * * @param messageHandler The <code>MessageHandler</code>. */ public void setMessageHandlerToAllConnections(MessageHandler messageHandler) { Iterator<Connection> connectionIterator = getConnectionIterator(); while (connectionIterator.hasNext()) { Connection connection = connectionIterator.next(); connection.setMessageHandler(messageHandler); } } /** * Gets an iterator of every connection to this server. * * @return The <code>Iterator</code>. * @see Connection */ public Iterator<Connection> getConnectionIterator() { return connections.values().iterator(); } /** * Shuts down the server thread. */ public void shutdown() { running = false; try { serverSocket.close(); logger.fine("Closed server socket."); } catch (IOException e) { logger.warning("Could not close the server socket!"); } synchronized (shutdownLock) { // Nothing to do here... just waiting for the server // thread to finish. For more info see the run() method } Iterator<Connection> connectionsIterator = getConnectionIterator(); while (connectionsIterator.hasNext()) { Connection c = connectionsIterator.next(); try { if (c != null) { //c.reallyClose(); c.close(); } } catch (IOException e) { logger.warning("Could not close the connection."); } } freeColServer.removeFromMetaServer(); logger.fine("Server shutdown."); } /** * Gets a <code>Connection</code> identified by a <code>Socket</code>. * * @param socket The <code>Socket</code> that identifies the * <code>Connection</code> * @return The <code>Connection</code>. */ public Connection getConnection(Socket socket) { return connections.get(socket); } /** * Adds a (usually Dummy)Connection into the hashmap. * * @param connection The connection to add. */ public void addDummyConnection(Connection connection) { connections.put(new Socket(), connection); } /** * Adds a Connection into the hashmap. * * @param connection The connection to add. */ public void addConnection(Connection connection) { connections.put(connection.getSocket(), connection); } /** * Removes the given connection. * * @param connection The connection that should be removed. */ public void removeConnection(Connection connection) { connections.remove(connection.getSocket()); } }