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()