001/* 002 * $Rev: 1357 $: Revision of last commit 003 * $Author: tgutwin $: Author of last commit 004 * $Date: 2020-06-20 14:19:31 -0700 (Sat, 20 Jun 2020) $: Date of last commit 005 * $URL: svn://fred.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/sockets/TCPSocketServer.java $ 006 */ 007/* 008 * 009 * Written by Tom Gutwin - WebARTS Design. 010 * Copyright (C) 2015 WebARTS Design, North Vancouver Canada 011 * http://www.webarts.bc.ca 012 * 013 * This program is free software; you can redistribute it and/or modify 014 * it under the terms of the GNU General Public License as published by 015 * the Free Software Foundation; either version 2 of the License, or 016 * (at your option) any later version. 017 * 018 * This program is distributed in the hope that it will be useful, 019 * but WITHOUT ANY WARRANTY; without_ even the implied warranty of 020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 021 * GNU General Public License for more details. 022 * 023 * You should have received a copy of the GNU General Public License 024 * along with this program; if not, write to the Free Software 025 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 026 */ 027 028package ca.bc.webarts.tools.sockets; 029 030import java.io.DataInputStream; 031import java.io.PrintStream; 032import java.io.IOException; 033import java.net.Socket; 034import java.net.ServerSocket; 035import java.util.concurrent.ArrayBlockingQueue; 036 037//import ca.bc.webarts.tools.NativeAppLauncher; 038//import ca.bc.webarts.widgets.ProcessRunner; 039 040/** 041 * A Multi-Threaded TCP Socket server that listens for requests and then performs specific functions 042 * (wrapped in the {@link ClientThread ClientThread}) on the server. 043 * It acts as a Network based relay of requests. 044 * It works well with its companion class {@link ca.bc.webarts.tools.sockets.TCPSocketClient TCPSocketClient}. <br><br> 045 * 046 * This class has a main method to execute directly as an app listening on its {@link #DEFAULT_PORT DEFAULT_port} {@link #DEFAULT_PORT 44444}.<pre> 047 * java ca.bc.webarts.tools.sockets.TCPSocketServer [portNumber] 048 * - portNumber is optional</pre> 049 * Its default function is to take whatever message 050 * is sent and <b>execute it directly on the commanline</b>. You should change this to whatever you want. <br><br> 051 * It can also be wrapped in another java class<br>Example usage :<br> 052 * <pre> TCPSocketServer tcpServer = new TCPSocketServer(portToUse); 053 054 int i = 0; 055 boolean posted = false; 056 while (tcpServer.getSocket()!=null) 057 { 058 try 059 { 060 // Listening for another client connection 061 if (debug_>1) System.out.println("Accepting new connections "); 062 posted = tcpServer.postConnection(tcpServer.getSocket().accept()); 063 064 //if (debug_>1) System.out.println("connectionPosted> "+posted); 065 if (!posted) 066 { 067 if (debug_>0) System.out.println(" NOT Posted: ClientThreads MAXXED: "); 068 } 069 } 070 catch (IOException e) 071 { 072 System.out.println(e); 073 } 074 } 075 </pre> 076 **/ 077public class TCPSocketServer 078{ 079 /** Simple multi-level debug flag (0-2).The higher the number, the higher the verbosity. **/ 080 public static int debug_=1; 081 082 /** Default Server Listening Port (44444). **/ 083 public static final int DEFAULT_PORT = 44444; 084 /** This chat server can accept up to maxClientsCount clients' connections. **/ 085 protected static final int MAX_CLIENTS_COUNT = 25; 086 087 /** Template Result message for a successfull completion.**/ 088 public static final String SUCCESS = "! Success"; 089 /** Template Result message for a bad /error result. **/ 090 public static final String ERROR = "! Puke"; 091 /** Template Result message for a last final/close result message. **/ 092 public static final String END = "! END"; 093 094 /** The server socket. **/ 095 protected ServerSocket serverSocket_ = null; 096 /** The client socket. **/ 097 protected Socket clientSocket_ = null; 098 /** This sever's listening TCP socket. **/ 099 protected int portNumber_ = DEFAULT_PORT; 100 101 /** The client request processor object thread. It managesall the client requests that come in. **/ 102 protected ClientProcessorThread processor_ = null; 103 /** The queue of client process threads used by the processor_ . **/ 104 protected ArrayBlockingQueue<Thread> clientQueue_ = null; 105 /** the name of the ClientThread class that gets used for processing. DEFAULTS to 'TCPSocketServer.ClientThread'. **/ 106 protected static String clientThreadClassname_ = "ClientThread"; 107 108 protected boolean acceptingConnections_ = true; 109 110 protected String messageEnd_ = END; 111 112 /** Default Constructor using defaults. **/ 113 public TCPSocketServer() 114 { 115 initPortProcessors(); 116 } 117 118 119 /** Constructor to allow a custom port. **/ 120 public TCPSocketServer(int portNum) 121 { 122 portNumber_ = portNum; 123 initPortProcessors(); 124 } 125 126 127 /** Initializes all required class objects to be able to process requests. New ClientProcessorThread , Queue and starts them up. **/ 128 protected void initPortProcessors() 129 { 130 try 131 { 132 serverSocket_ = new ServerSocket(portNumber_); 133 clientQueue_ = new ArrayBlockingQueue <Thread> (MAX_CLIENTS_COUNT,true); 134 processor_ = new ClientProcessorThread(clientQueue_); 135 processor_.start(); 136 } 137 catch (IOException e) 138 { 139 System.out.println(e); 140 } 141 } 142 143 144 public ServerSocket getServerSocket(){return serverSocket_;} 145 public ServerSocket getSocket(){return getServerSocket();} 146 147 148 /** 149 * Returns the value of messageEnd_. 150 */ 151 public String getMessageEnd() { 152 return messageEnd_; 153 } 154 155 156 /** 157 * Sets the value of messageEnd_. 158 * @param messageEnd The value to assign to messageEnd_. 159 */ 160 public void setMessageEnd(String messageEnd) { 161 this.messageEnd_ = messageEnd; 162 } 163 164 165 /** Clean shutdown. Closes client connections after waiting, closes socket and cleans things up 166 * like it was before calling {@link #initPortProcessors initPortProcessors}. **/ 167 public void closeUpShop(int finalTime) 168 { 169 /* Stop Accepting Connections */ 170 acceptingConnections_ = false; 171 172 processor_.shutDown(); 173 int waiter = 0;int sleepTime=250; int maxBlockTime_ms = 3000; 174 while( waiter<maxBlockTime_ms) {ca.bc.webarts.widgets.Util.sleep(sleepTime);waiter+=sleepTime;} 175 try{serverSocket_.close();} 176 catch(IOException ioEx){} 177 finally{serverSocket_=null;} 178 } 179 180 181 /** 182 * Set Method for class field 'clientThreadClassname_'. 183 * 184 * @param clientThreadClassname is the value to set this class field to. 185 * 186 **/ 187 public static void setClientThreadClassname(String clientThreadClassname) 188 { 189 clientThreadClassname_ = clientThreadClassname; 190 } // setClientThreadClassname Method 191 192 193 /** 194 * Get Method for class field 'clientThreadClassname_'. 195 * 196 * @return String - The value the class field 'clientThreadClassname_'. 197 * 198 **/ 199 public static String getClientThreadClassname() 200 { 201 return clientThreadClassname_; 202 } // getClientThreadClassname Method 203 204 205 /** Get an instance of the default ClientThread connected to the default class clientSocket_ and executes all client requests as a command on the server. 206 * 207 * @return an instance of the default impl of a ClientThread 208 **/ 209 public ClientThread getClientThreadInstance() 210 { 211 return new ClientThread(clientSocket_); 212 } 213 214 215 /** Get an instance of the your own overridden ClientThread that handles client requests as you program. it connects to the default class clientSocket_. 216 * 217 * @param newClientThreadClassName the name of your ClientThread class 218 * @return an instance of the default impl of a ClientThread or null if failed 219 **/ 220 public ClientThread getClientThreadInstance(String newClassName) 221 { 222 ClientThread newCT = null; 223 try 224 { 225 Class newClientThreadClassName = Class.forName(newClassName); 226 //example access 1 227 if (debug_>1) System.out.println(" Using Class: "+newClientThreadClassName.getName()); 228 try 229 { 230 newCT = (ClientThread) newClientThreadClassName.newInstance(); 231 newCT.setClientSocket(clientSocket_); 232 } catch (InstantiationException ex) 233 { 234 //Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex); 235 } catch (IllegalAccessException ex) 236 { 237 //Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex); 238 } 239 } 240 catch(ClassNotFoundException cnfEx) 241 { 242 if (debug_>2) System.out.println("\n*ERROR: ClassNotFoundException for "+newClassName); 243 } 244 return newCT; 245 } 246 247 248 public boolean postConnection(Socket s ) { return postConnection(s,1500); } 249 250 251 /** 252 * Call this when a new connection comes in to start the procesing of the action that is taken. 253 * This does the work whena call comes in. 254 * 255 * 256 * @return boolean if still accepting connections 257 **/ 258 public boolean postConnection(Socket s, int maxBlockTime_ms ) 259 { 260 boolean retVal = acceptingConnections_; 261 int waiter = 0;int sleepTime=125; //int maxBlockTime_ms = 1500 262 if (acceptingConnections_) 263 { 264 clientSocket_=s; 265 if (debug_>1) System.out.println("\n >Posting a new "+getClientThreadClassname()+" connection: "); 266 Thread ct = getClientThreadInstance(getClientThreadClassname()); // does something with the request 267 if(ct!=null) 268 { 269 if (debug_>1) System.out.println(" and have its class instance to "+getClientThreadClassname()); 270 } 271 else 272 { 273 if (debug_>1) System.out.println(" UNSuccessful.... using default ClientThread."); 274 ct = getClientThreadInstance(); 275 } 276 277 /* Now loop and send them to get processed. */ 278 while(ct!=null && !clientQueue_.offer(ct)) 279 { 280 while( waiter<maxBlockTime_ms) {ca.bc.webarts.widgets.Util.sleep(sleepTime);waiter+=sleepTime;} 281 //System.out.println("Maximum Clients Reached: "+MAX_CLIENTS_COUNT); 282 } 283 } 284 285 if (acceptingConnections_) retVal = (waiter<maxBlockTime_ms); 286 return retVal; 287 } 288 289 290 /** Runs this from the commandline using defaults. It also serves as example code to use this class within other classes. **/ 291 public static void main(String args[]) 292 { 293 int portToUse= TCPSocketServer.DEFAULT_PORT; 294 if (args.length < 1) 295 { 296 System.out.println("Usage: java ca.bc.webarts.tools.sockets.TCPSocketServer <ClientThreadClassname> <portNumber>\n" + "Now using DEFAULT port number=" + portToUse); 297 } 298 else 299 { 300 System.out.println("TCPSocketServer Starting." ); 301 if (args.length < 2) 302 try { portToUse= Integer.valueOf(args[0]).intValue();System.out.println(" using port number=" + portToUse);} 303 catch (Exception ex) 304 { 305 // It must have been the ClientThreadClassname 306 setClientThreadClassname(args[0]); 307 System.out.println(" using ClientThreadClassname=" + getClientThreadClassname()); 308 } 309 else if (args.length < 3) 310 try 311 { 312 setClientThreadClassname(args[0]); 313 portToUse= Integer.valueOf(args[1]).intValue(); 314 System.out.println(" using port number=" + portToUse); 315 System.out.println(" AND ClientThreadClassname=" + getClientThreadClassname()); 316 } 317 catch (Exception ex) 318 { 319 // use defaults for port 320 } 321 322 // use defaults 323 } 324 TCPSocketServer tcpServer = new TCPSocketServer(portToUse); 325 326 int i = 0; 327 boolean posted = false; 328 while (tcpServer.getSocket()!=null) 329 { 330 try 331 { 332 // Listening for another client connection 333 if (debug_>1) System.out.println("Accepting new connections "); 334 posted = tcpServer.postConnection(tcpServer.getSocket().accept()); // accept blocks until a connection comes in 335 336 //if (debug_>1) System.out.println("connectionPosted> "+posted); 337 if (!posted) 338 { 339 if (debug_>0) System.out.println(" NOT Posted: ClientThreads MAXXED: "); 340 } 341 } 342 catch (IOException e) 343 { 344 System.out.println(e); 345 } 346 } 347 if (debug_>0) System.out.println("Done, Exiting"); 348 } 349 350 351/** Manages the processing of the connection requests that come in. It uses a queue to store / retrievie / and start 352 * the requests each in their own threads. 353 **/ 354class ClientProcessorThread extends Thread 355{ 356 protected int debug_=1; 357 ArrayBlockingQueue <Thread> q_ = null; 358 java.util.Vector<Thread> clientThreadList = new java.util.Vector<Thread>(); 359 int numActive_ = 0; 360 361 private boolean shutDownFlag_ = false; 362 private boolean killFlag_ = false; 363 int waiter = 0;int sleepTime=200;int maxBlockTime_ms = 1000; 364 365 366 public ClientProcessorThread(ArrayBlockingQueue <Thread> q) 367 { 368 if (debug_>1) System.out.println(" ClientProcessorThread: Instantiated "); 369 this.q_ = q; 370 } 371 372 373 public ClientProcessorThread(ArrayBlockingQueue <Thread> q,int blockTime_ms) 374 { 375 if (debug_>1) System.out.println(" ClientProcessorThread: Instantiated with a blockTime"); 376 this.q_ = q; 377 maxBlockTime_ms=blockTime_ms; 378 } 379 380 381 public synchronized void shutDown(){ shutDownFlag_ = true;} 382 public synchronized void kill(){ killFlag_ = true;} 383 384 385 public void run() 386 { 387 if (debug_>1) System.out.print(" ClientProcessorThread: started/running "); 388 int i = 0; 389 while(!shutDownFlag_ && !killFlag_) 390 { 391 numActive_ = 0; 392 for(i=0;i<clientThreadList.size();i++) 393 if((Thread)clientThreadList.get(i)!=null && 394 ((Thread)clientThreadList.get(i)).getState()!=Thread.State.TERMINATED) numActive_++; 395 if (debug_>2) System.out.print(" Q"+numActive_+"/"+clientThreadList.size()+" "); 396 Thread ct = (Thread) q_.poll(); 397 398 waiter = 0; 399 if(ct==null) 400 while( waiter<maxBlockTime_ms) {ca.bc.webarts.widgets.Util.sleep(sleepTime);waiter+=sleepTime;} 401 else 402 { 403 clientThreadList.add(ct); 404 ct.start(); 405 } 406 } 407 408 if(!killFlag_) 409 // Drain queue and exit 410 { 411 } 412 } 413} 414 415} 416