ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/cms/source/conient/uk/org/iscream/cms/conient/ConnectionHandler.java
Revision: 1.18
Committed: Mon Feb 26 00:25:00 2001 UTC (23 years, 3 months ago) by ajm
Branch: MAIN
Changes since 1.17: +32 -23 lines
Log Message:
added support for debugging packets
fixed problem when connecting and starting data but not getting config ;)

File Contents

# Content
1 //---PACKAGE DECLARATION---
2 package uk.ac.ukc.iscream.conient;
3
4 //---IMPORTS---
5 import uk.ac.ukc.iscream.util.*;
6 import java.io.*;
7 import java.net.*;
8 import javax.swing.JOptionPane;
9
10 /**
11 * This is the main thread for the client.
12 * Once started i continually checks it actionQueue
13 * for actions that other areas of the system have placed
14 * there, it then performs those actions.
15 *
16 * Currently this is the main thread where all non-GUI events
17 * are dispatched to.
18 *
19 * @author $Author: ajm4 $
20 * @version $Id: ConnectionHandler.java,v 1.17 2001/02/05 01:44:01 ajm4 Exp $
21 */
22 public class ConnectionHandler extends Thread {
23
24 //---FINAL ATTRIBUTES---
25
26 /**
27 * The current CVS revision of this class
28 */
29 public final String REVISION = "$Revision: 1.17 $";
30
31
32 /**
33 * The hardcoded protocol version that we are using.
34 * Used when handshaking with the server
35 */
36 public final double PROTOCOL_VERSION = 1.0;
37
38 /**
39 * Thread action DONOTHING.
40 * This is an invalid action, but here for completeness
41 * if anything sends this action it will warn appropriately
42 * but do what it says, ie, nothing ;)
43 */
44 public static final int DONOTHING = 0;
45
46 /**
47 * Thread action CONNECT.
48 * Opens the control link to the i-scream server.
49 */
50 public static final int CONNECT = 1;
51
52 /**
53 * Thread action STARTDATA.
54 * Opens the data link to the i-scream server
55 * and prepares all gui data components for data
56 * it then starts all relavant threads going.
57 */
58 public static final int STARTDATA = 2;
59
60 /**
61 * Thread action STOPDATA.
62 * Closes the data link and shuts down all components
63 * that update gui for various things.
64 */
65 public static final int STOPDATA = 3;
66
67 /**
68 * Thread action DISCONNECT.
69 * Checks to see if STOPDATA has been called if not,
70 * it will add STOPDATA and then DISCONNECT to
71 * the action queue and return. If STOPDATA has been
72 * called it closes down the Control Link and tidies up
73 * all relevant threads
74 */
75 public static final int DISCONNECT = 4;
76
77 /**
78 * Thread action QUIT.
79 * Checks the status of the two links, if either
80 * are still up, it queues the appropriate commands
81 * to close them down. It then System.exit(0)'s!
82 */
83 public static final int QUIT = 5;
84
85 /**
86 * Thread action GETCONFIGURATION
87 * Starts the command to obtain the configuration
88 * from the server.
89 * It then passes the IO to the Configuration object
90 * so it can obtain any specific configuration
91 */
92 public static final int GETCONFIGURATION = 6;
93
94 /**
95 * This is the time in seconds that this class
96 * should wait on the DataReader class to fully
97 * shutdown after calling shutdown() before
98 * forcing a shutdown
99 */
100 public static final int DATAREADER_SHUTDOWN_TIMEOUT = 5;
101
102 /**
103 * The default local server to connect to when a
104 * firewall is in use if one is not specified in the server
105 */
106 public static final String DEFAULT_FIREWALL_SERVER = "localhost";
107
108 /**
109 * The default time in seconds to wait for the
110 * firewall setup command to execute
111 */
112 public static final int DEFAULT_FIREWALL_COMMANDWAIT = 5;
113
114 //---STATIC METHODS---
115
116 //---CONSTRUCTORS---
117
118 /**
119 * Constructs new data handler.
120 * Needs a reference to the data panel so that
121 * it can set it processing inBound data
122 * Also gets a reference to the queue that will be
123 * used by other areas of the system to send it actions
124 *
125 * @param data the DataPanel in use
126 * @param actionQueue the actionQueue for this class
127 */
128 public ConnectionHandler(DataPanel data, Queue actionQueue) {
129 _data = data;
130 _actionQueue = actionQueue;
131 _myQueue = _actionQueue.getQueue();
132 }
133
134 //---PUBLIC METHODS---
135
136
137 /**
138 * Starts this ConnectionHandler running.
139 * This basically runs until told to stop, it gets "actions"
140 * from its actionQueue and switch's on them do determine
141 * what it should do it then carries out the action
142 *
143 * For details on what each action does see the action
144 * types.
145 */
146 public void run() {
147 if(_configuration.getProperty("control.onstartconnect") != null) {
148 _actionQueue.add(new Integer(CONNECT));
149 }
150 if(_configuration.getProperty("data.onstartconnect") != null) {
151 _actionQueue.add(new Integer(STARTDATA));
152 }
153 while(_running) {
154 // we wait for a call...
155 int action = 0;
156 try {
157 action = ((Integer) _actionQueue.get(_myQueue)).intValue();
158 } catch (InvalidQueueException e) {
159 // we 're never going to get this
160 // but if we do we should do something nasty
161 throw new RuntimeException("unable to retrieve events from actionQueue!");
162 }
163
164 // examine our action...
165 // if it was to connect...then we connect...
166 switch(action) {
167 case CONNECT:
168 if (_controlLink == null) {
169 try {
170 // get the server name from the config
171 _configuredServer = _configuration.getProperty("control.server");
172
173 if (_configuredServer == null) {
174 throw new IOException("no i-scream server in current configuration");
175 }
176
177 // open the socket to the server and bind the IO
178 // get the port from the config
179 String portString = _configuration.getProperty("control.port");
180 if (portString == null) {
181 throw new IOException("no i-scream server port in current configuration");
182 }
183 int port = 0;
184 try {
185 port = Integer.parseInt(portString);
186 } catch (NumberFormatException e) {
187 throw new IOException("no valid i-scream server port in current configuration");
188 }
189
190 // start firewall if needed
191 _server = handleFirewall(_configuredServer, port, _controlFirewallProcess);
192
193 Conient.setControlStatus("Connecting to - " + _server);
194 _controlLink = new Socket(_server, port);
195 _inBound = new BufferedReader(new InputStreamReader(_controlLink.getInputStream()));
196 _outBound = new PrintWriter(_controlLink.getOutputStream());
197 Conient.setControlStatus("Connection Established - " + _server);
198
199 String response;
200 response = _inBound.readLine();
201
202 // check the servers Protocol Identity against our own
203 // we SHOULD be backwards compatible, so we can continue if
204 // they are using a newer protocol, anything else then we die
205 if (!(Double.parseDouble(response.substring(10, response.length())) > PROTOCOL_VERSION)) {
206 Conient.addMessage("WARNING{control link}: server is using a newer protocol (" + response + "), please update your client, continuing with old protocol (PROTOCOL " + PROTOCOL_VERSION + ")" );
207 } else if (!(Double.parseDouble(response.substring(10, response.length())) < PROTOCOL_VERSION)) {
208 // tidy up
209 throw new IOException("incompatible protocol version");
210 }
211
212 // send the name of the client
213 _outBound.println(_configuration.getProperty("clientname"));
214 _outBound.flush();
215 response = _inBound.readLine();
216 if (!response.equals("OK")) {
217 // tidy up
218
219 throw new IOException("client name rejected - " + _configuration.getProperty("clientname"));
220 }
221 // get the config...we are connected now!
222 getConfigFromServer();
223 } catch (IOException e) {
224 // print the error and tidy up what's left
225 Conient.addMessage("ERROR{control link}: " + e);
226 _controlLink = null;
227 // and the firewall handler if there is one
228 closeFirewall(_controlFirewallProcess);
229 _actionQueue.clearQueue(_myQueue);
230 Conient.setControlStatus("Disconnected");
231 }
232 } else {
233 Conient.addMessage("WARNING{control link}: already established");
234 }
235 break;
236 case STARTDATA:
237 // as long as the data link hasn't been established
238 // we want to establish it
239 if (_dataLink == null) {
240 // check that the control link is open, if it isn't we
241 // might want to sort that problemo out
242 // we do this by simply queueing the event to occour, then
243 // this event to run again ;-)
244 if(_controlLink == null) {
245 Conient.addMessage("WARNING{data link}: control link not established - queueing start events");
246 _actionQueue.add(new Integer(CONNECT));
247 _actionQueue.add(new Integer(STARTDATA));
248 } else {
249 try {
250
251 // ask the server to start the data link
252 String response;
253 _outBound.println("STARTDATA");
254 _outBound.flush();
255 response = _inBound.readLine();
256
257 // see if the server suggested a good port
258 if (response.equals("ERROR")) {
259 throw new IOException("server unable to start data link at this time");
260 }
261 int port = 0;
262 try {
263 port = Integer.parseInt(response);
264 } catch (NumberFormatException e) {
265 throw new IOException("invalid data port suggested by server - " + response);
266 }
267
268 // start firewall if needed
269 _server = handleFirewall(_configuredServer, port, _dataFirewallProcess);
270 Conient.setDataStatus("Connecting to - " + _server + ":" + response);
271
272 _dataLink = new Socket(_server, port);
273
274 response = _inBound.readLine();
275 if (!response.equals("OK")) {
276 throw new IOException("server reported error establishing data channel");
277 }
278
279 // if the socket was ok, then we attack our IO hooks
280 _dataInBound = new BufferedReader(new InputStreamReader(_dataLink.getInputStream()));
281 _dataOutBound = new PrintWriter(_dataLink.getOutputStream());
282 Conient.setDataStatus("Connection Established - " + _server);
283 // now we want to start reading the data in
284 // so we start the appropriate components on their way
285 // we create a queue to give to both the reader and the
286 // displayer
287 Queue theQueue = new Queue();
288 _dataReader = new DataReader(_dataInBound, theQueue);
289 _data.setQueue(theQueue);
290 _data.cleanUpTabs();
291
292 // start the data rocking
293 new Thread(_data).start();
294 _dataReader.start();
295 // finished for us....
296 } catch (IOException e) {
297 // print the error and tidy up what's left
298 Conient.addMessage("ERROR{data link}: " + e);
299 _dataLink = null;
300 // and the firewall handler if there is one
301 closeFirewall(_dataFirewallProcess);
302 _actionQueue.clearQueue(_myQueue);
303 Conient.setDataStatus("Disconnected");
304 }
305 }
306 } else {
307 Conient.addMessage("WARNING{data link}: already established");
308 }
309 break;
310 case STOPDATA:
311 if(_dataLink != null) {
312 try {
313 String response;
314 // shut down the data link
315 Conient.setDataStatus("Disconnecting - " + _server);
316
317 // close the reader
318 _dataReader.shutdown();
319 // wait for it to close
320 boolean dirtyShutdown = true;
321 long startTime = System.currentTimeMillis();
322 while((System.currentTimeMillis() - startTime) < (DATAREADER_SHUTDOWN_TIMEOUT * 1000)) {
323 if (!_dataReader.isAlive()) {
324 dirtyShutdown = false;
325 break;
326 }
327 }
328 // warn if it didn't shutdown in time
329 if (dirtyShutdown) {
330 Conient.addMessage("WARNING{data link}: data reader thread did not close within timeout, killing its IO anyway!");
331 }
332
333 // tell the server
334 _outBound.println("STOPDATA");
335 _outBound.flush();
336 response = _inBound.readLine();
337
338 // check the server was ok with our request...
339 // even if it wasn't we will go anyway!
340 if (!response.equals("OK")) {
341 throw new IOException("server didn't OK request to stop data channel - stopping anyway");
342 }
343
344
345 // close the lot down
346 _dataInBound.close();
347 _dataOutBound.close();
348 _dataLink.close();
349 // get rid of the socket
350 _dataLink = null;
351
352 // and the firewall handler if there is one
353 closeFirewall(_dataFirewallProcess);
354
355 Conient.setDataStatus("Disconnected");
356 } catch (IOException e) {
357 // print the error and tidy up what's left
358 Conient.addMessage("ERROR{data link}: " + e);
359 try {
360 _dataOutBound.close();
361 _dataInBound.close();
362 _dataLink.close();
363 // and the firewall handler if there is one
364 closeFirewall(_dataFirewallProcess);
365 } catch (IOException e2) {
366 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
367 }
368 _dataLink = null;
369 _actionQueue.clearQueue(_myQueue);
370 Conient.setDataStatus("Disconnected");
371 }
372 } else {
373 Conient.addMessage("WARNING{data link}: already disconnected");
374 }
375 break;
376 case DISCONNECT:
377 if (_controlLink != null) {
378 if (_dataLink != null) {
379 // we want to tell ourselves to stop it
380 Conient.addMessage("WARNING{control link}: data link not disconnected - queueing stop events");
381 _actionQueue.add(new Integer(STOPDATA));
382 _actionQueue.add(new Integer(DISCONNECT));
383 } else {
384 try {
385 // request the server to disconnect
386 String response;
387 _outBound.println("DISCONNECT");
388 _outBound.flush();
389 response = _inBound.readLine();
390
391 // check the server was ok with our request...
392 // even if it wasn't we will go anyway!
393 if (!response.equals("OK")) {
394 throw new IOException("server didn't OK request to stop control channel - stopping anyway");
395 }
396
397 // then lets shutdown the link
398 Conient.setControlStatus("Disconnecting - " + _server);
399 _inBound.close();
400 _outBound.close();
401 _controlLink.close();
402 // for good measure
403 _controlLink = null;
404
405 // and the firewall handler if there is one
406 closeFirewall(_controlFirewallProcess);
407
408 Conient.setControlStatus("Disconnected");
409 } catch (IOException e) {
410 Conient.addMessage("ERROR{control link}: " + e);
411 try {
412 _inBound.close();
413 _outBound.close();
414 _controlLink.close();
415 // and the firewall handler if there is one
416 closeFirewall(_controlFirewallProcess);
417 } catch (IOException e2) {
418 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
419 }
420 _controlLink = null;
421 _actionQueue.clearQueue(_myQueue);
422 Conient.setControlStatus("Disconnected");
423 }
424 }
425 } else {
426 Conient.addMessage("WARNING{control link}: already disconnected");
427 }
428 break;
429 case QUIT:
430 Conient.addMessage("Exiting.");
431 try {
432 // stop data and control if data up
433 if (_dataLink != null) {
434 _actionQueue.add(new Integer(STOPDATA));
435 _actionQueue.add(new Integer(DISCONNECT));
436 _actionQueue.add(new Integer(QUIT));
437 throw new IOException();
438 }
439 // stop control
440 if (_controlLink != null) {
441 _actionQueue.add(new Integer(DISCONNECT));
442 _actionQueue.add(new Integer(QUIT));
443 throw new IOException();
444 }
445 Conient.addMessage("Finished.");
446 // go!
447 System.exit(0);
448 } catch (IOException e) {
449 Conient.addMessage("WARNING: open connections detected - queueing stop events");
450 }
451 break;
452 case GETCONFIGURATION:
453 getConfigFromServer();
454 break;
455 }
456 }
457 }
458
459 /**
460 * This method allows other classes
461 * to shutdown this connection handler.
462 */
463 public void shutdown() {
464 _running = false;
465 }
466
467 //---PRIVATE METHODS---
468
469 /**
470 * This method performs the STARTCONFIG command on
471 * the server. this method is called after a CONNECT,
472 * and when a GETCONFIGURATION command is called.
473 */
474 private void getConfigFromServer() {
475 if(_controlLink != null) {
476 try {
477 String response = null;
478 _outBound.println("STARTCONFIG");
479 _outBound.flush();
480 response = _inBound.readLine();
481 if (!response.equals("OK")) {
482 throw new IOException("server refused (" + response + ")");
483 }
484 _configuration.readServerConfiguration(_inBound, _outBound);
485 _outBound.println("ENDCONFIG");
486 _outBound.flush();
487 response = _inBound.readLine();
488 if (!response.equals("OK")) {
489 throw new IOException("server reported error when finishing configuration");
490 }
491 } catch (IOException e) {
492 Conient.addMessage("ERROR{control link}: when getting configuration - " + e);
493 }
494 }
495 }
496
497 /**
498 * Handles opening pipes through firewalls.
499 * It checks the configuration for various entries
500 * to set up the link or not.
501 *
502 * If a link is setup it will return the name of the
503 * new server to connect to, if a link is NOT setup,
504 * it will simply return the original server name.
505 *
506 * The name of the machine that the pipe is setup on
507 * defaults to "localhost", unless the configuration
508 * specifies otherwise.
509 *
510 * Once it has run the firewall command it then waits
511 * a set period according to the config for the firewall
512 * pipe to be set up.
513 *
514 * The firewall process should be destroyed when the
515 * link is finished with.
516 *
517 * @param server the name of the server to open up the pipe to
518 * @param port the port number to open up the pipe to
519 * @param firewallProcess the holder for the new firewall process
520 * @return the server to connect to, as determined by this routine
521 */
522 private String handleFirewall(String server, int port, Process firewallProcess) {
523 String firewallCommand = _configuration.getProperty("firewall.command");
524 String firewallCommandWait = _configuration.getProperty("firewall.commandwait");
525 String firewallServer = _configuration.getProperty("firewall.server");
526 // if we are running firewall support...then lets start it
527 if (firewallCommand != null) {
528 // clean up the command with what we want
529 firewallCommand = replaceText(firewallCommand, "%PORT%", new Integer(port).toString());
530 firewallCommand = replaceText(firewallCommand, "%SERVER%", server);
531 Conient.addMessage("WARNING{firewall}: firewall pipes requested, running pipe setup command \"" + firewallCommand + "\"");
532 try {
533 // run the command
534 firewallProcess = Runtime.getRuntime().exec(firewallCommand);
535
536 // work out how long we should wait before carrying on
537 int time = 0;
538 try {
539 time = Integer.parseInt(firewallCommandWait);
540 } catch (NumberFormatException e) {
541 time = 0;
542 }
543 if (time == 0) {
544 time = DEFAULT_FIREWALL_COMMANDWAIT;
545 }
546 Conient.addMessage("WARNING{firewall}: waiting " + time + " seconds for command to complete!");
547 // wait for the command to finish
548 try {
549 Thread.sleep(time * 1000);
550 } catch (InterruptedException e) {
551 }
552
553 // set the server we want to return
554 server = firewallServer;
555 if (server == null) {
556 server = DEFAULT_FIREWALL_SERVER;
557 }
558 } catch (IOException e) {
559 Conient.addMessage("ERROR{firewall}: unable to start pipe to i-scream server");
560 }
561 }
562 return server;
563 }
564
565 /**
566 * Checks to see if the given firewall process
567 * has been created. If it has it calls destroy()
568 * on it.
569 *
570 * @param firewallProcess the process to check
571 */
572 private void closeFirewall(Process firewallProcess) {
573 if (firewallProcess != null) {
574 firewallProcess.destroy();
575 firewallProcess = null;
576 Conient.addMessage("WARNING{firewall}: firewall process destroyed");
577 }
578 }
579
580 /**
581 * Searches a string and replaces all occurences
582 * of the given search text with the given replacement
583 * text.
584 *
585 * @param text the text to search and replace in
586 * @param search the string to look for and replace
587 * @param replace the text to replace with
588 * @return the updated version of text
589 */
590 private String replaceText(String text, String search, String replace) {
591 StringBuffer textBuffer = new StringBuffer(text);
592 int currIndex = 0;
593 currIndex = text.indexOf(search, currIndex);
594 while(currIndex != -1) {
595 if (currIndex != -1) {
596 textBuffer.delete(currIndex, currIndex + search.length());
597 textBuffer.insert(currIndex, replace);
598 }
599 text = textBuffer.toString();
600 currIndex = text.indexOf(search, currIndex + search.length());
601 }
602 return new String(textBuffer);
603 }
604 //---ACCESSOR/MUTATOR METHODS---
605
606 //---ATTRIBUTES---
607
608 /**
609 * The state if this thread
610 */
611 private boolean _running = true;
612
613 /**
614 * A reference to the displaying class DataPanel
615 */
616 private DataPanel _data;
617
618 /**
619 * The queue that actions are added to by other parts of the system
620 */
621 private Queue _actionQueue;
622
623 /**
624 * The queue number that we are reading from on the action queue
625 */
626 private int _myQueue;
627
628 /**
629 * The control link socket
630 */
631 private Socket _controlLink;
632
633 /**
634 * The data link socket
635 */
636 private Socket _dataLink;
637
638 /**
639 * The input for the control link
640 */
641 private BufferedReader _inBound;
642
643 /**
644 * The output for the control link
645 */
646 private PrintWriter _outBound;
647
648 /**
649 * The input for the data link
650 */
651 private BufferedReader _dataInBound;
652
653 /**
654 * The output for the data link
655 */
656 private PrintWriter _dataOutBound;
657
658 /**
659 * A reference to the DataReader in use
660 */
661 private DataReader _dataReader;
662
663 /**
664 * A reference to the system configuration component
665 */
666 private Configuration _configuration = Configuration.getInstance();
667
668 /**
669 * The server we will be connecting to
670 */
671 private String _server = null;
672
673 /**
674 * The server that the config says we should connect to
675 */
676 private String _configuredServer = null;
677
678 /**
679 * The process used to start the firewall pipe
680 * for the control link.
681 */
682 private Process _controlFirewallProcess = null;
683
684 /**
685 * The process used to start the firewall pipe
686 * for the data link.
687 */
688 private Process _dataFirewallProcess = null;
689
690 //---STATIC ATTRIBUTES---
691
692 }