import repository from arizona
[raven.git] / lib / arizona-lib / arizonacomm.py
1 #! /usr/bin/env python
2
3 """
4 <Program Name>
5    arizonacomm.py
6
7 <Started>   
8    November 7, 2005
9
10 <Author>
11    Jeffry Johnston
12
13 <Purpose>
14    Client-server communications.
15 """
16
17 import socket
18 import sys
19 import time
20 import types
21 import arizonageneral
22 import arizonareport
23 import traceback
24 from threading import Thread
25 import storklog
26
27
28 # How much to read at one time
29 BLOCK_SIZE = 1024
30
31 # End of command sequence
32 EOL = "\r\n"
33
34 # Default command start character
35 COMMAND_START = "$"
36
37 # Default pulse character (completely ignored in input)
38 PULSE = "#"
39
40 # Default escape character
41 ESCAPE = "\\"
42
43 # Replacement escape character for the lost PULSE character
44 # Will be ESCAPE + PULSE_REPLACE
45 PULSE_REPLACE = "p" # \p
46
47 glo_comm = None
48 glo_data = ""
49 glo_stop = False
50
51
52
53
54
55 def connect(host, port):
56    """ 
57    <Purpose>
58       Connects to a listening server on the given host and port.
59
60    <Arguments>
61       host:
62               Hostname or IP address of the machine to connect to.
63       port:
64               Port to connect on.
65
66    <Exceptions>
67       TypeError if a bad parameter is detected.
68       IOError if socket communications fails.
69    
70    <Side Effects>
71       Sets glo_comm and glo_data.
72
73    <Returns>
74       None.
75    """
76    global glo_comm
77
78    # check params
79    arizonageneral.check_type_simple(host, "host", str, "arizonacomm.connect")
80    arizonageneral.check_type_simple(port, "port", int, "arizonacomm.connect")
81
82    __init_session()
83
84    __check_connection()
85    if glo_comm == None:
86       try:
87          glo_comm = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
88          glo_comm.connect((host, port))
89       except socket.error, e:
90          raise IOError, e   
91
92
93
94
95
96 def listen(host, port, handler, max_pending=5):
97    """ 
98    <Purpose>
99       Listens for client connections on the given local host and port.  
100       When a connection is established, forks off a child process for the 
101       connection and passes control to the specified handler function, 
102       while the parent continues to listen for additional connections.
103
104       The handler will be passed two arguments, a str and an int: the 
105       connected ip address and port.  The handler return value will be 
106       ignored.
107
108    <Arguments>
109       port:
110               Port to listen for connections on.
111       handler:   
112               Function that handles the connection.  Should return as soon
113               as possible so that future connections may be handled 
114               promptly.  Will be passed two arguments (str and int): the 
115               connected ip address and port.  Any handler return value 
116               will be ignored.
117       max_pending:
118               Maximum allowable number of pending connections.
119
120    <Exceptions>
121       TypeError if a bad parameter is detected.
122       IOError if socket communications fails.
123    
124    <Side Effects>
125       Sets glo_comm and glo_data.
126
127    <Returns>
128       None.
129    """
130    global glo_comm
131
132    # check params
133    arizonageneral.check_type_simple(host, "host", str, "arizonacomm.listen")
134    arizonageneral.check_type_simple(port, "port", int, "arizonacomm.listen")
135    arizonageneral.check_type(handler, "handler", [types.FunctionType, types.MethodType], "arizonacomm.listen")
136    arizonageneral.check_type_simple(max_pending, "max_pending", int, "arizonacomm.listen")
137    
138    # Close the connection if one is currently open
139    __check_connection()
140    if glo_comm:
141       glo_comm.close()
142
143    # Flush any previous communication data
144    __init_session()
145
146    # Get ready to accept connections...
147    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
148    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
149    s.bind((host, port))
150    s.listen(max_pending)
151
152    while True:
153       try:
154          comm, addr = s.accept()
155       except socket.error, e:
156          # to handle signal interrupt exceptions
157          if str(e)[:3] == "(4,":
158             continue 
159          else:
160             raise IOError, e
161
162       # Set up the new session
163       glo_comm = comm
164       __init_session()
165
166       # Call the user provided function
167       handler(addr[0], addr[1])
168
169       # Close the connection if they forgot...
170       __check_connection()
171       if glo_comm:
172          glo_comm.close()
173       
174
175
176
177
178 def handle_session(commands, command_start=COMMAND_START):
179    """ 
180    <Purpose>
181       Monitors the current connection, awaiting data and commands.
182       Once a command has been detected, the data is dispatched to 
183       the function associated with the received command.  
184       
185       Compatible functions take a single string as input.  Any
186       return value is ignored.  
187       
188       Note: now turns off blocking on the socket to better catch
189       disconnections.
190    <Arguments>
191       commands:
192               Dictionary of command -> function associations, where 
193               command is the input to look for, and function is a pointer 
194               to the function to call to process the received command 
195               data.  Commands should not start with the command_start
196               character (it is added automatically).
197       command_start:
198               Command start character.  If encountered in the input, it 
199               will trigger a command, unless it is repeated, in which case
200               the command_start character is treated as input.  For 
201               example, if the command_start character is `$', then `$cmd'
202               is a command, but `$$' is treated as a regular input 
203               character `$'.     
204
205    <Exceptions>
206       TypeError if a bad parameter is detected.
207       IOError if socket communications fails.
208    
209    <Side Effects>
210       Modifies glo_data.
211
212    <Returns>
213       Returns a list of the unique items in the original list.
214    """
215    global glo_data
216    global glo_stop
217    
218    # check params
219    arizonageneral.check_type(commands, "commands", [[dict, str]], "arizonacomm.handle_session")
220    arizonageneral.check_type_simple(command_start, "command_start", str, "arizonacomm.handle_session")
221    if len(command_start) != 1:
222       raise TypeError("arizonacomm.handle_connection: command_start string must be exactly 1 character in length.")
223    for command in commands:
224       arizonageneral.check_type(commands[command], "command " + command + " in commands", [types.FunctionType, types.MethodType], "arizonacomm.handle_connection")
225
226    arizonareport.send_out(4, "[" + arizonageneral.getusername() + "] Escape character: `" + command_start + "'")
227    arizonareport.send_out(4, "[" + arizonageneral.getusername() + "] Handling commands: " + ", ".join(commands))
228
229    glo_stop = False
230    while not glo_stop:      
231       # find a command
232       i_start = 0
233       while i_start >= 0:
234          i_start = glo_data.find(command_start, i_start)
235          
236          # Found a command_start character, but is it doubled?  If it is, 
237          # then this is just data, and not the start of a command.
238          if i_start > 0 and glo_data[i_start + 1: ] == command_start:
239             # skip past the doubled command_start 
240             i_start += 2
241          else:
242             break
243
244       # was a command found? (i.e. was there an command_start character?)
245       if i_start >= 0:
246          i_end = -1
247          while i_end < 0:
248             # read command until the EOL character
249             i_end = glo_data.find(EOL, i_start + 1)
250             if i_end < 0:
251                # no command end, read another block from the connection
252                glo_data += __read().replace(PULSE, "")
253                
254          # separate command from data      
255          # original block: data, command_start, command
256          command = __unescape(glo_data[i_start + 1: i_end]) # strip off command_start
257          data = glo_data[: i_start] 
258
259          # replace doubled command_start's in data with single characters
260          data = __unescape(data.replace(command_start + command_start, command_start))
261          
262          # remove used data from global buffer (it has been processed)
263          glo_data = glo_data[i_end + len(EOL): ]
264          if commands.has_key(command):
265             arizonareport.send_out(4, "[" + arizonageneral.getusername() + "] Received command: `" + str(command) + "', with data `" + str(data) + "'.")
266             storklog.log_nest("arizonacomm", "handle_session", "command", command, data)
267             commands[command](data)
268          else:
269             # bad command sent: disconnect 
270             print "[" + arizonageneral.getusername() + "] Bad command: `" + str(command) + "', data `" + str(data) + "'.  Disconnecting."
271             storklog.log_nest("arizonacomm", "handle_session", "bad", command, data)
272             disconnect()
273             break
274       else:
275          # no command, read another block of data from the connection
276          glo_data += __read().replace(PULSE, "")
277
278    arizonareport.send_out(4, "[" + arizonageneral.getusername() + "] Session ended")
279
280
281
282
283
284 def send(command, data, command_start=COMMAND_START):
285    """ 
286    <Purpose>
287       Send a change mode command to the remote connection.
288
289    <Arguments>
290       command:
291               Command string (the new mode).  
292       data:
293               Data for the command.
294       command_start:
295               Character sent to indicate the start of a command.              
296
297    <Exceptions>
298       TypeError if a bad parameter is detected.
299       IOError if socket communications fails.
300    
301    <Side Effects>
302       None.
303
304    <Returns>
305       None.
306    """
307    # check params
308    arizonageneral.check_type_simple(command, "command", str, "arizonacomm.send")
309    arizonageneral.check_type_simple(data, "data", str, "arizonacomm.send")
310    arizonageneral.check_type_simple(command_start, "command_start", str, "arizonacomm.send")
311    if len(command_start) != 1:
312       raise TypeError("arizonacomm.send: command_start string must be exactly 1 character in length.")
313    if len(command) < 1:
314       raise TypeError("arizonacomm.send: command must be at least 1 character in length.")
315    
316    # send command
317    #arizonareport.send_out(4, "[DEBUG] Sending command: `" + command + "', with data: `" + data + "'.")
318    storklog.log_nest("arizonacomm", "send", "", command, data)
319       
320    data = data.replace(command_start, command_start + command_start)
321    buf = data + command_start + command + EOL
322    while len(buf) > 0:
323       try:
324          #arizonareport.send_out(4, "[DEBUG] Send: " + str(traceback.format_stack()))
325          sent = glo_comm.send(buf)
326       except socket.error, e:
327          raise IOError, e   
328       buf = buf[sent:]
329
330
331
332
333
334 def sendraw(data):
335    """ 
336    <Purpose>
337       Send raw data to the remote connection.
338
339    <Arguments>
340       data:
341               Data to be sent.
342
343    <Exceptions>
344       TypeError if a bad parameter is detected.
345       IOError if socket communications fails.
346    
347    <Side Effects>
348       None.
349
350    <Returns>
351       None
352    """
353    # check params
354    arizonageneral.check_type_simple(data, "data", str, "arizonacomm.send")
355
356    storklog.log_nest("arizonacomm", "sendraw", "", "", data)
357    
358    # send data
359    while len(data) > 0:
360       try:
361          sent = glo_comm.send(data)
362       except socket.error, e:
363          raise IOError, e   
364       data = data[sent:]
365
366
367
368
369 def __unescape(s):
370    escape = False
371    r = ""
372    for c in s:
373       if escape:
374          escape = False
375          if c == ESCAPE:
376             # example: \\ -> \ (doubled escape)
377             r += ESCAPE
378          elif c == PULSE_REPLACE:
379             # example: \p -> # (replace with lost PULSE)
380             r += PULSE
381          else:
382             # bad escape sequence
383             raise ValueError, "Invalid escape sequence: " + str(ESCAPE) + str(c) + ", glo_data=`" + str(glo_data) + "'"
384       elif c == ESCAPE:
385         # character is the escape character, don't do anything yet
386         escape = True
387       else:
388          # example ab -> b (last char wasn't an escape char, so print this one)
389          r += c
390        
391    # Duy Nguyen - Workaround: Problem in executing storknestrpm 
392    #if escape:
393       # if string ended with an escape character
394       #raise ValueError, "Unterminated escape sequence, glo_data=`" + str(glo_data) + "'"
395       
396    return r
397
398
399
400
401
402 def __init_session():
403    """ 
404    <Purpose>
405       Initializes a new command session.  
406
407    <Arguments>
408       None
409
410    <Exceptions>
411       None.
412    
413    <Side Effects>
414       Sets glo_stop = False, glo_data = ""
415
416    <Returns>
417       None.
418    """
419    global glo_stop
420    global glo_data
421    glo_stop = False
422    glo_data = ""
423
424
425
426
427
428 def end_session():
429    """ 
430    <Purpose>
431       End the current command session.  Note: to disconnect, use the 
432       disconnect function.
433
434    <Arguments>
435       None
436
437    <Exceptions>
438       None.
439    
440    <Side Effects>
441       Sets glo_stop = True
442
443    <Returns>
444       None.
445    """
446    global glo_stop
447    glo_stop = True
448
449
450
451
452
453 def disconnect(reason=""):
454    """ 
455    <Purpose>
456       Disconnect the current connection.  Note: to end the current session
457       without disconnecting, use end_session function.
458
459    <Arguments>
460       None
461
462    <Exceptions>
463       None.
464    
465    <Side Effects>
466       Sets glo_stop = True
467
468    <Returns>
469       None.
470    """
471    global glo_comm
472    end_session()
473    
474    try:
475       glo_comm.close()
476    except socket.error, e:
477       raise IOError, e   
478    glo_comm = None   
479    arizonareport.send_out(4, "[" + arizonageneral.getusername() + "] Disconnecting (" + str(reason) + ")")
480
481
482
483
484
485 def __check_connection():
486    """ 
487    <Purpose>
488       Checks to see if the current connection has been broken.
489
490    <Arguments>
491       None
492
493    <Exceptions>
494       None.
495    
496    <Side Effects>
497       Sets glo_comm = None if not connected.
498
499    <Returns>
500       None.
501    """
502    global glo_comm,EOL
503    if glo_comm != None:
504       try:
505          # check if we're truely alive with a blocking socket... 
506          glo_comm.sendall(PULSE)
507       except socket.error:
508          glo_comm = None
509          arizonareport.send_out(4, "[" + arizonageneral.getusername() + "] Socket error (connection closed)")
510
511
512
513
514
515 def __read():
516    """ 
517    <Purpose>
518       Reads a block of data from the current connection.
519
520    <Arguments>
521       None
522
523    <Exceptions>
524       IOError if socket communications fails.
525    
526    <Side Effects>
527       None.
528
529    <Returns>
530       Data read from the connection.
531    """
532    try:
533       data = glo_comm.recv(BLOCK_SIZE)
534       if len(data) == 0:
535          # if nothing was received then the connection has been lost
536          arizonareport.send_out(4, "[" + arizonageneral.getusername() + "] Nothing received, disconnecting")
537          disconnect()
538    except socket.error, e:
539       raise IOError, e
540
541    return data
542
543
544
545
546 #just a wrapper
547 def check_types(list,function,modulename=None):arizonageneral.check_types(list,function,modulename)
548
549 class single_conn(Thread):
550    """
551    <Purpose>
552       Wraps up the client side of arizonacomm into a single class.
553    <Author>
554       Jason Hardies
555    <Side Effects>
556       Defaults to running itself in a thread.
557    """
558    EOL='\r\n'
559    ESCAPE = "$"
560    def __init__(self,host,port,commands,handler=None,escape=ESCAPE,autostart=True,nonblocking=False):
561       """
562       <Purpose>
563          Initializes the class.
564          (see handle_session above for more details)
565       <Arguments>
566          host,port = host,port to connect to
567          commands = commands (as in the handle_session function above)
568          escape = escape character
569          autostart = whether to automatically start the thread (default: true)
570          nonblocking = whether to use nonblocking sockets or not (default: false)
571       <Exceptions>
572          TypeError if a bad parameter is detected.
573       <Side Effects>
574          if autostart is true, will start itself in a thread.
575       """
576       Thread.__init__(self)
577       self.host=host
578       self.port=port
579       self.commands=commands
580       self.escape=escape
581       self.handler=handler
582       self.connected=False
583       self.nonblocking=nonblocking
584       self.done=False
585       self.sock=None
586       self.readsleep=0.2 # see the comment in read()
587       self.retrysleep=600 # how long to sleep before attempting to reconnect
588       if handler is None:self.handler=handler=self.default_handler
589       tlist=[[host,str],[port,int],[commands,[[dict,str]]],[handler,[types.FunctionType,types.MethodType]],[escape,str],[autostart,bool],[nonblocking,bool]]
590       check_types(tlist,single_conn.__init__)
591       #DEBUG:print "single_conn:init:autostarting"
592       if autostart:self.start()
593    def stop(self):
594       """
595       <Purpose>
596          Should stop the thread.  If nonblocking is set to false, it may remain waiting for the next read.
597       """
598       #DEBUG:print "single_conn:stop:stopping"
599       self.done=True
600       self.disconnect()
601    def disconnect(self):
602       """
603       <Purpose>
604          Attempts to disconnect and reset the socket variable.
605       """
606       #DEBUG:print "single_conn:disconnect:attempting disconnect"
607       if not self.sock is None:
608          #DEBUG:print "single_conn:disconnect:attempting to close socket"
609          try:self.sock.close()
610          except:pass
611          self.sock=None
612       self.connected=False
613    def connect(self,host=None,port=None):
614       """
615       <Purpose>
616          Connects to the specified host/port.  This will be done automatically if start() or run() are called.
617       <Arguments>
618          host,port = host,port to connect to
619       <Exceptions>
620          TypeError if a bad parameter is detected.
621       <Returns>
622          A bool indicating the success of the connection.
623       """
624       #DEBUG:print "single_conn:connect:starting connect sequence",self.connected
625       if self.connected and not self.sock is None:return True
626       if not host is None:self.host=host
627       if not port is None:self.port=port
628       check_types([[host,[types.NoneType,str]],[port,[types.NoneType,int]]],single_conn.connect)
629       try:
630          self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
631          self.sock.connect((self.host, self.port))
632          #DEBUG:print "single_conn:connect:connected"
633          if self.nonblocking:self.sock.setblocking(0)
634       except socket.error, e:
635          #DEBUG:print "single_conn:connect:error:",str(e)
636          try:self.sock.close()
637          except:pass
638          self.sock=None
639          return False
640       self.connected=True
641       return True
642    def send(self,command,data,escape=ESCAPE):
643       """
644       <Purpose>
645          Sends a mesage to the host. Modeled after the send function above.
646       <Arguments>
647          command = command string (does not require a starting escape)
648          data = data string
649          escape = escape character
650       <Exceptions>
651          TypeError if a bad parameter is detected.
652       """
653       #DEBUG:print "single_conn:send:starting send"
654       # check params
655       tlist=[[command,str],[data,str],[escape,str]]
656       check_types(tlist, single_conn.send)
657       if len(escape) != 1:
658          #DEBUG:print "single_conn:send:bad escape char:",[escape]
659          raise TypeError("arizonacomm.single_conn.send: escape string must be exactly 1 character in length.")
660    
661       # send command
662       #print "[DEBUG] Sending command: `" + command + "', with data: `" + data + "'."   
663       data = data.replace(escape, escape*2)
664       if command[0]==escape:command=command[1:]
665       command=command.replace(escape,escape*2)
666       buf = data + escape + command + EOL
667       while len(buf) > 0:
668          try:
669             sent = self.sock.send(buf)
670          except socket.error, e:
671             #DEBUG:print "single_conn:send:send error",str(e)
672             raise IOError, e   
673          buf = buf[sent:]
674       #DEBUG:print "single_conn:send:message sent"
675    def recv(self,length=1024,loop=True):
676       """
677       <Purpose>
678          Receives data based on the type of connection.
679       <Arguments>
680          length = how many bytes to specify to recv.
681          loop   = whether to loop until data is received
682       <Exceptions>
683          TypeError if a bad parameter is detected.
684       <Returns>
685          None if no data (or a blocking socket received an empty string), otherwise the data.
686       """
687       #paramcheck
688       #DEBUG:print "single_conn:recv:starting recv"
689       check_types([[length,int],[loop,bool]],single_conn.recv)
690       done=False
691       data=''
692       while not done:
693          if not loop:done=True
694          if self.nonblocking:
695             try:
696                data=self.sock.recv(length)
697                if len(data) ==0:#lost connection
698                   self.disconnect()
699                   return None
700                else:return data
701             except socket.error:pass
702          else:
703             try:
704                data=self.sock.recv(length)
705                if len(data)>0:return data
706             except socket.error,e:
707                #DEBUG:print "single_conn:recv:socket error:",str(e)
708                #connection issue - disconnect!
709                self.disconnect()
710                return None
711          time.sleep(self.readsleep) #doesn't matter how small this value is, as long as it sleeps a little, that will prevent it from eating up cpu
712       return None
713    def default_handler(self,commands,recvmethod=None,escape=ESCAPE):
714       """
715       <Purpose>
716          The default handler for incoming data.
717          (see handle_session above for more details)
718       <Arguments>
719          commands = commands (as in the handle_session function above)
720          recvmethod = the method to use to receive the data (defaults to self.recv)
721          escape = escape character
722       <Exceptions>
723          TypeError if a bad parameter is detected.
724       <Side Effects>
725          Calls the methods associated with commands received.
726       """
727       global glo_restr
728       #DEBUG:print "single_conn:default_handler:starting"
729       if recvmethod is None:recvmethod=self.recv
730       # check params - note that recvmethod could be either a function or method type
731       check_types([[commands,[[dict,str]]],[recvmethod,[types.MethodType,types.FunctionType]],[escape,str]],single_conn.default_handler)
732       if len(escape) != 1:
733          raise TypeError("arizonacomm.single_conn.default_handler: escape string must be exactly 1 character in length.")
734       for command in commands:
735          arizonageneral.check_type(commands[command], "command " + command + " in commands", [types.FunctionType,types.MethodType], "arizonacomm.single_conn.default_handler")
736       
737       if not hasattr(self,'buffer'):self.buffer=''
738       connected=True #an assumption that may be changed with the first call of the recv method
739       
740       #esc=escape
741       #if escape in ".$^+*?(){}()\\[]|":esc="\\"+escape
742       #cescape=re.compile(glo_restr.replace('%s',esc),re.S)
743       
744       while connected:
745          #DEBUG:print "single_conn:default_handler:starting loop iteration"
746          data=None
747          try:data=recvmethod()
748          except:data=None
749          if data is None:
750             connected=False
751             break
752          self.buffer+=data
753          while self.EOL in self.buffer:
754             #DEBUG:print "single_conn:default_handler:starting EOL iteration"
755             cmdstr,self.buffer=self.buffer.split(self.EOL,1)
756             if not escape in cmdstr:
757                # bad command sent: disconnect
758                print "Invalid command string: `" + cmdstr + "'.  Disconnecting."   
759                connected=False
760                disconnect()
761                break
762             #find the command break
763             cmdb=len(cmdstr)
764             while cmdb!=-1:
765                cmdb=cmdstr.rfind(escape,0,cmdb-1)
766                if not cmdstr[cmdb-1]==escape:
767                   break
768             cmdstr=[cmdstr[:cmdb].replace(escape*2,escape),cmdstr[cmdb+1:].replace(escape*2,escape)]
769             #try:cmdstr=cescape.match(cmdstr).groups()
770             #except AttributeError,e:
771             #   print "Invalid command string: `" + cmdstr + "'.  Disconnecting."   
772             #   connected=False
773             #   disconnect()
774             #   break
775             #cmdstr=[cmdstr[0].replace(escape*2,escape),cmdstr[-1].replace(escape*2,escape)]
776             if not (commands.has_key(cmdstr[1]) or commands.has_key(escape+cmdstr[1])): #order is data,command -- this should also weed out the case where all the escapes are escaped (ie. no single escapes to point out the command)
777                # bad command sent: disconnect
778                print "Bad command in: `" + str(cmdstr) + "'.  Disconnecting."   
779                connected=False
780                disconnect()
781                break
782             if commands.has_key(cmdstr[1]):commands[cmdstr[1]](cmdstr[0])
783             else:commands[escape+cmdstr[1]](cmdstr[0])
784
785    def run(self):
786       """
787       <Purpose>
788          The main loop of the class.  Handles connecting, reconnecting, and calling the handler.
789       """
790       #DEBUG:print "single_conn:run:starting"
791       while not self.done:
792          #DEBUG:print "single_conn:run:loop iteration"
793          #try to connect
794          self.connect()
795          #if connected, handle the session
796          if self.connected:self.handler(self.commands,self.recv,self.escape)
797          #DEBUG:else:print "single_conn:run:no connection"
798          #if disconnected, retry in 5 min.
799          if not self.done:time.sleep(self.retrysleep)
800
801 class listener(Thread):
802    """
803    <Purpose>
804       Wraps up the host side of the arizonacomm functions (with the help of check_connection below).
805    <Author>
806       Jason Hardies
807    <Side Effects>
808       Defaults to running itself in a thread.
809    """
810    def __init__(self,port,handler=None,resethandler=None,maxpending=5,autostart=True):
811       """
812       <Purpose>
813          Initializes the class.
814       <Arguments>
815          port = port to bind to
816          handler = the method to call when accepting a new socket (defaults to default_handler method)
817          resethandler = the method to call when the listening socket has died (defaults to default_reset_handler method)
818          maxpending = the maximum queue size for incoming connections
819          autostart = whether to start the thread automatically or not (default: True)
820       <Exceptions>
821          TypeError if a bad parameter is detected.
822       <Side Effects>
823          if autostart is true, will start itself in a thread.
824       """
825       Thread.__init__(self)
826       self.port=port
827       self.handler=handler
828       self.resethandler=resethandler
829       self.maxpending=maxpending
830       self.done=False
831       self.sock=None
832       self.default_nonblocking=True
833       if handler is None:self.handler=handler=self.default_handler
834       if resethandler is None:self.resethandler=resethandler=self.default_resethandler
835       tlist=[[port,int],[handler,[types.FunctionType,types.MethodType]],[resethandler,[types.FunctionType,types.MethodType]],[maxpending,int],[autostart,bool]]
836       check_types(tlist,listener.__init__)
837       if autostart:self.start()
838    def stop(self):
839       """
840       <Purpose>
841          Called to attempt to stop the listener.
842          Note: this will not stop the socket's accept method, so this will likely not kill the thread."""
843       self.done=True
844    def default_handler(self,sock,addr):
845       """
846       <Purpose>
847          The default handler for socket accept events.
848       <Arguments>
849          sock = the socket of the new connection
850          addr = the tuple/list of the address
851       <Exceptions>
852          TypeError if a bad parameter is detected.
853       <Side Effects>
854          Creates/appends to a list attribute (connlist) and sets all sockets to nonblocking if default_nonblocking is true.
855       """
856       check_types([[sock,socket._socketobject],[addr,[tuple,list]]],listener.default_handler)
857       if not hasattr(self,'connlist'):self.connlist=[]
858       if self.default_nonblocking:sock.setblocking(0)
859       self.connlist.append((sock,addr))
860    def default_resethandler(self):
861       """
862       <Purpose>
863          The default method called when the listening socket must be reset.
864       <Side Effects>
865          closes all sockets and empties the connlist attribute if it exists.
866       """
867       #since we lost the original socket, it's probably a good idea to force everyone to reconnect.
868       if hasattr(self,'connlist'):
869          for i in self.connlist:
870             try:i[0].close()
871             except:pass
872          self.connlist=[]
873    def default_list_check(self):
874       """
875       <Purpose>
876          Checks all sockets in the connlist attribute, removing disconnected sockets from the list.
877          Note: this is best used with the default_handler and assumes nonblocking behavior when default_nonblocking is
878             set to true.
879       <Side Effects>
880          will add the connlist attribute if it doesn't exist.
881       """
882       if not hasattr(self,'connlist'):self.connlist=[]
883       i=0
884       while i<len(self.connlist):
885          if not type(self.connlist[i]) in [list,tuple] or len(self.connlist[i])<2 or self.connlist[i][0] is None:
886             self.connlist=self.connlist[:i]+self.connlist[i+1:]
887             continue
888          if not check_connection(self.connlist[i][0],self.default_nonblocking):
889             self.connlist=self.connlist[:i]+self.connlist[i+1:]
890             continue
891          i+=1
892    def run(self):
893       """
894       <Purpose>
895          The main loop - called by start() as a thread (or with autostart on), or called
896          directly to use the current thread."""
897       while not self.done:
898          #attempt to use the port
899          #would do check_connection here, but it doesn't make sense for a listening socket.
900          skiplisten=False
901          if not self.sock is None:
902             try:self.sock.close()
903             except:pass
904             self.sock = None
905          try:
906             self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
907             self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
908             self.sock.bind(("127.0.0.1", self.port))
909          except socket.error:skiplisten=True
910          #listen to the port...
911          if not skiplisten:
912             self.sock.listen(self.maxpending)
913             while not self.done:
914                try:
915                   comm, addr = self.sock.accept()
916                except socket.error, e:
917                   # to handle signal interrupt exceptions
918                   if str(e)[:3] == "(4,":
919                      continue 
920                   else:break #we'll just try resetting
921                      #raise IOError, e
922                #call the handler
923                self.handler(comm,addr)
924          #wait 5 minutes if we errored/could not get the port to try again.
925          if not self.sock is None:self.resethandler()
926          if not self.done:time.sleep(600)
927
928 def check_connection(sock=None,nonblocking=False):
929    """
930    <Purpose>
931       Checks if a given socket is still 'alive' using the given nonblocking bool value to determine the method to use. 
932       If a socket is defined, this will assume to use that socket instead of the global.
933    <Arguments>
934       sock = the socket to check (will use the global socket by default)
935       nonblocking = a bool value indicating whether or not to assume the socket has blocking turned on
936    <Exceptions>
937       TypeError if a bad parameter is detected.
938    <Side Effects>
939       if autostart is true, will start itself in a thread.
940    <Returns>
941       False if not connected, otherwise a true value.  With nonblocking set to true, this may return the
942       string received, if any.
943    """
944    global glo_comm,EOL
945    if sock is None and glo_comm is None:return False
946    if sock is None and not glo_comm is None:sock=glo_comm
947    check_types([[sock,socket._socketobject],[nonblocking,bool]],check_connection)
948    if not nonblocking:
949       try:
950          sock.sendall("####"+EOL)
951          sock.sendall("####"+EOL)
952       except socket.error:
953          return False
954    else:
955       rd=""
956       try:
957          rd=sock.recv(1024)
958          if rd is None or len(rd)==0:return False
959          return rd
960       except socket.error,e:
961           if e.args[0]==9:return False 
962    return True
963       
964