1717Note that read_all() won't read until eof -- it just reads some data
1818-- but it guarantees to read at least one byte unless EOF is hit.
1919
20- It is possible to pass a Telnet object to select.select() in order to
21- wait until more data is available. Note that in this case,
22- read_eager() may return b'' even if there was data on the socket,
23- because the protocol negotiation may have eaten the data. This is why
24- EOFError is needed in some cases to distinguish between "no data" and
25- "connection closed" (since the socket also appears ready for reading
26- when it is closed).
20+ It is possible to pass a Telnet object to a selector in order to wait until
21+ more data is available. Note that in this case, read_eager() may return b''
22+ even if there was data on the socket, because the protocol negotiation may have
23+ eaten the data. This is why EOFError is needed in some cases to distinguish
24+ between "no data" and "connection closed" (since the socket also appears ready
25+ for reading when it is closed).
2726
2827To do:
2928- option negotiation
3433
3534
3635# Imported modules
37- import errno
3836import sys
3937import socket
40- import select
38+ import selectors
4139
4240__all__ = ["Telnet" ]
4341
130128EXOPL = bytes ([255 ]) # Extended-Options-List
131129NOOPT = bytes ([0 ])
132130
131+
132+ # poll/select have the advantage of not requiring any extra file descriptor,
133+ # contrarily to epoll/kqueue (also, they require a single syscall).
134+ if hasattr (selectors , 'PollSelector' ):
135+ _TelnetSelector = selectors .PollSelector
136+ else :
137+ _TelnetSelector = selectors .SelectSelector
138+
139+
133140class Telnet :
134141
135142 """Telnet interface class.
@@ -206,7 +213,6 @@ def __init__(self, host=None, port=0,
206213 self .sb = 0 # flag for SB and SE sequence.
207214 self .sbdataq = b''
208215 self .option_callback = None
209- self ._has_poll = hasattr (select , 'poll' )
210216 if host is not None :
211217 self .open (host , port , timeout )
212218
@@ -288,61 +294,6 @@ def read_until(self, match, timeout=None):
288294 possibly the empty string. Raise EOFError if the connection
289295 is closed and no cooked data is available.
290296
291- """
292- if self ._has_poll :
293- return self ._read_until_with_poll (match , timeout )
294- else :
295- return self ._read_until_with_select (match , timeout )
296-
297- def _read_until_with_poll (self , match , timeout ):
298- """Read until a given string is encountered or until timeout.
299-
300- This method uses select.poll() to implement the timeout.
301- """
302- n = len (match )
303- call_timeout = timeout
304- if timeout is not None :
305- from time import time
306- time_start = time ()
307- self .process_rawq ()
308- i = self .cookedq .find (match )
309- if i < 0 :
310- poller = select .poll ()
311- poll_in_or_priority_flags = select .POLLIN | select .POLLPRI
312- poller .register (self , poll_in_or_priority_flags )
313- while i < 0 and not self .eof :
314- try :
315- ready = poller .poll (call_timeout )
316- except OSError as e :
317- if e .errno == errno .EINTR :
318- if timeout is not None :
319- elapsed = time () - time_start
320- call_timeout = timeout - elapsed
321- continue
322- raise
323- for fd , mode in ready :
324- if mode & poll_in_or_priority_flags :
325- i = max (0 , len (self .cookedq )- n )
326- self .fill_rawq ()
327- self .process_rawq ()
328- i = self .cookedq .find (match , i )
329- if timeout is not None :
330- elapsed = time () - time_start
331- if elapsed >= timeout :
332- break
333- call_timeout = timeout - elapsed
334- poller .unregister (self )
335- if i >= 0 :
336- i = i + n
337- buf = self .cookedq [:i ]
338- self .cookedq = self .cookedq [i :]
339- return buf
340- return self .read_very_lazy ()
341-
342- def _read_until_with_select (self , match , timeout = None ):
343- """Read until a given string is encountered or until timeout.
344-
345- The timeout is implemented using select.select().
346297 """
347298 n = len (match )
348299 self .process_rawq ()
@@ -352,27 +303,26 @@ def _read_until_with_select(self, match, timeout=None):
352303 buf = self .cookedq [:i ]
353304 self .cookedq = self .cookedq [i :]
354305 return buf
355- s_reply = ([self ], [], [])
356- s_args = s_reply
357306 if timeout is not None :
358- s_args = s_args + (timeout ,)
359307 from time import time
360- time_start = time ()
361- while not self .eof and select .select (* s_args ) == s_reply :
362- i = max (0 , len (self .cookedq )- n )
363- self .fill_rawq ()
364- self .process_rawq ()
365- i = self .cookedq .find (match , i )
366- if i >= 0 :
367- i = i + n
368- buf = self .cookedq [:i ]
369- self .cookedq = self .cookedq [i :]
370- return buf
371- if timeout is not None :
372- elapsed = time () - time_start
373- if elapsed >= timeout :
374- break
375- s_args = s_reply + (timeout - elapsed ,)
308+ deadline = time () + timeout
309+ with _TelnetSelector () as selector :
310+ selector .register (self , selectors .EVENT_READ )
311+ while not self .eof :
312+ if selector .select (timeout ):
313+ i = max (0 , len (self .cookedq )- n )
314+ self .fill_rawq ()
315+ self .process_rawq ()
316+ i = self .cookedq .find (match , i )
317+ if i >= 0 :
318+ i = i + n
319+ buf = self .cookedq [:i ]
320+ self .cookedq = self .cookedq [i :]
321+ return buf
322+ if timeout is not None :
323+ timeout = deadline - time ()
324+ if timeout < 0 :
325+ break
376326 return self .read_very_lazy ()
377327
378328 def read_all (self ):
@@ -577,29 +527,35 @@ def fill_rawq(self):
577527
578528 def sock_avail (self ):
579529 """Test whether data is available on the socket."""
580- return select .select ([self ], [], [], 0 ) == ([self ], [], [])
530+ with _TelnetSelector () as selector :
531+ selector .register (self , selectors .EVENT_READ )
532+ return bool (selector .select (0 ))
581533
582534 def interact (self ):
583535 """Interaction function, emulates a very dumb telnet client."""
584536 if sys .platform == "win32" :
585537 self .mt_interact ()
586538 return
587- while 1 :
588- rfd , wfd , xfd = select .select ([self , sys .stdin ], [], [])
589- if self in rfd :
590- try :
591- text = self .read_eager ()
592- except EOFError :
593- print ('*** Connection closed by remote host ***' )
594- break
595- if text :
596- sys .stdout .write (text .decode ('ascii' ))
597- sys .stdout .flush ()
598- if sys .stdin in rfd :
599- line = sys .stdin .readline ().encode ('ascii' )
600- if not line :
601- break
602- self .write (line )
539+ with _TelnetSelector () as selector :
540+ selector .register (self , selectors .EVENT_READ )
541+ selector .register (sys .stdin , selectors .EVENT_READ )
542+
543+ while True :
544+ for key , events in selector .select ():
545+ if key .fileobj is self :
546+ try :
547+ text = self .read_eager ()
548+ except EOFError :
549+ print ('*** Connection closed by remote host ***' )
550+ return
551+ if text :
552+ sys .stdout .write (text .decode ('ascii' ))
553+ sys .stdout .flush ()
554+ elif key .fileobj is sys .stdin :
555+ line = sys .stdin .readline ().encode ('ascii' )
556+ if not line :
557+ return
558+ self .write (line )
603559
604560 def mt_interact (self ):
605561 """Multithreaded version of interact()."""
@@ -645,79 +601,6 @@ def expect(self, list, timeout=None):
645601 or if more than one expression can match the same input, the
646602 results are undeterministic, and may depend on the I/O timing.
647603
648- """
649- if self ._has_poll :
650- return self ._expect_with_poll (list , timeout )
651- else :
652- return self ._expect_with_select (list , timeout )
653-
654- def _expect_with_poll (self , expect_list , timeout = None ):
655- """Read until one from a list of a regular expressions matches.
656-
657- This method uses select.poll() to implement the timeout.
658- """
659- re = None
660- expect_list = expect_list [:]
661- indices = range (len (expect_list ))
662- for i in indices :
663- if not hasattr (expect_list [i ], "search" ):
664- if not re : import re
665- expect_list [i ] = re .compile (expect_list [i ])
666- call_timeout = timeout
667- if timeout is not None :
668- from time import time
669- time_start = time ()
670- self .process_rawq ()
671- m = None
672- for i in indices :
673- m = expect_list [i ].search (self .cookedq )
674- if m :
675- e = m .end ()
676- text = self .cookedq [:e ]
677- self .cookedq = self .cookedq [e :]
678- break
679- if not m :
680- poller = select .poll ()
681- poll_in_or_priority_flags = select .POLLIN | select .POLLPRI
682- poller .register (self , poll_in_or_priority_flags )
683- while not m and not self .eof :
684- try :
685- ready = poller .poll (call_timeout )
686- except OSError as e :
687- if e .errno == errno .EINTR :
688- if timeout is not None :
689- elapsed = time () - time_start
690- call_timeout = timeout - elapsed
691- continue
692- raise
693- for fd , mode in ready :
694- if mode & poll_in_or_priority_flags :
695- self .fill_rawq ()
696- self .process_rawq ()
697- for i in indices :
698- m = expect_list [i ].search (self .cookedq )
699- if m :
700- e = m .end ()
701- text = self .cookedq [:e ]
702- self .cookedq = self .cookedq [e :]
703- break
704- if timeout is not None :
705- elapsed = time () - time_start
706- if elapsed >= timeout :
707- break
708- call_timeout = timeout - elapsed
709- poller .unregister (self )
710- if m :
711- return (i , m , text )
712- text = self .read_very_lazy ()
713- if not text and self .eof :
714- raise EOFError
715- return (- 1 , None , text )
716-
717- def _expect_with_select (self , list , timeout = None ):
718- """Read until one from a list of a regular expressions matches.
719-
720- The timeout is implemented using select.select().
721604 """
722605 re = None
723606 list = list [:]
@@ -728,27 +611,27 @@ def _expect_with_select(self, list, timeout=None):
728611 list [i ] = re .compile (list [i ])
729612 if timeout is not None :
730613 from time import time
731- time_start = time ()
732- while 1 :
733- self . process_rawq ( )
734- for i in indices :
735- m = list [ i ]. search ( self .cookedq )
736- if m :
737- e = m . end ( )
738- text = self . cookedq [: e ]
739- self . cookedq = self . cookedq [ e :]
740- return ( i , m , text )
741- if self .eof :
742- break
743- if timeout is not None :
744- elapsed = time () - time_start
745- if elapsed >= timeout :
746- break
747- s_args = ([ self . fileno ()], [], [], timeout - elapsed )
748- r , w , x = select . select ( * s_args )
749- if not r :
750- break
751- self .fill_rawq ()
614+ deadline = time () + timeout
615+ with _TelnetSelector () as selector :
616+ selector . register ( self , selectors . EVENT_READ )
617+ while not self . eof :
618+ self .process_rawq ( )
619+ for i in indices :
620+ m = list [ i ]. search ( self . cookedq )
621+ if m :
622+ e = m . end ()
623+ text = self . cookedq [: e ]
624+ self .cookedq = self . cookedq [ e :]
625+ return ( i , m , text )
626+ if timeout is not None :
627+ ready = selector . select ( timeout )
628+ timeout = deadline - time ()
629+ if not ready :
630+ if timeout < 0 :
631+ break
632+ else :
633+ continue
634+ self .fill_rawq ()
752635 text = self .read_very_lazy ()
753636 if not text and self .eof :
754637 raise EOFError
0 commit comments