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