001package jmri.jmrix.dccpp.dccppovertcp; 002 003import java.io.FileInputStream; 004import java.io.FileNotFoundException; 005import java.io.FileOutputStream; 006import java.io.IOException; 007import java.io.OutputStream; 008import java.io.PrintStream; 009import java.net.ServerSocket; 010import java.net.Socket; 011import java.util.LinkedList; 012import java.util.Properties; 013import java.util.Set; 014import jmri.InstanceInitializer; 015import jmri.InstanceManager; 016import jmri.implementation.AbstractInstanceInitializer; 017import jmri.jmrix.dccpp.DCCppConstants; 018import jmri.util.FileUtil; 019import jmri.util.zeroconf.ZeroConfService; 020import org.openide.util.lookup.ServiceProvider; 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * Implementation of the DCCppOverTcp Server Protocol. 026 * 027 * @author Alex Shepherd Copyright (C) 2006 028 * @author Mark Underwood Copyright (C) 2015 029 */ 030public class Server { 031 032 private final LinkedList<ClientRxHandler> clients = new LinkedList<>(); 033 Thread socketListener; 034 ServerSocket serverSocket; 035 boolean settingsLoaded = false; 036 ServerListner stateListner; 037 boolean settingsChanged = false; 038 Runnable shutDownTask; 039 ZeroConfService service = null; 040 static final String AUTO_START_KEY = "AutoStart"; 041 static final String PORT_NUMBER_KEY = "PortNumber"; 042 static final String SETTINGS_FILE_NAME = "DCCppOverTcpSettings.ini"; 043 044 private Server() { 045 } 046 047 public void setStateListner(ServerListner l) { 048 stateListner = l; 049 } 050 051 private void loadSettings() { 052 if (!settingsLoaded) { 053 settingsLoaded = true; 054 Properties settings = new Properties(); 055 056 String settingsFileName = FileUtil.getUserFilesPath() + SETTINGS_FILE_NAME; 057 058 try { 059 log.debug("Server: opening settings file {}", settingsFileName); 060 java.io.InputStream settingsStream = new FileInputStream(settingsFileName); 061 try { 062 settings.load(settingsStream); 063 } finally { 064 settingsStream.close(); 065 } 066 067 String val = settings.getProperty(AUTO_START_KEY, "0"); 068 autoStart = (val.equals("1")); 069 val = settings.getProperty(PORT_NUMBER_KEY, Integer.toString(DCCppConstants.DCCPP_OVER_TCP_PORT)); 070 portNumber = Integer.parseInt(val, 10); 071 } catch (FileNotFoundException ex) { 072 log.debug("Server: loadSettings file not found"); 073 } catch (IOException ex) { 074 log.debug("Server: loadSettings exception: ", ex); 075 } 076 updateServerStateListener(); 077 } 078 } 079 080 public void saveSettings() { 081 // we can't use the store capabilities of java.util.Properties, as 082 // they are not present in Java 1.1.8 083 String settingsFileName = FileUtil.getUserFilesPath() + SETTINGS_FILE_NAME; 084 log.debug("Server: saving settings file {}", settingsFileName); 085 086 try ( OutputStream outStream = new FileOutputStream(settingsFileName); 087 PrintStream settingsStream = new PrintStream(outStream); ) { 088 089 settingsStream.println("# DCCppOverTcp Configuration Settings"); 090 settingsStream.println(AUTO_START_KEY + " = " + (autoStart ? "1" : "0")); 091 settingsStream.println(PORT_NUMBER_KEY + " = " + portNumber); 092 093 settingsStream.flush(); 094 settingsStream.close(); 095 settingsChanged = false; 096 } catch ( IOException ex) { 097 log.warn("Server: saveSettings exception: ", ex); 098 } 099 updateServerStateListener(); 100 } 101 private boolean autoStart; 102 103 public boolean getAutoStart() { 104 loadSettings(); 105 return autoStart; 106 } 107 108 public void setAutoStart(boolean start) { 109 loadSettings(); 110 autoStart = start; 111 settingsChanged = true; 112 updateServerStateListener(); 113 } 114 private int portNumber = DCCppConstants.DCCPP_OVER_TCP_PORT; 115 116 public int getPortNumber() { 117 loadSettings(); 118 return portNumber; 119 } 120 121 public void setPortNumber(int port) { 122 loadSettings(); 123 if ((port >= 1024) && (port <= 65535)) { 124 portNumber = port; 125 settingsChanged = true; 126 updateServerStateListener(); 127 } 128 } 129 130 public boolean isEnabled() { 131 return (socketListener != null) && (socketListener.isAlive()); 132 } 133 134 public boolean isSettingChanged() { 135 return settingsChanged; 136 } 137 138 public void enable() { 139 if (socketListener == null) { 140 socketListener = new Thread(new ClientListener()); 141 socketListener.setDaemon(true); 142 socketListener.setName("DCCppOverTcpServer"); 143 log.info("Starting new DCCppOverTcpServer listener on port {}", portNumber); 144 socketListener.start(); 145 updateServerStateListener(); 146 // advertise over Zeroconf/Bonjour 147 if (this.service == null) { 148 this.service = ZeroConfService.create("_dccppovertcpserver._tcp.local.", portNumber); 149 } 150 log.info("Starting ZeroConfService _dccppovertcpserver._tcp.local for DCCppOverTCP Server"); 151 this.service.publish(); 152 if (this.shutDownTask == null) { 153 this.shutDownTask = this::disable; 154 } 155 if (this.shutDownTask != null) { 156 InstanceManager.getDefault(jmri.ShutDownManager.class).register(this.shutDownTask); 157 } 158 } 159 } 160 161 public void disable() { 162 if (socketListener != null) { 163 socketListener.interrupt(); 164 socketListener = null; 165 try { 166 if (serverSocket != null) { 167 serverSocket.close(); 168 } 169 } catch (IOException ex) { 170 } 171 172 updateServerStateListener(); 173 174 // Now close all the client connections 175 Object[] clientsArray; 176 177 synchronized (clients) { 178 clientsArray = clients.toArray(); 179 } 180 for (int i = 0; i < clientsArray.length; i++) { 181 ((ClientRxHandler) clientsArray[i]).close(); 182 } 183 } 184 this.service.stop(); 185 if (this.shutDownTask != null) { 186 InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(this.shutDownTask); 187 } 188 } 189 190 public void updateServerStateListener() { 191 if (stateListner != null) { 192 stateListner.notifyServerStateChanged(this); 193 } 194 } 195 196 public void updateClinetStateListener() { 197 if (stateListner != null) { 198 stateListner.notifyClientStateChanged(this); 199 } 200 } 201 202 class ClientListener implements Runnable { 203 204 @Override 205 public void run() { 206 Socket newClientConnection; 207 String remoteAddress; 208 try { 209 serverSocket = new ServerSocket(getPortNumber()); 210 serverSocket.setReuseAddress(true); 211 while (!socketListener.isInterrupted()) { 212 newClientConnection = serverSocket.accept(); 213 remoteAddress = newClientConnection.getRemoteSocketAddress().toString(); 214 log.info("Server: Connection from: {}", remoteAddress); 215 addClient(new ClientRxHandler(remoteAddress, newClientConnection)); 216 } 217 serverSocket.close(); 218 } catch (IOException ex) { 219 if (ex.toString().indexOf("socket closed") == -1) { 220 log.error("Server: IO Exception: ", ex); 221 } 222 } 223 serverSocket = null; 224 } 225 } 226 227 protected void addClient(ClientRxHandler handler) { 228 synchronized (clients) { 229 clients.add(handler); 230 } 231 updateClinetStateListener(); 232 } 233 234 protected void removeClient(ClientRxHandler handler) { 235 synchronized (clients) { 236 clients.remove(handler); 237 } 238 updateClinetStateListener(); 239 } 240 241 public int getClientCount() { 242 synchronized (clients) { 243 return clients.size(); 244 } 245 } 246 247 @ServiceProvider(service = InstanceInitializer.class) 248 public static class Initializer extends AbstractInstanceInitializer { 249 250 @Override 251 public <T> Object getDefault(Class<T> type) { 252 if (type.equals(Server.class)) { 253 Server instance = new Server(); 254 if (instance.getAutoStart()) { 255 instance.enable(); 256 } 257 return instance; 258 } 259 return super.getDefault(type); 260 } 261 262 @Override 263 public Set<Class<?>> getInitalizes() { 264 Set<Class<?>> set = super.getInitalizes(); 265 set.add(Server.class); 266 return set; 267 } 268 } 269 270 private final static Logger log = LoggerFactory.getLogger(Server.class); 271 272}