associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

Revisão 1e 2003-11-16 17:55:50

Excluir mensagem

MonitorandoSocketsComTkinter

Receita: Monitorando Sockets com Tkinter

Seguindo a mesma idéia da outra receita, podemos usar o loop também da Tk para monitorar a entrada ou saída de dados em sockets e arquivos. Infelizmente, a Tk não tem uma interface de alto nível, como a que existe em GTK, o que exige um pouco mais de trabalho.

Para isso, eu criei uma classe, mix-in, que fornece a interface, com a API idêntica à da GTK, permitindo obter os mesmos resultados. Basta adicionar esta classe às classes base usadas pelo seu aplicativo. Segue abaixo a classe e um exemplo:

Código

   1 #!/usr/bin/env python
   2 # -*- coding: UTF-8 -*-
   3 
   4 import select
   5 import socket
   6 import itertools
   7 
   8 # Algumas constantes usadas
   9 
  10 INPUT_READ = 0
  11 INPUT_WRITE = 1
  12 INPUT_EXCEPTION = 2
  13 
  14 _read  = {}
  15 _write = {}
  16 _error = {}
  17 
  18 _modes = [_read, _write, _error]
  19 _select = select.select
  20 
  21 _tags = itertools.count()
  22 
  23 class _Wrapper(object):
  24     # Classe wrapper para os sockets ou arquivos usados
  25     
  26     def __init__(self, source, callback):
  27         self.source = source
  28         self.fileno = source.fileno
  29         self.close = source.close
  30         self.callback = callback
  31 
  32 
  33 class TkSelectMixIn:
  34     # Classe wrapper para select.select().
  35 
  36     def input_add(self, source, mode, callback):
  37         # Insere um objeto na pilha e retorna um identificador
  38         #
  39         # source é o objeto, normalmente um socket
  40         #
  41         # mode é uma das constantes INPUT_READ, INPUT_WRITE ou
  42         # INPUT_EXCEPTION
  43         #
  44         # callback é o método a ser chamado quando a condição
  45         # monitorada for satisfeita
  46 
  47         if mode not in (0, 1, 2):
  48             raise ValueError("modo inválido")
  49 
  50         tag = _tags.next()
  51         _modes[mode][tag] = _Wrapper(source, callback)
  52 
  53         return tag
  54 
  55     def input_remove(self, tag):
  56         # Remove um objeto da pilha. key é o identificador retornado
  57         # quando o objeto foi inserido
  58         # Note que o socket NÃO É FECHADO, apenas removido
  59         
  60         for mode in _modes:
  61             if tag in mode:
  62                 mode.pop(tag)
  63                 break
  64 
  65     def _check(self):
  66         # Verifica todos os sockets sendo usados e remove quaisquer
  67         # que estejam com problemas
  68         
  69         for mode in _modes:
  70             for tag, source in mode.iteritems():
  71                 try:
  72                     _select([source], [source], [source], 0)
  73                 except: # encontramos o vilão
  74                     mode.remove(tag)
  75                     source.close()
  76         
  77     def _select(self,
  78               # Essa declaração estranha tem uma finalidade. Armazenar
  79               # as globais no namespace local, acelerando a consulta
  80               # de nomes, permitindo que este método seja executado o
  81               # mais rápido possível.
  82               _read=_read,
  83               _write=_write,
  84               _error=_error,
  85               _select=_select):
  86 
  87 
  88         while 1:
  89 
  90             # tentamos o select até não ocorrer erros
  91             try:
  92                 ready = _select(_read.values(), _write.values(),
  93                                 _error.values(), 0)
  94                 break
  95             except ValueError:
  96                 # Um socket inválido foi passado... 
  97                 self._check()
  98             except TypeError:
  99                 # Algo que não era um socket foi passado...
 100                 self._check()
 101             except socket.error, reason:
 102                 # Algum dos sockets está com problemas... 
 103                 code, msg = reason.args
 104                 if code == EINTR:
 105                     # consulte man 2 select
 106                     return
 107                 if code == EBADF:
 108                     # Socket com problemas... 
 109                     self._check()
 110                 else:
 111                     # Se chegamos aqui, realmente não sei o que ocorreu
 112                     raise
 113 
 114         for condition, mode in enumerate(ready):
 115             for source in mode:
 116                 source.callback(source.source, condition)
 117 
 118         self.after(100, self._select)
 119         
 120 
 121     def start(self):
 122         self.after(100, self._select)
 123 
 124 
 125 
 126 if __name__ == '__main__':
 127 
 128     import Tkinter
 129     from ScrolledText import ScrolledText
 130     from Tkconstants import *
 131 
 132     class MainWindow(Tkinter.Tk, TkSelectMixIn):
 133         def __init__(self):
 134             Tkinter.Tk.__init__(self)
 135             
 136             self.textbox = ScrolledText(self, bg='white')
 137             self.textbox.pack(fill=BOTH, expand=1)
 138             self.server()
 139             self.start()
 140 
 141         def server(self):
 142             # inicializa o servidor
 143 
 144             self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 145             self.sock.bind(('localhost', 8000))
 146             self.sock.listen(1)
 147 
 148             # a chamada para input_read para o socket do servidor é um pouco 
 149             # diferente, tendo como callback o método self.accept
 150             self.server_tag = self.input_add(self.sock, INPUT_READ, self.accept)
 151             # mantemos uma lista dos clientes conectados
 152             self.clients = {}
 153 
 154         def accept(self, source, condition):
 155             # método chamado quando o servidor tem um cliente
 156             # esperando para ser aceito
 157 
 158             conn, addr = source.accept()
 159             self.insert("%s:%s conectado\n" % addr)
 160 
 161             # insere o cliente na lista e registra o método self.write
 162             # como callback para quando existirem dados esperando para
 163             # serem lidos.
 164 
 165             self.clients[addr] = (conn, self.input_add(conn, INPUT_READ, 
 166                                                        self.write))
 167         def write(self, source, condition):
 168             # método chamado quando um cliente envia dados
 169 
 170             data = source.recv(1024)
 171             if not data.strip() or data.strip() == 'bye':
 172                 # se o cliente enviar um "bye" ou uma linha em branco,
 173                 # desconecte-o
 174                 source.close()
 175 
 176                 for addr, (conn, tag) in self.clients.iteritems():
 177                     if source is conn:
 178                         self.input_remove(tag)
 179                         self.insert('%s:%s desconectado\n' % addr)
 180                         del self.clients[addr]
 181                         break
 182             else:
 183                 for (addr, port), (conn, tag) in self.clients.iteritems():
 184                     if source is conn:
 185                         self.insert('%s:%s>>>%s\n'%(addr, port, data.strip()))
 186                         break
 187 
 188         def insert(self, data):
 189             self.textbox.insert(END, data)
 190             self.textbox.see(END)
 191 
 192         def quit(self):
 193             self.input_remove(self.server_tag)
 194             for add, (conn, tag) in self.clients.iteritems():
 195                 self.input_remove(tag)
 196                 conn.close()
 197             self.sock.close()
 198             Tkinter.Tk.destroy(self)
 199         
 200 
 201 if __name__ == "__main__":
 202 
 203     root = MainWindow()
 204     Tkinter.mainloop()
 205             
 206         


Pedro Werneck