001/*
002 *  $Revision: 1357 $
003 *  $Author: tgutwin $
004 *  $Date: 2020-06-20 14:19:31 -0700 (Sat, 20 Jun 2020) $
005 *  $URL: svn://fred.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/sockets/TCPSocketClient.java $
006 */
007/*
008 *
009 *  Written by Tom Gutwin - WebARTS Design.
010 *  Copyright (C) 2014 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 */
027package ca.bc.webarts.tools.sockets;
028
029import java.io.DataInputStream;
030import java.io.PrintStream;
031import java.io.BufferedReader;
032import java.io.InputStreamReader;
033import java.io.IOException;
034import java.net.Socket;
035import java.net.UnknownHostException;
036
037/** A basic TCPSocket client that works sends messages to a TCP Socket listener/server. It optionally waits for a response.
038  * <br /><br />It works well with its companion class {@link ca.bc.webarts.tools.sockets.TCPSocketServer TCPSocketServer}.<br /><br />
039 *
040 * This class has a main method to execute directly as an app messaging to a specified serverIP on {@link #DEFAULT_PORT DEFAULT_port} {@link #DEFAULT_PORT 44444}.<pre>
041 *         java ca.bc.webarts.tools.sockets.TCPSocketClient [tcpSocketServer]
042 *              - portNumber is optional</pre>
043 * It reads messages directly from the System.in/commandline. <br><br>
044 * It can also be wrapped in another java class... Example usage :<br>
045 * <pre>
046 * try
047 * {
048 *   TCPSocketClient instance =  new TCPSocketClient( "someHostName", 44844);
049 *   String req = "ogg123 -z /mnt/snd/someTunesDir";
050 *   while ( !req.trim().equalsIgnoreCase("Bye") &&
051 *           !req.trim().equalsIgnoreCase("ttfn") &&
052 *           !req.trim().equalsIgnoreCase("Quit") &&
053 *           !req.trim().equalsIgnoreCase("Exit"))
054 *   {
055 *     try
056 *     {
057 *       if( //instance.isListening() &&
058 *          !req.trim().equals(""))
059 *       {
060 *         //req = inputLine.readLine().trim();
061 *         if (instance.debug_>1) System.out.println("Request: "+req);
062 *         instance.initSocket();
063 *         if (instance.isInit())
064 *         {
065 *           instance.sendAndWait(req,2000);
066 *           instance.closeSocket();
067 *         }
068 *         else System.err.println("Socket NOT init");
069 *       }
070 *     }
071 *     catch (IOException e)
072 *     {
073 *       System.err.println("IOException:  " + e);
074 *       e.printStackTrace();
075 *     }
076 *     //Go Back for another message
077 *     req = getAnotherMessage().trim();
078 *   } // loop back and send another msg
079 * }
080 * catch (UnknownHostException e)
081 * {
082 *   System.err.println("Don't know about host " + instance.host_);
083 * }
084 * catch (IOException ioEx)
085 * {
086 *   ioEx.printStackTrace();
087 *   System.err.println("Couldn't get I/O for the connection to the host " + instance.host_);
088 * }
089 * </pre>
090 **/
091public class TCPSocketClient implements Runnable
092{
093
094  protected static String DEFAULT_SERVER_HOST = "localhost";
095
096  /** Simple multi-level debug flag (0-2).The higher the number, the higher the verbosity. **/
097  protected int debug_ = 1;
098
099  /** The client socket. **/
100  protected  Socket clientSocket_ = null;
101  /** The output stream. **/
102  protected PrintStream os_ = null;
103  /** The input stream. **/
104  protected DataInputStream is_ = null;
105
106  protected String host_ = "localhost";
107  /** The port to send messages. DEFAULTS to {@link ca.bc.webarts.tools.TCPSocketServer.DEFAULT_PORT TCPSocketServer.DEFAULT_PORT}. **/
108  protected int port_ = TCPSocketServer.DEFAULT_PORT;
109
110  protected Thread listener_ = null;
111  protected boolean listening_ = false;
112  protected  boolean waitingForResponse_ = false;
113  protected boolean errorResponse_ = false;
114  java.util.Vector<String>  responseList_ = null;
115  // private boolean listeningLock_ = false;
116  //private  boolean responseLock_ = false;
117
118
119  /** Default constructor that uses default server and port. **/
120  public TCPSocketClient()
121  {
122  }
123
124
125  /** Constructor that uses sets a specified server and port. **/
126  public TCPSocketClient(String hostName, int portNum)
127  {
128    host_=hostName;
129    port_=portNum;
130  }
131
132
133  /** if NOT already initialized, it will instantiate a new socket, and associated in and out streams; Else it will return the existing.
134    * @return the clientSocket_
135    **/
136  public synchronized Socket initSocket() throws UnknownHostException, IOException
137  {
138    if (debug_>1) System.out.println("initSocket() and isInit="+isInit());
139    if(!isInit())
140    {
141      clientSocket_ = new Socket(host_, port_);
142      os_ = new PrintStream(clientSocket_.getOutputStream());
143      is_ = new DataInputStream(clientSocket_.getInputStream());
144    }
145    return clientSocket_;
146  }
147
148
149  /** Starts the listener Thread, AFTER checking that everything is initialized and ready to go (initsocket).**/
150  public boolean listenToSocket() throws IOException
151  {
152    if (debug_>1) System.out.println("listenToSocket() and isInit="+isInit());
153    if(isInit())
154    {
155      listener_ = new Thread(this, "SocketListener");
156      listener_.start();
157      int waiter = 0;int sleepTime=200;
158      while(!isListening() && waiter<1000) {ca.bc.webarts.widgets.Util.sleep(sleepTime);waiter+=sleepTime;}
159    }
160    return isListening();
161  }
162
163
164  /** sends an interrupt message to the listener thread. **/
165  public void signalClose()
166  {
167    if (debug_>1) System.out.println("signalClose()");
168    if (listener_!=null && listener_.isAlive())
169      try{listener_.interrupt();}catch(SecurityException sEx){if (debug_>1) System.out.println("Could NOT interrupt the listener... aborting and moving on.");}
170  }
171
172
173  /** Clean shutdown. Closes client connections after waiting, closes socket and cleans things up
174    * like it was before calling {@link #initSocket initSocket}. **/
175  public synchronized void closeSocket() throws IOException
176  {
177    if (debug_>1) System.out.println("closeSocket() and isInit: "+isInit());
178    signalClose();
179
180     int waiter = 0;int sleepTime=75;int maxBlockTime_ms = 2000;
181     while(isWaitngForResponse() && waiter<maxBlockTime_ms) {ca.bc.webarts.widgets.Util.sleep(sleepTime);waiter+=sleepTime;}
182
183     if (debug_>1) System.out.println("  CloseWaitComplete="+waiter+"/"+maxBlockTime_ms);
184
185     if( os_!=null ) os_.close();
186     if( is_!=null ) is_.close();
187     if( clientSocket_!=null ) clientSocket_.close();
188     //listening_=false;
189  }
190
191
192  /** Only a new thread  should call this. **/
193  private synchronized boolean setListening(boolean l)
194  {
195    //getListeningLock();
196    listening_=l;
197    //releaseListeningLock();
198    return l;
199  }
200
201
202  private boolean setListening()
203  {
204    return setListening(true);
205  }
206
207
208  /** Confirms if a listener Thread has started and is running/listening for a server response. Listeners start by calling the listenToSocket() method.**/
209  private synchronized boolean isListening()
210  {
211    boolean l = false;
212   // getListeningLock();
213    l=listening_;
214    //releaseListeningLock();
215    return l;
216  }
217
218
219  private synchronized boolean setWaitngForResponse(boolean l)
220  {
221    //getWaitngingLock();
222    waitingForResponse_=l;
223    //releaseWaitngingLock();
224    return l;
225  }
226
227
228  protected  boolean setWaitngForResponse()
229  {
230    return setWaitngForResponse(true);
231  }
232
233
234  protected  synchronized boolean isWaitngForResponse()
235  {
236    boolean l = false;
237    //getWaitngingLock();
238    l=waitingForResponse_;
239    //releaseWaitngingLock();
240    return l;
241  }
242
243/*
244  synchronized private boolean getWaitngingLock()
245  {
246    if(!responseLock_)
247      responseLock_=true;
248    return responseLock_;
249  }
250
251
252  synchronized private boolean getListeningLock()
253  {
254    if(!listeningLock_)
255      listeningLock_=true;
256    return listeningLock_;
257  }
258*/
259
260
261  /**
262    * Set Method for class field 'responseList_'.
263    *
264    * @param responseList is the value to set this class field to.
265    *
266    **/
267  public  void setResponseList(java.util.Vector<String> responseList)
268  {
269    this.responseList_ = responseList;
270  }  // setResponseList_ Method
271
272
273  /**
274    * Get Method for class field 'responseList_'.
275    *
276    * @return java.util.Vector<String> - The value the class field 'responseList_'.
277    *
278    **/
279  public java.util.Vector<String> getResponseList()
280  {
281    return responseList_;
282  }  // getResponseList Method
283
284
285  public boolean isInit()
286  {
287    return (clientSocket_!=null && !clientSocket_.isClosed() &&  os_!=null && is_!=null );
288  }
289
290
291  /** Send the message and return as soon as it is sent (without any waiting for a response. **/
292  public void send(String message) throws IOException
293  { sendAndWait(message,0);}
294
295
296  /** Send the message to the TCPSocketServer AND wait for a response upto a maximum blocking time. **/
297  public void sendAndWait(String message, int maxBlockTime_ms) throws IOException
298  {
299    if (debug_>1) System.out.println("sendAndWait(\""+message+"\", "+maxBlockTime_ms+")");
300    if(isInit())
301    {
302      //setWaitngForResponse();
303      if(listenToSocket())
304      {
305        if (debug_>0) System.out.println("  ...sendingMessage: "+message);
306        os_.println(message+"\n");
307        int waiter = 0;int sleepTime=75;
308        while(isWaitngForResponse() && waiter<maxBlockTime_ms) {ca.bc.webarts.widgets.Util.sleep(sleepTime);waiter+=sleepTime;}
309        if (debug_>0) System.out.println("SendWaitComplete="+waiter+"/"+maxBlockTime_ms);
310      }
311      else
312      {
313        // try again
314        if(listenToSocket())
315        {
316          os_.println(message+"\n");
317          int waiter = 0;int sleepTime=100;
318          while(isWaitngForResponse() && waiter<maxBlockTime_ms) {ca.bc.webarts.widgets.Util.sleep(sleepTime);waiter+=sleepTime;}
319          if (debug_>0) System.out.println("SendWaitComplete = "+waiter+"/"+maxBlockTime_ms+"ms");
320        }
321      }
322
323    }
324    else System.out.println("Socket NOT init");
325  }
326
327
328  public static void main(String[] args)
329  {
330    TCPSocketClient instance= null;
331    if (args.length < 2)
332    {
333      instance = new TCPSocketClient();
334      System.out.println("Usage: java TCPSocketClient <host> <portNumber>\n" +
335                         "Now using host=" + instance.host_ + ", portNumber=" + instance.port_);
336    }
337    else
338    {
339      //host = args[0];
340      //portNumber = Integer.valueOf(args[1]).intValue();
341      System.out.println("Usage: java TCPSocketClient <host> <portNumber>\n" +
342                         "Now using host=" +  args[0] + ", portNumber=" + Integer.valueOf(args[1]).intValue());
343      instance = new TCPSocketClient( args[0], Integer.valueOf(args[1]).intValue());
344    }
345
346    try
347    {
348        if (instance.debug_>0) System.out.print((instance.isListening()?"?>":"-"));
349
350        /* Read From The Commandline */
351        BufferedReader inputLine = null;
352        inputLine = new BufferedReader(new InputStreamReader(System.in));
353        String req = "";
354
355        while (!req.trim().equalsIgnoreCase("Bye") &&
356               !req.trim().equalsIgnoreCase("Quit") &&
357               !req.trim().equalsIgnoreCase("Exit"))
358        {
359          try
360          {
361            if ( //instance.isListening() &&
362                !req.trim().equals(""))
363            {
364              //req = inputLine.readLine().trim();
365              if (instance.debug_>0) System.out.print("Request: "+req);
366              instance.initSocket();
367              if (instance.isInit())
368              {
369                System.out.println(" being SENT... ");
370                instance.sendAndWait(req, 2500);// Sends and starts it listening here
371                instance.closeSocket();
372              }
373              else
374                System.err.println(" Socket NOT init");
375            }
376          }
377          catch (IOException e)
378          {
379            System.err.println("IOException:  " + e);
380            e.printStackTrace();
381          }
382          //Go Back for another message
383          System.out.print((instance.isListening()?"?>":"->"));
384          req = inputLine.readLine().trim();
385        }
386
387    }
388    catch (UnknownHostException e)
389    {
390      System.err.println("Don't know about host " + instance.host_);
391    }
392    catch (IOException ioEx)
393    {
394      ioEx.printStackTrace();
395      System.err.println("Couldn't get I/O for the connection to the host " + instance.host_);
396    }
397  }
398
399
400  /** Chunks the output from the passed inputStream into individual lines/Strings.  **/
401  protected  void printResponse(java.io.InputStream i) { printResponse(i, System.out);}
402  protected  void printResponse(java.io.InputStream i, java.io.OutputStream o)
403  {
404    if (debug_>0) System.out.print(" StreamGobbling starting");
405    ca.bc.webarts.tools.StreamGobbler g =
406      new ca.bc.webarts.tools.StreamGobbler(i, "  }] ", new java.io.PrintStream(o,true));
407    g.setCapture(true);
408    g.start();
409  }
410
411
412  /** Chunks the output from the passed inputStream into individual lines/Strings.  **/
413  protected  java.util.Vector<String> readResponse(java.io.InputStream i)
414  {
415    java.util.Vector<String>  retVal = new java.util.Vector<String>();
416    String line = "";
417    java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(i));
418
419    try{if(in.ready())while ((line = in.readLine()) != null) retVal.add(line);}
420    catch(IOException ioex){ System.out.println(" ?WHY? "+ioex.getMessage());}
421    return retVal;
422  }
423
424
425  public void run()
426  {
427    if (debug_>0) System.out.print(" TCPSocketClient thread starting");
428    setListening(); // creates the mutex
429    if (debug_>0) System.out.println((isListening()?", is Listening for a response":" and waiting for a response..."));
430    /*
431     * Keep on reading from the socket till we receive "Bye" from the
432     * server. Once we received that then we want to break.
433     */
434    String responseLine = null;
435    int sleepTime = 2500;
436    boolean readAll = true;
437    ca.bc.webarts.tools.StreamGobbler g = null;
438    try
439    {
440      errorResponse_ = true;
441      setWaitngForResponse();
442      ca.bc.webarts.widgets.Util.sleep(sleepTime);
443
444      if (debug_>2) System.out.println(" is_ is null: "+(is_ == null));
445
446      if(readAll && is_ != null)
447      {
448        if(false)
449        {
450          if (debug_>0) System.out.print(" StreamGobbling starting");
451          //printResponse(is_);
452            g = new ca.bc.webarts.tools.StreamGobbler(is_, "  }] ");
453            g.setCapture(true);
454            g.start();
455        }
456        else
457        {
458          responseList_ =readResponse(is_);
459          System.out.println("### ServerResponses ");
460          for (String r: responseList_) if(r.startsWith("!")) System.out.println(" "+r);
461                                        else System.out.println("    > "+r);
462          for (String r: responseList_)
463          {
464
465            if (r.startsWith(TCPSocketServer.END))
466            {
467              setWaitngForResponse(false);
468              setListening(false);
469              break;
470            }
471            else if (r.startsWith(TCPSocketServer.ERROR))
472            {
473              setWaitngForResponse(false);
474              errorResponse_ = true;
475              //System.out.println("  ERROR: "+ responseLine.substring("ERROR".length()));
476            }
477            else if ( r.startsWith(TCPSocketServer.SUCCESS))
478            {
479              setWaitngForResponse(false);
480            }
481          }
482        }
483      }
484      else
485      while (is_ != null && (responseLine = is_.readLine()) != null)
486      {
487        System.out.println("]]] ServerResponse: "+ responseLine);
488        if (responseLine.startsWith(TCPSocketServer.END) ||
489             responseLine.startsWith("END") ) // SERVER  response
490        {
491          setWaitngForResponse(false);
492          setListening(false);
493          break;
494        }
495        else if (responseLine.startsWith(TCPSocketServer.ERROR) ||
496             responseLine.startsWith("ERROR") ) // SERVER response
497        {
498          setWaitngForResponse(false);
499          errorResponse_ = true;
500          //System.out.println("  ERROR: "+ responseLine.substring("ERROR".length()));
501        }
502        else if ( responseLine.startsWith(TCPSocketServer.SUCCESS) ||
503             responseLine.startsWith("SUCCESS") ) // SERVER response
504        {
505          setWaitngForResponse(false);
506        }
507        //else
508          System.out.println("            Response: "+ responseLine);
509
510        //ca.bc.webarts.widgets.Util.sleep(sleepTime);
511
512      }
513      if(g!=null)
514      {
515        System.out.println("\n|");
516        ca.bc.webarts.widgets.Util.sleep(sleepTime);
517        System.out.println(g.getCapturedOutput());
518        g.finishedGobbling_ = true;
519      }
520      System.out.println("\nEND of responses. ");
521
522      if (debug_>0) System.out.println("Done Listening ");
523      if (debug_>1) System.out.println("    responseLine==null is "+(responseLine==null));
524    }
525    catch (java.net.SocketException sEx)
526    {
527      if (debug_>1) System.err.println(sEx.getMessage());
528    }
529    catch (java.nio.channels.ClosedByInterruptException intEx)
530    {
531      if (debug_>1) System.err.println("listenerThread Interupted");
532    }
533    catch (IOException e)
534    {
535      System.err.println("runIOException:  " + e);
536      e.printStackTrace();
537    }
538    finally
539    {
540      setWaitngForResponse(false);
541      setListening(false);
542    }
543    if (debug_>1) System.out.println("Exiting Listener Thread.Run");
544  }
545}