4 * Copyright 2015 TJ <hacker@iam.tj>.
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 package uk.ac.ntu.n0521366.wsyd.libs.net;
26 import java.io.IOException;
27 import java.net.MulticastSocket;
28 import java.net.SocketException;
29 import java.net.NetworkInterface;
30 import java.util.logging.Level;
31 import java.util.logging.Logger;
32 import java.util.ArrayList;
33 import java.util.Enumeration;
37 * @author TJ <hacker@iam.tj>
39 public class NetworkServerUDPMulticast extends NetworkServerUDP {
42 * the Multicast socket.
44 * Deliberately hides the DatagramSocket in NetworkServerUDP so that inherited methods
45 * can access the same name.
47 private MulticastSocket _multicastSocket = null;
49 * Construct the server with a Logger.
51 * No socket is opened.
53 * @param socketAddress The socket to listen on
54 * @param title source identifier for use in log messages and sent NetworkMessage objects
55 * @param logger An instance of Logger to be used by all objects of this class
57 public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title, Logger logger) {
58 super(socketAddress, title, logger);
59 // permit broadcasting to pseudo-host 'all' since this is multicast
60 this._serviceToHostMap.put("all", new LastSeenHost(socketAddress.getSocketAddress()));
64 * Construct the server without a Logger.
66 * No socket is opened.
68 * @param socketAddress The socket to listen on
69 * @param title source identifier for use in log messages and sent NetworkMessage objects
71 public NetworkServerUDPMulticast(WSYD_SocketAddress socketAddress, String title) {
72 super(socketAddress, title);
76 * Get the MulticastSocket object for this service.
78 * @return the base-class DatagramSocket type
81 public java.net.DatagramSocket getSocket() {
82 return this._multicastSocket;
85 * Join the multicast group on all interfaces ready for accepting packets.
87 * It should also set a reasonable socket timeout with a call to setSoTimeout()
88 * to prevent unnecessary blocking.
90 * @throws SocketException
93 public void serverOpen() throws SocketException {
95 ArrayList<String> messages = new ArrayList<>();
97 this._multicastSocket = new MulticastSocket(_socketAddress.getPort());
98 this._multicastSocket.setTimeToLive(2); // don't traverse more than 2 routers
99 this._multicastSocket.setSoTimeout(100); // 1/10th second blocking timeout on receive()
100 this._multicastSocket.setLoopbackMode(true); // inverted logic; true == disable. Don't want to receive our own sent packets
102 Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
103 while (ifs.hasMoreElements()) {
104 NetworkInterface iface = ifs.nextElement();
106 messages.add(iface.getName());
107 messages.add(Boolean.toString(iface.supportsMulticast()));
108 log(Level.INFO, _title, "Interface {0}: probe multicast support: {1}", messages);
109 if (iface.supportsMulticast()) {
112 messages.add(iface.getName());
113 messages.add(_socketAddress.getSocketAddress().getAddress().toString());
114 this._multicastSocket.joinGroup(_socketAddress.getSocketAddress(), iface);
115 log(Level.INFO, _title, "Interface {0}: joined multicast group {1}", messages);
116 } catch (Exception e) {
118 messages.add(iface.getName());
119 log(Level.SEVERE, _title, "Interface {0}: failed to join multicast group", messages);
123 } catch (IOException e) {
124 log(Level.SEVERE, _title, "Failed to open multicast socket");
131 * @throws SocketException
134 public void serverClose() throws SocketException {
135 if (this._multicastSocket != null) {
137 log(Level.INFO, _title, "Leaving multicast group");
138 this._multicastSocket.leaveGroup(_socketAddress.getAddress());
139 this._multicastSocket.close();
140 } catch (IOException e) {
141 log(Level.SEVERE, _title, "failed to leave multicast group");
148 * Remove stale service records.
150 * @param ageInMillis milliseconds since last seen to be considered stale
151 * @return quantity of records removed
153 public ArrayList<String> cleanServiceToHostMap(long ageInMillis) {
154 ArrayList<String> result = new ArrayList<>();
155 long expireTime = System.currentTimeMillis() - ageInMillis;
156 java.util.Enumeration<String> keys = this._serviceToHostMap.keys();
157 while (keys.hasMoreElements()) {
158 String key = keys.nextElement();
160 // XXX: special handling for "all" target - never remove it
161 if (!key.equals("all")) {
162 LastSeenHost host = _serviceToHostMap.get(key);
164 if (host.timeInMillis < expireTime) {
165 if (_serviceToHostMap.remove(key, host)) {
167 ArrayList<String> messages = new ArrayList<>();
169 log(Level.INFO, _title, "Removed \"{0}\" from service map", messages);