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