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}