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

Diferenças para "MonitorandoSocketsComTkinter"

Diferenças entre as versões de 3 e 4
Revisão 3e 2004-07-07 16:33:57
Tamanho: 7608
Comentário:
Revisão 4e 2008-09-26 14:07:15
Tamanho: 7839
Editor: localhost
Comentário: converted to 1.6 markup
Nenhuma diferença encontrada!

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, \
 145                                       socket.SOCK_STREAM)
 146             self.sock.bind(('localhost', 8000))
 147             self.sock.listen(1)
 148 
 149             # a chamada para input_read para o socket do servidor é
 150             # um pouco diferente, tendo como callback o método
 151             # self.accept
 152             self.server_tag = self.input_add(self.sock, \
 153                                              INPUT_READ, self.accept)
 154             # mantemos uma lista dos clientes conectados
 155             self.clients = {}
 156 
 157         def accept(self, source, condition):
 158             # método chamado quando o servidor tem um cliente
 159             # esperando para ser aceito
 160 
 161             conn, addr = source.accept()
 162             self.insert("%s:%s conectado\n" % addr)
 163 
 164             # insere o cliente na lista e registra o método self.write
 165             # como callback para quando existirem dados esperando para
 166             # serem lidos.
 167 
 168             self.clients[addr] = (conn, self.input_add(conn,
 169                                                INPUT_READ, self.write))
 170         def write(self, source, condition):
 171             # método chamado quando um cliente envia dados
 172 
 173             data = source.recv(1024)
 174             if not data.strip() or data.strip() == 'bye':
 175                 # se o cliente enviar um "bye" ou uma linha em branco,
 176                 # desconecte-o
 177                 source.close()
 178 
 179                 for addr, (conn, tag) in self.clients.iteritems():
 180                     if source is conn:
 181                         self.input_remove(tag)
 182                         self.insert('%s:%s desconectado\n' % addr)
 183                         del self.clients[addr]
 184                         break
 185             else:
 186                 for (addr, port), (conn, tag) in \
 187                    self.clients.iteritems():
 188                     if source is conn:
 189                         self.insert('%s:%s>>>%s\n'%(addr, port, \
 190                                     data.strip()))
 191                         break
 192 
 193         def insert(self, data):
 194             self.textbox.insert(END, data)
 195             self.textbox.see(END)
 196 
 197         def quit(self):
 198             self.input_remove(self.server_tag)
 199             for add, (conn, tag) in self.clients.iteritems():
 200                 self.input_remove(tag)
 201                 conn.close()
 202             self.sock.close()
 203             Tkinter.Tk.destroy(self)
 204 
 205 if __name__ == "__main__":
 206     root = MainWindow()
 207     Tkinter.mainloop()


Nota: Hoje (23/11/2003) descobri meio por acaso que a Tkinter tem sim uma interface, aliás idêntica à GTk. No entanto, ela parece ser tão pouco usada que não é documentada em lugar nenhum. Eu uso Tkinter há mais de três anos e nunca tinha visto. As funções são Tkinter.tkinter.createfilehandler() e Tkinter.tkinter.deletefilehandler. Argumentos e retorno são idênticas as mesmas funções em PyGTK ou na API descrita aqui.


PedroWerneck