*/
WSYD_SocketAddress _socketAddress;
+ protected ServiceAddressMap _serviceToHostMap;
/**
* Thread safe First In, First Out Queue of NetworkMessage objects waiting to be sent.
*
*/
protected ConcurrentLinkedQueue<NetworkMessage> _sendMessageQueue = new ConcurrentLinkedQueue<>();
- /**
- * Encapsulates a unique network host and the last time it was seen.
- */
- protected class LastSeenHost {
- final long timeInMillis;
- final InetSocketAddress address;
-
- LastSeenHost(InetSocketAddress address, long timeInMillis) {
- this.address = address;
- this.timeInMillis = timeInMillis;
- }
- LastSeenHost(InetSocketAddress host) {
- this(host, System.currentTimeMillis());
- }
-
- /**
- * Formatted string representation of IneAddress and timestamp.
- * @return the representation
- */
- @Override
- public String toString() {
- return MessageFormat.format("{0}:{1,number,integer}@{2}", this.address.getHostString(), this.address.getPort(), this.timeInMillis);
- }
- };
- /**
- * Maps service _title to its parent network host.
- * <p>
- * Used by methods on the Owner Thread to determine the list of valid service
- * names it can submit messages to (by iterating the keys using keySet()).</p>
- * <p>
- * New service names can be added in two ways:<br/>
- * <ol>
- * <li>by the Worker Thread from received messages</li>
- * <li>by the Owner or (other thread) from a service discovery helper (such as multicast discovery)</li>
- * </ol>
- */
- protected ConcurrentHashMap<String, LastSeenHost> _serviceToHostMap = new ConcurrentHashMap<>();;
-
/**
* Wrapper for filtering NetworkMessageEvents based on the message intent
*/
this._connectionCount = 0;
this._title = null;
this._socketAddress = null;
+ this._serviceToHostMap = null;
}
/**
*
* @param socketAddress The socket to listen on
* @param title source identifier for use in log messages and sent NetworkMessage objects
+ * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
* @param logger An instance of Logger to be used by all objects of this class
*/
- public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title, Logger logger) {
+ public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap, Logger logger) {
this._connectionCount = 0;
this._title = title;
this._socketAddress = socketAddress;
+ this._serviceToHostMap = serviceToHostMap;
if (LOGGER == null) // do not replace existing logger reference
LOGGER = logger;
}
*
* @param socketAddress The socket to listen on
* @param title source identifier for use in log messages and sent NetworkMessage objects
+ * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
*/
- public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title) {
- this(socketAddress, title, null);
+ public NetworkServerAbstract(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap) {
+ this(socketAddress, title, serviceToHostMap, null);
}
/**
@Override
protected abstract void done();
-
- /**
- * Ensure service is in the map of known hosts.
- * @param service the service name to check
- * @return true is the target service is known
- */
- protected boolean isServiceValid(String service) {
- return this._serviceToHostMap.containsKey(service);
- }
-
/**
* Adds a message to the queue of pending messages.
*
String target = message.getTarget();
if (target == null)
throw new IllegalArgumentException("target cannot be null");
- if(!isServiceValid(target))
+ if(!_serviceToHostMap.isServiceValid(target))
throw new IllegalArgumentException("target service does not exist: " + target);
NetworkMessage temp;
return result;
}
- /**
- * Get the current InetAddress of a target from the services map.
- *
- * @param target name of the service
- * @return IP address and port of the service
- */
- public InetSocketAddress getTargetAddress(String target) {
- InetSocketAddress result = null;
-
- if (target != null && target.length() > 0) {
- LastSeenHost host = this._serviceToHostMap.get(target);
- if (host != null)
- result = host.address;
- }
-
- return result;
- }
-
/**
* Add a NetworkMessageEvent listener.
*
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import uk.ac.ntu.n0521366.wsyd.libs.message.MessageLogRecord;
+import uk.ac.ntu.n0521366.wsyd.libs.net.ServiceAddressMap;
*
* @param socketAddress The socket to listen on
* @param title source identifier for use in log messages and sent NetworkMessage objects
+ * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
* @param logger An instance of Logger to be used by all objects of this class
*/
- public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title, Logger logger) {
- super(socketAddress, title, logger);
+ public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap, Logger logger) {
+ super(socketAddress, title, serviceToHostMap, logger);
}
/**
*
* @param socketAddress The socket to listen on
* @param title source identifier for use in log messages and sent NetworkMessage objects
+ * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
*/
- public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title) {
- super(socketAddress, title);
+ public NetworkServerUDP(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap) {
+ super(socketAddress, title, serviceToHostMap);
}
/**
if (!messageReceived.getSender().equals(_title)) {
// add or update the last-seen time of the Sender host in the known services map
- LastSeenHost host = new LastSeenHost((InetSocketAddress)packetReceive.getSocketAddress());
+ ServiceAddressMap.LastSeenHost host = new ServiceAddressMap.LastSeenHost((InetSocketAddress)packetReceive.getSocketAddress());
this._serviceToHostMap.put(messageReceived.getSender(), host);
log(Level.FINEST, _title, MessageFormat.format("Added \"{0}\" to service map", messageReceived.getSender()));
boolean result = false;
if (message != null) {
- LastSeenHost host = _serviceToHostMap.get(message.getTarget());
+ ServiceAddressMap.LastSeenHost host = _serviceToHostMap.get(message.getTarget());
if (host != null) {
InetSocketAddress address = host.address;
if (address != null) {
import java.util.logging.Logger;
import java.util.ArrayList;
import java.util.Enumeration;
+import uk.ac.ntu.n0521366.wsyd.libs.net.ServiceAddressMap;
/**
*
*
* @param socketAddress The socket to listen on
* @param title source identifier for use in log messages and sent NetworkMessage objects
+ * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
* @param logger An instance of Logger to be used by all objects of this class
*/
- public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title, Logger logger) {
- super(socketAddress, title, logger);
+ public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap, Logger logger) {
+ super(socketAddress, title, serviceToHostMap, logger);
// permit broadcasting to pseudo-host 'all' since this is multicast
- this._serviceToHostMap.put("all", new LastSeenHost(socketAddress.getSocketAddress()));
+ this._serviceToHostMap.put("all", new ServiceAddressMap.LastSeenHost(socketAddress.getSocketAddress()));
}
/**
*
* @param socketAddress The socket to listen on
* @param title source identifier for use in log messages and sent NetworkMessage objects
+ * @param serviceToHostMap the map object used for host <> InetSocketAddress lookups
*/
- public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title) {
- super(socketAddress, title);
+ public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title, ServiceAddressMap serviceToHostMap) {
+ super(socketAddress, title, serviceToHostMap);
}
/**
}
}
-
- /**
- * Remove stale service records.
- *
- * @param ageInMillis milliseconds since last seen to be considered stale
- * @return quantity of records removed
- */
- public ArrayList<String> cleanServiceToHostMap(long ageInMillis) {
- ArrayList<String> result = new ArrayList<>();
- long expireTime = System.currentTimeMillis() - ageInMillis;
- java.util.Enumeration<String> keys = this._serviceToHostMap.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
-
- // XXX: special handling for "all" target - never remove it
- if (!key.equals("all")) {
- LastSeenHost host = _serviceToHostMap.get(key);
- if (host != null) {
- if (host.timeInMillis < expireTime) {
- if (_serviceToHostMap.remove(key, host)) {
- result.add(key);
- ArrayList<String> messages = new ArrayList<>();
- messages.add(key);
- log(Level.INFO, _title, "Removed \"{0}\" from service map", messages);
- }
- }
- }
- }
- }
- return result;
- }
-
}
--- /dev/null
+/*
+ * The MIT License
+ *
+ * Copyright 2015 TJ <hacker@iamtj>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package uk.ac.ntu.n0521366.wsyd.libs.net;
+
+import java.net.InetSocketAddress;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Maps service names on hosts to an InetAddress:Port pair.
+ *
+ * @author TJ <hacker@iamtj>
+ */
+public class ServiceAddressMap {
+ /**
+ * Multi-thread safe map keyed on the service name.
+ */
+ protected ConcurrentHashMap<String, LastSeenHost> _serviceToAddressMap = new ConcurrentHashMap<>();
+
+ /**
+ * Encapsulates a unique network host and the last time it was seen.
+ */
+ public static class LastSeenHost {
+ /**
+ * State of a record:
+ *
+ * STATIC = was manually entered and should not be removed
+ * DYNAMIC = was discovered via multicast announcement or new connection and can be removed
+ */
+ public static enum STATE {STATIC, DYNAMIC}
+
+ final STATE state;
+ final long timeInMillis;
+ final InetSocketAddress address;
+
+ /**
+ * Constructs a new instance of a host.
+ *
+ * @param address The current address:port
+ * @param timeInMillis time last seen
+ * @param state whether record was added through dynamic discovery
+ */
+ LastSeenHost(InetSocketAddress address, long timeInMillis, STATE state) {
+ this.address = address;
+ this.timeInMillis = timeInMillis;
+ this.state = state;
+ }
+
+ /**
+ * Construct a new instance of a dynamically announced host.
+ *
+ * @param host
+ */
+ LastSeenHost(InetSocketAddress host) {
+ this(host, System.currentTimeMillis(), STATE.DYNAMIC);
+ }
+
+ /**
+ * Formatted string representation of InetAddress and timestamp.
+ * @return the representation
+ */
+ @Override
+ public String toString() {
+ return MessageFormat.format("{0}:{1,number,integer}@{2}", this.address.getHostString(), this.address.getPort(), this.timeInMillis);
+ }
+ };
+
+ /**
+ * The Logger to use.
+ */
+ private static Logger LOGGER = null;
+
+ /**
+ * Facility name for logger
+ */
+ private String _facility = null;
+
+ /**
+ * Construct a map with a Logger.
+ *
+ * @param facility The log facility to tag messages with
+ * @param logger The Logger
+ */
+ public ServiceAddressMap(String facility, Logger logger) {
+ this._facility = facility;
+ LOGGER = logger;
+ }
+
+ /**
+ * Log some message.
+ * @param level
+ * @param message
+ */
+ private void log(Level level, String message) {
+ if (LOGGER == null)
+ return;
+ LOGGER.logp(level, _facility, null, message);
+
+ }
+ /**
+ * Remove stale service records.
+ *
+ * @param ageInMillis milliseconds since last seen to be considered stale
+ * @return quantity of records removed
+ */
+ public ArrayList<String> cleanServiceAddressMap(long ageInMillis) {
+ ArrayList<String> result = new ArrayList<>();
+ long expireTime = System.currentTimeMillis() - ageInMillis;
+ java.util.Enumeration<String> keys = this._serviceToAddressMap.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+
+ // XXX: special handling for "all" target - never remove it
+ LastSeenHost host = _serviceToAddressMap.get(key);
+ if (host != null && host.state == LastSeenHost.STATE.DYNAMIC) {
+ if (host.timeInMillis < expireTime) {
+ if (_serviceToAddressMap.remove(key, host)) {
+ result.add(key);
+ log(Level.INFO, MessageFormat.format("Removed \"{0}\" from service map", key));
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Ensure service is in the map of known hosts.
+ * @param service the service name to check
+ * @return true is the target service is known
+ */
+ protected boolean isServiceValid(String service) {
+ return this._serviceToAddressMap.containsKey(service);
+ }
+
+ /**
+ * Get the current InetSocketAddress of a target.
+ *
+ * @param target name of the service
+ * @return IP address and port of the service
+ */
+ public InetSocketAddress getServiceAddress(String target) {
+ InetSocketAddress result = null;
+
+ if (target != null && target.length() > 0) {
+ LastSeenHost host = this._serviceToAddressMap.get(target);
+ if (host != null)
+ result = host.address;
+ }
+
+ return result;
+ }
+
+ public void put(String service, LastSeenHost host) {
+ this._serviceToAddressMap.put(service, host);
+ }
+
+ public LastSeenHost get(String service) {
+ return this._serviceToAddressMap.get(service);
+ }
+}
import javax.swing.ImageIcon;
import java.awt.Desktop;
import java.net.URI;
+import java.net.InetAddress;
import java.io.IOException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* class-wide logger
*/
- static final Logger LOGGER = Logger.getLogger("ServerManagement");
+ Logger LOGGER = null;
/**
* Location of resource bundles, images, icons
/**
* Readable/displayable name of this application
*/
- String _title;
+ final String _title = "ServerManagement";
+ ServiceAddressMap _serviceToAddressMap;
/**
* The UDP listener address for incoming log messages
*/
* Multi-cast neighbour advertise and discover service in a SwingWorker thread.
*/
NetworkServerUDPMulticast _multicastServer = null;
-
+
+ boolean _multicastAnnouncements = false;
/**
* Enable or suspend logging
* @see #initListeners()
*/
public ServerManagement() {
- _title = "ServerManagement";
+ if (LOGGER == null) // single instance of the Logger shared by all class objects
+ LOGGER = Logger.getLogger(_title);
LOGGER.setLevel(Level.ALL);
+ _serviceToAddressMap = new ServiceAddressMap(_title, LOGGER);
initComponents();
_doLogging = true;
_autoScroll = true;
gDialogAbout.getContentPane().setBackground(gDialogAbout.getBackground());
}
+ /**
+ * Creates new Server Management GUI that also announces itself using Multicast Neighbour discovery.
+ * @param multicastAnnouncements true if it should announce itself
+ * @param serverSocial IP address of ServerSocial - if non-null prevents ServerSocial map entry being treated as stale
+ */
+ public ServerManagement(boolean multicastAnnouncements, InetAddress serverSocial) {
+ this();
+ this._multicastAnnouncements = multicastAnnouncements;
+ // TODO: implement constructor setting IP address of SocialServer from command-line value - needs ServiceToHostMap separating from NetworkServerAbstract and do not allow manually added service map element being treated as stale
+ }
/**
* Initialise listeners and other objects that require a reference to 'this'.
*
LOGGER.log(Level.INFO, "Server Management starting");
_logServerSA = new WSYD_SocketAddress(Network.PORTS_SERVER_LOG, Protocol.UDP);
- _logServer = new NetworkServerUDP(_logServerSA, "ServerLog", LOGGER);
+ _logServer = new NetworkServerUDP(_logServerSA, _title, _serviceToAddressMap, LOGGER);
_logServer.addNetworkMessageEventListener(this, "Log");
_logServer.setSimulate(false);
_logServer.execute();
+
_multicastAdvertiserSA = new WSYD_SocketAddress(Network.MULTICAST_IP, Network.PORTS_MULTICAST_DISCOVERY, Protocol.UDP);
- _multicastServer = new NetworkServerUDPMulticast(_multicastAdvertiserSA, "ServerLogMC", LOGGER);
+ _multicastServer = new NetworkServerUDPMulticast(_multicastAdvertiserSA, "ServerManagementMC", _serviceToAddressMap, LOGGER);
_multicastServer.addNetworkMessageEventListener(this, "Neighbour");
_multicastServer.execute();
-
- ActionListener multicastAnnounceActionListener = new ActionListener() {
- /**
- * Activated by timer events to send multi-cast neighbour announcements for the Log Service.
- * @param e
- */
- @Override
- public void actionPerformed(ActionEvent e) {
- // Create local log report first
- LogRecord record = new LogRecord(Level.FINEST, "Multicast: Announcing Presence");
- record.setSourceClassName("ServerLog");
- record.setMillis(System.currentTimeMillis());
- LOGGER.log(record);
-
- // Announce the Log Server service
- MessagePresence mp = new MessagePresence("ServerLog", Network.PORTS_SERVER_LOG);
- NetworkMessage nm = NetworkMessage.createNetworkMessage("Neighbour", "all", mp);
- nm.setSender("ServerLog");
- _multicastServer.queueMessage(nm);
-
- // clean up the known hosts map and keep Server menu up-to-date
- ArrayList<String> servicesRemoved = _multicastServer.cleanServiceToHostMap(5000);
- for (String service: servicesRemoved) {
+
+ if (this._multicastAnnouncements) {
+ ActionListener multicastAnnounceActionListener = new ActionListener() {
+ /**
+ * Activated by timer events to send multi-cast neighbour
+ * announcements for the Log Service.
+ *
+ * @param e
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // Create local log report first
+ LogRecord record = new LogRecord(Level.FINEST, "Multicast: Announcing Presence");
+ record.setSourceClassName("ServerLog");
+ record.setMillis(System.currentTimeMillis());
+ LOGGER.log(record);
+
+ // Announce the Log Server service
+ MessagePresence mp = new MessagePresence("ServerLog", Network.PORTS_SERVER_LOG);
+ NetworkMessage nm = NetworkMessage.createNetworkMessage("Neighbour", "all", mp);
+ nm.setSender("ServerLog");
+ _multicastServer.queueMessage(nm);
+
+ // clean up the known hosts map and keep Server menu up-to-date
+ ArrayList<String> servicesRemoved = _serviceToAddressMap.cleanServiceAddressMap(5000);
+ for (String service : servicesRemoved) {
switch (service) {
case "ServerSocial":
gMenuServerSocial.setEnabled(false);
break;
case "ServerChat":
gMenuServerChat.setEnabled(false);
- break;
+ break;
}
- }
+ }
- }
- };
- multicastAnnounce = new Timer(1000, multicastAnnounceActionListener);
- multicastAnnounce.setInitialDelay(100);
- multicastAnnounce.start();
+ }
+ };
+ multicastAnnounce = new Timer(1000, multicastAnnounceActionListener);
+ multicastAnnounce.setInitialDelay(100);
+ multicastAnnounce.start();
+ }
return this;
}
boolean result = false;
if (message != null) {
- InetSocketAddress address = this._multicastServer.getTargetAddress(message.getTarget());
+ InetSocketAddress address = this._serviceToAddressMap.getServiceAddress(message.getTarget());
if (address != null) {
message.setSender("ServerManagement");
try {
DatagramSocket socket = new DatagramSocket();
// acknowledge receipt
socket.send(packetSend);
- System.err.println(MessageFormat.format("Sending packet for {0} to {1} ({3}:{4}) from {2}", message.getIntent(), message.getTarget(), message.getSender(), packetSend.getAddress().getHostAddress(), packetSend.getPort()));
+ LOGGER.logp(Level.FINEST, _title, null, MessageFormat.format("Sending packet for {0} to {1} ({3}:{4,number,integer}) from {2}", message.getIntent(), message.getTarget(), message.getSender(), packetSend.getAddress().getHostAddress(), packetSend.getPort()));
result = true; // successful
} catch (IOException e) {
private void gMenuServerSocialRestartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuServerSocialRestartActionPerformed
// TODO add your handling code here:
- System.err.println("Requesting ServerSocial Restart");
+ LOGGER.logp(Level.FINEST, _title, null, "Requesting ServerSocial Restart");
MessageServerControl mc = new MessageServerControl(MessageServerControl.EXIT.NO , MessageServerControl.RESTART.YES);
- NetworkMessage nm = NetworkMessage.createNetworkMessage("Control", "ServerSocial", mc);
- nm.setSender("ServerLog");
+ NetworkMessage nm = NetworkMessage.createNetworkMessage("Control", "ServerSocialControl", mc);
+ nm.setSender(_title);
UDPSend(nm);
this.isLoggable(m.record);
break;
case "Neighbour":
- // TODO: NetworkMessageReceived(): Test Multicast message received handler to enable menu items for recognised servers
String type = nm.getMessage().getMessageType();
if (type.equals(MessagePresence.getType())) { // Presence
MessagePresence mp = (MessagePresence)nm.getMessage();
//</editor-fold>
//</editor-fold>
//</editor-fold>
-
/* Create and display the form */
- java.awt.EventQueue.invokeLater(new Runnable() {
+ class App implements Runnable {
+ public boolean multicastAnnouncements = false;
+ public InetAddress serverSocial = null;
@Override
public void run() {
try {
- new ServerManagement().initListeners().setVisible(true);
+ new ServerManagement(multicastAnnouncements, serverSocial).initListeners().setVisible(true);
}
catch(UnknownHostException e) {
System.err.println("Error: cannot create log server listener socket");
}
}
- });
+ }
+ App app = new App();
+
+ // process command line arguments
+ for (int i = 0; i < args.length; i++) {
+ switch (args[i]) {
+ case "-announce":
+ app.multicastAnnouncements = true;
+ break;
+ case "-server":
+ if (args.length >= i+1) {
+ // read the next argument as an IP address
+ InetAddress temp;
+ try {
+ temp = InetAddress.getByName(args[i+1]);
+ app.serverSocial = temp;
+ } catch (UnknownHostException e) {
+ System.err.println(MessageFormat.format("Error: {0} is not a valid hostname or IP address", args[i+1]));
+ }
+ }
+ break;
+ }
+ }
+ java.awt.EventQueue.invokeLater(app);
}
// Variables declaration - do not modify//GEN-BEGIN:variables