001package jmri.jmris; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.net.ServerSocket; 007import java.net.Socket; 008import java.util.ArrayList; 009import java.util.HashMap; 010 011import jmri.InstanceManager; 012import jmri.ShutDownTask; 013import jmri.util.zeroconf.ZeroConfService; 014 015/** 016 * This is the main JMRI Server implementation. 017 * 018 * It starts a thread for each client. 019 * 020 */ 021public class JmriServer { 022 023 protected int portNo = 3000; // Port to listen to for new clients. 024 protected int timeout = 0; // Timeout in milliseconds (0 = no timeout). 025 protected ServerSocket connectSocket; 026 protected ZeroConfService service = null; 027 protected ShutDownTask shutDownTask = null; 028 private Thread listenThread = null; 029 protected ArrayList<ClientListener> connectedClientThreads = new ArrayList<>(); 030 031 // Create a new server using the default port 032 public JmriServer() { 033 this(3000); 034 } 035 036 // Create a new server using a given port and no timeout 037 public JmriServer(int port) { 038 this(port, 0); 039 } 040 041 // Create a new server using a given port with a timeout 042 // A timeout of 0 is infinite 043 public JmriServer(int port, int timeout) { 044 super(); 045 // Try registering the server on the given port 046 try { 047 this.connectSocket = new ServerSocket(port); 048 } catch (IOException e) { 049 log.error("Failed to connect to port {}", port); 050 } 051 this.portNo = port; 052 this.timeout = timeout; 053 } 054 055 // Maintain a vector of connected clients 056 // Add a new client 057 private synchronized void addClient(ClientListener client) { 058 if (!connectedClientThreads.contains(client)) { 059 connectedClientThreads.add(client); 060 client.start(); 061 } 062 } 063 064 //Remove a client 065 private synchronized void removeClient(ClientListener client) { 066 if (connectedClientThreads.contains(client)) { 067 client.stop(this); 068 connectedClientThreads.remove(client); 069 } 070 } 071 072 public void start() { 073 /* Start the server thread */ 074 if (this.listenThread == null) { 075 this.listenThread = jmri.util.ThreadingUtil.newThread(new NewClientListener(connectSocket)); 076 this.listenThread.start(); 077 this.advertise(); 078 } 079 if (this.shutDownTask != null) { 080 InstanceManager.getDefault(jmri.ShutDownManager.class).register(this.shutDownTask); 081 } 082 } 083 084 // Advertise the service with ZeroConf 085 protected void advertise() { 086 this.advertise("_jmri._tcp.local."); 087 } 088 089 protected void advertise(String type) { 090 this.advertise(type, new HashMap<>()); 091 } 092 093 protected void advertise(String type, HashMap<String, String> properties) { 094 if (this.service == null) { 095 this.service = ZeroConfService.create(type, this.portNo, properties); 096 } 097 this.service.publish(); 098 } 099 100 public void stop() { 101 this.connectedClientThreads.forEach((client) -> { 102 client.stop(this); 103 }); 104 this.listenThread = null; 105 this.service.stop(); 106 if (this.shutDownTask != null) { 107 InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(this.shutDownTask); 108 } 109 } 110 111 // Internal thread to listen for new connections 112 class NewClientListener implements Runnable { 113 114 ServerSocket listenSocket = null; 115 boolean running = true; 116 117 public NewClientListener(ServerSocket socket) { 118 119 listenSocket = socket; 120 } 121 122 @Override 123 public void run() { 124 // Listen for connection requests 125 try { 126 while (running) { 127 Socket clientSocket = listenSocket.accept(); 128 clientSocket.setSoTimeout(timeout); 129 log.debug(" Client Connected from IP {} port {}", clientSocket.getInetAddress(), clientSocket.getPort()); 130 addClient(new ClientListener(clientSocket)); 131 } 132 } catch (IOException e) { 133 log.error("IOException while Listening for clients"); 134 } 135 } 136 137 public void stop() { 138 //super.stop(); 139 running = false; 140 try { 141 listenSocket.close(); 142 log.debug("Listen Socket closed"); 143 } catch (IOException e) { 144 log.error("socket in ThreadedServer won't close"); 145 } 146 } 147 } // end of NewClientListener class 148 149 // Internal class to handle a client 150 protected class ClientListener implements Runnable { 151 152 Socket clientSocket = null; 153 DataInputStream inStream = null; 154 DataOutputStream outStream = null; 155 Thread clientThread = null; 156 157 public ClientListener(Socket socket) { 158 log.debug("Starting new Client"); 159 clientSocket = socket; 160 try { 161 inStream = new DataInputStream(clientSocket.getInputStream()); 162 outStream = new DataOutputStream(clientSocket.getOutputStream()); 163 } catch (IOException e) { 164 log.error("Error obtaining I/O Stream from socket."); 165 } 166 } 167 168 public void start() { 169 clientThread = jmri.util.ThreadingUtil.newThread(this); 170 clientThread.start(); 171 } 172 173 public void stop(JmriServer server) { 174 try { 175 server.stopClient(inStream, outStream); 176 clientSocket.close(); 177 } catch (IOException e) { 178 // silently ignore, since we may be reacting to a closed socket 179 } 180 clientThread = null; 181 } 182 183 @Override 184 public void run() { 185 // handle a client. 186 try { 187 handleClient(inStream, outStream); 188 } catch (IOException ex) { 189 // When we get an IO exception here, we're done 190 log.debug("Server Exiting"); 191 // Unregister with the server 192 removeClient(this); 193 } catch (java.lang.NullPointerException ex) { 194 // When we get an IO exception here, we're done with this client 195 log.debug("Client Disconnect", ex); 196 // Unregister with the server 197 removeClient(this); 198 } 199 } 200 } // end of ClientListener class. 201 202 // Handle communication to a client through inStream and outStream 203 public void handleClient(DataInputStream inStream, DataOutputStream outStream) throws IOException { 204 // Listen for commands from the client until the connection closes 205 byte cmd[] = new byte[100]; 206 int count; 207 while (true) { 208 // Read the command from the client 209 count = inStream.read(cmd); 210 // Echo the input back to the client 211 if (count != 0) { 212 outStream.write(cmd); 213 } 214 } 215 } 216 217 // Send a stop message to the client if applicable 218 public void stopClient(DataInputStream inStream, DataOutputStream outStream) throws IOException { 219 outStream.writeBytes(""); 220 } 221 222 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(JmriServer.class); 223 224}