MonitorandoSocketsComPyGtk

Receita: Monitorando Sockets com PyGTK

Acredito que quando se cria aplicações de rede, a forma mais eficiente de lidar com conexões simultâneas seja assincronamente. No caso de Python existem basicamente duas opções para isso: os módulos asynchat/asyncore, presentes na biblioteca padrão e o Twisted (http://www.twistedmatrix.com).

No entanto, quando escrevemos aplicações cliente, com interface gráfica há uma outra opção. O princípio de funcionamento desses módulos assíncronos é exatamente o mesmo que um ambiente gráfico utiliza: eventos, dentro de uma única linha de tempo. Nada impede que nós usemos o mesmo loop da interface gráfica para manipular as conexões de rede.

Muitos desconhecem que o GTK (e outros) têm embutida uma interface de alto nível para o select() do sistema operacional, facilitando em muito o trabalho. São os métodos input_add() e input_remove(). O input_add recebe três argumentos: o socket ou arquivo a ser monitorado, uma constante indicando que condição deve ser monitorada e um método a ser chamado quando a condição for satisfeita. O método input_remove remove um socket ou arquivo que esteja sendo monitorado.

Código

   1 #!/usr/bin/env python
   2 # -*- coding:UTF-8 -*-
   3 
   4 import socket
   5 import gtk
   6 import GDK
   7 
   8 class MainWindow(gtk.GtkWindow):
   9     def __init__(self):
  10         gtk.GtkWindow.__init__(self, gtk.WINDOW_TOPLEVEL)
  11         self.connect("destroy", self.quit)
  12         self.connect("delete_event", self.quit)
  13         self.show()
  14     
  15         self.set_usize(500, 210)
  16         self.main_box = gtk.GtkVBox(False, 1)
  17         self.main_box.set_border_width(1)
  18         self.add(self.main_box)
  19         self.body()
  20         self.server()
  21         self.main_box.show()
  22 
  23     def body(self):
  24         textbox = self.textbox = gtk.GtkText()
  25         textbox.set_editable(True)
  26         self.main_box.pack_start(textbox, True, True, 0)
  27         textbox.show()
  28 
  29         entry = self.entry = gtk.GtkEntry(1024)
  30         self.main_box.pack_start(entry, True, True, 0)
  31         entry.show()
  32 
  33         send = gtk.GtkButton('Enviar')
  34         send.connect('clicked', self.send)
  35         self.main_box.pack_start(send, True, True, 0)
  36         send.show()
  37 
  38     def send(self, data=None, widget=None):
  39         text = self.entry.get_text()
  40         self.do_send(text)
  41         self.entry.set_text('')
  42 
  43         # nada de novo até aqui
  44 
  45     def do_send(self, data):
  46 
  47         # envia ''data'' para todos os clientes conectados
  48 
  49         for addr, (conn, tag) in self.clients.iteritems():
  50             conn.send(data)
  51 
  52     def server(self):
  53 
  54         # inicializa o servidor
  55 
  56         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  57         self.sock.bind(('localhost', 8000))
  58         self.sock.listen(1)
  59 
  60         # a chamada para input_read para o socket do servidor é um pouco 
  61         # diferente, tendo como callback o método self.accept
  62 
  63         self.server_tag = gtk.input_add(self.sock, GDK.INPUT_READ, \
  64                                           self.accept)
  65 
  66         # mantemos uma lista dos clientes conectados
  67 
  68         self.clients = {}
  69 
  70     def accept(self, source, condition):
  71 
  72         # método chamado quando o servidor tem um cliente 
  73         # esperando para ser aceito
  74 
  75         conn, addr = source.accept()
  76         self.insert("%s:%s conectado\n" % addr)
  77 
  78         # insere o cliente na lista e registra o método self.write
  79         # como  callback para quando existirem dados esperando
  80         # para serem lidos
  81 
  82         self.clients[addr] = (conn, gtk.input_add(conn, \
  83                                GDK.INPUT_READ, self.write))
  84 
  85     def write(self, source, condition):
  86 
  87         # método chamado quando um cliente envia dados 
  88 
  89         data = source.recv(1024)
  90         if data.strip() == 'bye' or not len(data):
  91 
  92             # se o cliente enviar um ''bye'', desconecte-o :)
  93 
  94             source.close()
  95             for addr, (conn, tag) in self.clients.iteritems():
  96                 if source is conn:
  97                     gtk.input_remove(tag)
  98                     self.insert('%s:%s desconectado\n' % addr)
  99                     del self.clients[addr]
 100                     break
 101         else:
 102             for (addr, port), (conn, tag) in self.clients.iteritems():
 103                 if source is conn:
 104                     self.insert('%s:%s >>> %s\n'%(addr, port, \
 105                                 data.strip()))
 106                     break
 107 
 108     def insert(self, data):
 109         self.textbox.insert_defaults(data)
 110 
 111     def quit(self, *args):
 112         gtk.input_remove(self.server_tag)
 113         for addr, (conn, tag) in self.clients.iteritems():
 114             gtk.input_remove(tag)
 115             conn.close()
 116         self.sock.close()
 117 
 118         self.hide()
 119         self.destroy()
 120         gtk.mainquit()
 121 
 122 if __name__ == '__main__':
 123     MainWindow()
 124     gtk.mainloop()


PedroWerneck

MonitorandoSocketsComPyGtk (editada pela última vez em 2008-09-26 14:06:43 por localhost)