1. # This is the Twisted Get Poetry Now! client, version 1.0.
  2.  
  3. # NOTE: This should not be used as the basis for production code.
  4. # It uses low-level Twisted APIs as a learning exercise.
  5.  
  6. import datetime, errno, optparse, socket
  7.  
  8. from twisted.internet import main
  9.  
  10.  
  11. def parse_args():
  12.     usage = """usage: %prog [options] [hostname]:port ...
  13.  
  14. This is the Get Poetry Now! client, Twisted version 1.0.
  15. Run it like this:
  16.  
  17.  python get-poetry.py port1 port2 port3 ...
  18.  
  19. If you are in the base directory of the twisted-intro package,
  20. you could run it like this:
  21.  
  22.  python twisted-client-1/get-poetry.py 10001 10002 10003
  23.  
  24. to grab poetry from servers on ports 10001, 10002, and 10003.
  25.  
  26. Of course, there need to be servers listening on those ports
  27. for that to work.
  28. """
  29.  
  30.     parser = optparse.OptionParser(usage)
  31.  
  32.     _, addresses = parser.parse_args()
  33.  
  34.     if not addresses:
  35.         print parser.format_help()
  36.         parser.exit()
  37.  
  38.     def parse_address(addr):
  39.         if ':' not in addr:
  40.             host = ''
  41.             port = addr
  42.         else:
  43.             host, port = addr.split(':', 1)
  44.  
  45.         if not port.isdigit():
  46.             parser.error('Ports must be integers.')
  47.  
  48.         return host, int(port)
  49.  
  50.     return map(parse_address, addresses)
  51.  
  52.  
  53. class PoetrySocket(object):
  54.  
  55.     poem = ''
  56.  
  57.     def __init__(self, task_num, address):
  58.         self.task_num = task_num
  59.         self.address = address
  60.         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  61.         try:
  62.             self.sock.connect(address)
  63.         except:
  64.             pass
  65.  
  66.         self.sock.setblocking(0)
  67.  
  68.         # tell the Twisted reactor to monitor this socket for reading
  69.         from twisted.internet import reactor
  70.         reactor.addReader(self)
  71.  
  72.     def fileno(self):
  73.         try:
  74.             return self.sock.fileno()
  75.         except socket.error:
  76.             return -1
  77.  
  78.     def connectionLost(self, reason):
  79.         self.sock.close()
  80.  
  81.         # stop monitoring this socket
  82.         from twisted.internet import reactor
  83.         reactor.removeReader(self)
  84.  
  85.         # see if there are any poetry sockets left
  86.         for reader in reactor.getReaders():
  87.             if isinstance(reader, PoetrySocket):
  88.                 return
  89.  
  90.         reactor.stop() # no more poetry
  91.  
  92.     def doRead(self):
  93.         bytes = ''
  94.  
  95.         while True:
  96.             try:
  97.                 bytes += self.sock.recv(1024)
  98.                 if not bytes:
  99.                     break
  100.             except socket.error, e:
  101.                 if e.args[0] == errno.EWOULDBLOCK:
  102.                     break
  103.                 return main.CONNECTION_LOST
  104.  
  105.         if not bytes:
  106.             print 'Task %d finished' % self.task_num
  107.             return main.CONNECTION_DONE
  108.         else:
  109.             msg = 'Task %d: got %d bytes of poetry from %s'
  110.             print  msg % (self.task_num, len(bytes), self.format_addr())
  111.  
  112.         self.poem += bytes
  113.  
  114.     def logPrefix(self):
  115.         return 'poetry'
  116.  
  117.     def format_addr(self):
  118.         host, port = self.address
  119.         return '%s:%s' % (host or '127.0.0.1', port)
  120.  
  121.  
  122. def poetry_main():
  123.     addresses = parse_args()
  124.  
  125.     start = datetime.datetime.now()
  126.  
  127.     sockets = [PoetrySocket(i + 1, addr) for i, addr in enumerate(addresses)]
  128.  
  129.     from twisted.internet import reactor
  130.     reactor.run()
  131.  
  132.     elapsed = datetime.datetime.now() - start
  133.  
  134.     for i, sock in enumerate(sockets):
  135.         print 'Task %d: %d bytes of poetry' % (i + 1, len(sock.poem))
  136.  
  137.     print 'Got %d poems in %s' % (len(addresses), elapsed)
  138.  
  139.  
  140. if __name__ == '__main__':
  141.     poetry_main()