1 |
#!/usr/bin/env python |
#!/usr/bin/env python |
2 |
# |
# |
3 |
#Minë. Mundo Interactivo-Narrativo en Español [Tierra-Media] |
#Minë. Mundo Interactivo-Narrativo en Español [Tierra-Media] |
4 |
#Copyright (C) 2002 Pablo Ruiz Múzquiz |
#Copyright (C) 2002 Pablo Ruiz Múzquiz |
5 |
# |
# |
6 |
# |
# |
7 |
#This program is free software; you can redistribute it and/or modify |
#This program is free software; you can redistribute it and/or modify |
8 |
#it under the terms of the GNU General Public License as published by |
#it under the terms of the GNU General Public License as published by |
9 |
#the Free Software Foundation; either version 2 of the License, or |
#the Free Software Foundation; either version 2 of the License, or |
10 |
#(at your option) any later version. |
#(at your option) any later version. |
11 |
# |
# |
12 |
#This program is distributed in the hope that it will be useful, |
#This program is distributed in the hope that it will be useful, |
13 |
#but WITHOUT ANY WARRANTY; without even the implied warranty of |
#but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
#GNU General Public License for more details. |
#GNU General Public License for more details. |
16 |
# |
# |
17 |
#You should have received a copy of the GNU General Public License |
#You should have received a copy of the GNU General Public License |
18 |
#along with this program; if not, write to the Free Software |
#along with this program; if not, write to the Free Software |
19 |
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
20 |
# |
# |
21 |
# |
# |
22 |
#Fichero: connection.py |
#Fichero: connection.py |
23 |
|
|
24 |
"""Module to handle communication via sockets at high level. |
"""Module to handle communication via sockets at high level. |
25 |
|
|
26 |
It contains these classes: |
It contains these classes: |
27 |
- Connection |
- Connection |
28 |
- ConnectionHandler |
- ConnectionHandler |
29 |
""" |
""" |
30 |
|
|
31 |
from socket import * |
from socket import * |
32 |
from select import * |
from select import * |
33 |
from string import * |
from string import * |
34 |
|
|
35 |
class Connection: |
class Connection: |
36 |
"""An accepted connection. |
"""An accepted connection. |
37 |
|
|
38 |
It allows to send and receive lines of text messages from a client. |
It allows to send and receive lines of text messages from a client. |
39 |
|
|
40 |
Do not directly instantiate it, but rather use instances returned |
Do not directly instantiate it, but rather use instances returned |
41 |
by the accept() method of ConnectionHandler. |
by the accept() method of ConnectionHandler. |
42 |
|
|
43 |
The Connection has two 'callback' function that the user of this |
The Connection has two 'callback' function that the user of this |
44 |
class must provide: |
class must provide: |
45 |
- One will be called every time a line of text arrives. |
- One will be called every time a line of text arrives. |
46 |
- The other will be called if there is an error or disconnection, |
- The other will be called if there is an error or disconnection, |
47 |
and the connection has been closed. |
and the connection has been closed. |
48 |
""" |
""" |
49 |
|
|
50 |
def __init__(self, handler, socket, address, receive_callback, error_callback): |
def __init__(self, handler, socket, address, receive_callback, error_callback): |
51 |
"""Only to be called from ConnectionHandler""" |
"""Only to be called from ConnectionHandler""" |
52 |
self.handler = handler |
self.handler = handler |
53 |
self.socket = socket |
self.socket = socket |
54 |
self.ip = address[0] |
self.ip = address[0] |
55 |
|
|
56 |
#This string collects incoming data, for the benefit of clients that pass |
#This string collects incoming data, for the benefit of clients that pass |
57 |
#all typed information as it arrives as opposed to waiting for a \n. |
#all typed information as it arrives as opposed to waiting for a \n. |
58 |
self.dataqueue = "" |
self.dataqueue = "" |
59 |
|
|
60 |
#This string holds a complete line of received data. Is not modified |
#This string holds a complete line of received data. Is not modified |
61 |
#until a "\n" is stored into dataqueue. |
#until a "\n" is stored into dataqueue. |
62 |
self.input = "" |
self.input = "" |
63 |
|
|
64 |
#This is the function that will be called to notify that incoming |
#This is the function that will be called to notify that incoming |
65 |
#data is available to read with receive(). One parameter will be |
#data is available to read with receive(). One parameter will be |
66 |
#passed to the function: this connection. |
#passed to the function: this connection. |
67 |
self.receive_callback = receive_callback |
self.receive_callback = receive_callback |
68 |
|
|
69 |
#This is the function that will be called to notify that an error |
#This is the function that will be called to notify that an error |
70 |
#has occured and the connection is closed. One parameter will be |
#has occured and the connection is closed. One parameter will be |
71 |
#passed to the function: this connection. |
#passed to the function: this connection. |
72 |
self.error_callback = error_callback |
self.error_callback = error_callback |
73 |
|
|
74 |
self.is_open = 1 |
self.is_open = 1 |
75 |
|
|
76 |
|
|
77 |
def receive(self): |
def receive(self): |
78 |
"""Return the input data received from the client. |
"""Return the input data received from the client. |
79 |
|
|
80 |
Use this only when the callback has been called to notify |
Use this only when the callback has been called to notify |
81 |
that a line of text has been received. The returned data |
that a line of text has been received. The returned data |
82 |
will be ended with a \\n.""" |
will be ended with a \\n.""" |
83 |
return self.input |
return self.input |
84 |
|
|
85 |
|
|
86 |
def send_paginado(self, string, end_of_line = 0): |
def send_paginado(self, string, end_of_line = 0): |
87 |
"""Pagina un documento al cliente. |
"""Pagina un documento al cliente. |
88 |
|
|
89 |
si end_of_line == 1, un final de línea se añade al string""" |
si end_of_line == 1, un final de línea se añade al string""" |
90 |
|
|
91 |
contador_lineas = 0 |
contador_lineas = 0 |
92 |
parada = 1 |
parada = 1 |
93 |
for linea in string: |
for linea in string: |
94 |
contador_lineas += 1 |
contador_lineas += 1 |
95 |
if contador_lineas % 20 == 0: |
if contador_lineas % 20 == 0: |
96 |
self.socket.send("\n---- presiona ENTER ----\n\n") |
self.socket.send("\n---- presiona ENTER ----\n\n") |
97 |
# while parada == 1: |
# while parada == 1: |
98 |
|
|
99 |
# stop = raw_input(' ') |
# stop = raw_input(' ') |
100 |
# if stop == 'sigue\n': parada = 0 |
# if stop == 'sigue\n': parada = 0 |
101 |
# else: pass |
# else: pass |
102 |
|
|
103 |
# else: |
# else: |
104 |
|
|
105 |
self.socket.send(linea+'\n') |
self.socket.send(linea+'\n') |
106 |
if (end_of_line): self.socket.send("\n") |
if (end_of_line): self.socket.send("\n") |
107 |
|
|
108 |
|
|
109 |
def send(self, string, end_of_line = 1, width = 79): |
def send(self, string, end_of_line = 1, width = 79): |
110 |
"""Send a string of text to the client. |
"""Send a string of text to the client. |
111 |
|
|
112 |
If end_of_line == 1, an end of line is appended to the string. |
If end_of_line == 1, an end of line is appended to the string. |
113 |
If width != 0, if the text is longer than width, it is cut to |
If width != 0, if the text is longer than width, it is cut to |
114 |
this width, breaking in spaces. |
this width, breaking in spaces. |
115 |
""" |
""" |
116 |
try: #si todos se desconectan a la vez ocurre una catástrofe. mejor con try |
try: #si todos se desconectan a la vez ocurre una catástrofe. mejor con try |
117 |
# %%Andres-> esto ke es???? |
# %%Andres-> esto ke es???? |
118 |
|
|
119 |
# Loop splitting the string in pieces of length |
# Loop splitting the string in pieces of length |
120 |
# less than or equal than width. |
# less than or equal than width. |
121 |
start = 0 |
start = 0 |
122 |
end = width |
end = width |
123 |
while start < len(string): |
while start < len(string): |
124 |
|
|
125 |
if end > len(string): |
if end > len(string): |
126 |
# We have reached the end of the string. |
# We have reached the end of the string. |
127 |
end = len(string) |
end = len(string) |
128 |
else: |
else: |
129 |
# Loop back searching the first blank character. |
# Loop back searching the first blank character. |
130 |
while (end > start+1) and (string[end-1] != ' '): |
while (end > start+1) and (string[end-1] != ' '): |
131 |
end -= 1 |
end -= 1 |
132 |
|
|
133 |
# If no blanks in the string, split just at the |
# If no blanks in the string, split just at the |
134 |
# given width. |
# given width. |
135 |
if end == start+1: |
if end == start+1: |
136 |
end = start + width |
end = start + width |
137 |
|
|
138 |
# Send the piece through the socket, with new line. |
# Send the piece through the socket, with new line. |
139 |
if start > 0: self.socket.send("\n") |
if start > 0: self.socket.send("\n") |
140 |
self.socket.send(string[start:end]) |
self.socket.send(string[start:end]) |
141 |
|
|
142 |
# Advance. |
# Advance. |
143 |
start = end |
start = end |
144 |
end = start + width |
end = start + width |
145 |
|
|
146 |
# Send the last new line, if asked to do so. |
# Send the last new line, if asked to do so. |
147 |
if (end_of_line): |
if (end_of_line): |
148 |
self.socket.send("\n") |
self.socket.send("\n") |
149 |
|
|
150 |
except: |
except: |
151 |
pass #%% ??? |
pass #%% ??? |
152 |
|
|
153 |
|
|
154 |
def close(self, with_error = 0): |
def close(self, with_error = 0): |
155 |
"""Disconnect and release the connection. |
"""Disconnect and release the connection. |
156 |
|
|
157 |
If with_error is set, the disconnection is abnormal and the error callback |
If with_error is set, the disconnection is abnormal and the error callback |
158 |
is called. |
is called. |
159 |
""" |
""" |
160 |
self.is_open = 0 |
self.is_open = 0 |
161 |
self.socket.close() |
self.socket.close() |
162 |
self.handler.connected.remove(self) #remove the connection from the array |
self.handler.connected.remove(self) #remove the connection from the array |
163 |
if with_error: |
if with_error: |
164 |
self.error_callback(self) |
self.error_callback(self) |
165 |
|
|
166 |
|
|
167 |
def fileno(self): |
def fileno(self): |
168 |
"""Only to be called from ConnectionHandler. |
"""Only to be called from ConnectionHandler. |
169 |
|
|
170 |
Return the fileno of the socket. This is required to use select, |
Return the fileno of the socket. This is required to use select, |
171 |
which is used to choose which sockets have incoming data later. |
which is used to choose which sockets have incoming data later. |
172 |
""" |
""" |
173 |
return self.socket.fileno() |
return self.socket.fileno() |
174 |
|
|
175 |
|
|
176 |
def data_available(self): |
def data_available(self): |
177 |
"""Only to be called from ConnectionHandler. |
"""Only to be called from ConnectionHandler. |
178 |
|
|
179 |
This function is called when any data is available to read |
This function is called when any data is available to read |
180 |
in the socket. |
in the socket. |
181 |
""" |
""" |
182 |
try: |
try: |
183 |
# Read the pending data |
# Read the pending data |
184 |
data = self.socket.recv(1024) |
data = self.socket.recv(1024) |
185 |
except: |
except: |
186 |
#There has been an error, drop the connection. |
#There has been an error, drop the connection. |
187 |
self.close(1) |
self.close(1) |
188 |
else: |
else: |
189 |
if not data: |
if not data: |
190 |
#There is somehow no data. This shouldn't happen unless there is an |
#There is somehow no data. This shouldn't happen unless there is an |
191 |
#error of some kind, so we are going to disconnect them. |
#error of some kind, so we are going to disconnect them. |
192 |
self.close(1) |
self.close(1) |
193 |
else: |
else: |
194 |
|
|
195 |
if len(data) > 0: |
if len(data) > 0: |
196 |
if not data[-1] == '\n': |
if not data[-1] == '\n': |
197 |
#If the last character recieved is not a return, then we save the incoming |
#If the last character recieved is not a return, then we save the incoming |
198 |
#data in the buffer. Some clients like to send fragments. |
#data in the buffer. Some clients like to send fragments. |
199 |
self._enqueue(data) |
self._enqueue(data) |
200 |
else: |
else: |
201 |
#The player pressed return, so we are going to process their input. |
#The player pressed return, so we are going to process their input. |
202 |
|
|
203 |
#Add the new data to the end of the queue. |
#Add the new data to the end of the queue. |
204 |
self._enqueue(data) |
self._enqueue(data) |
205 |
|
|
206 |
#Get the entire string of input for processing from the queue. |
#Get the entire string of input for processing from the queue. |
207 |
self.input = self._queue() |
self.input = self._queue() |
208 |
|
|
209 |
#Clear the queue. |
#Clear the queue. |
210 |
self._resetqueue() |
self._resetqueue() |
211 |
|
|
212 |
#Notify to the client that there is available data. |
#Notify to the client that there is available data. |
213 |
self.receive_callback(self) |
self.receive_callback(self) |
214 |
|
|
215 |
|
|
216 |
def _enqueue(self, string): |
def _enqueue(self, string): |
217 |
"""Add an incoming string to the input buffer. |
"""Add an incoming string to the input buffer. |
218 |
|
|
219 |
This is used to collect stray input until we recieve a '\\n' |
This is used to collect stray input until we recieve a '\\n' |
220 |
carriage return. Some clients are nice, they don't send the data |
carriage return. Some clients are nice, they don't send the data |
221 |
until it is all together, but some send it by keystroke. |
until it is all together, but some send it by keystroke. |
222 |
|
|
223 |
It also processes backspace key and suppress \\r characters. |
It also processes backspace key and suppress \\r characters. |
224 |
""" |
""" |
225 |
self.dataqueue = self.dataqueue + string |
self.dataqueue = self.dataqueue + string |
226 |
|
|
227 |
i = 0 |
i = 0 |
228 |
while i < len(self.dataqueue): |
while i < len(self.dataqueue): |
229 |
if self.dataqueue[i] == '\b': |
if self.dataqueue[i] == '\b': |
230 |
if i == 0: |
if i == 0: |
231 |
self.dataqueue = self.dataqueue[1:] |
self.dataqueue = self.dataqueue[1:] |
232 |
else: |
else: |
233 |
self.dataqueue = self.dataqueue[:i-1] + \ |
self.dataqueue = self.dataqueue[:i-1] + \ |
234 |
self.dataqueue[i+1:] |
self.dataqueue[i+1:] |
235 |
i = i - 1 |
i = i - 1 |
236 |
elif self.dataqueue[i] == '\r': |
elif self.dataqueue[i] == '\r': |
237 |
if i == 0: |
if i == 0: |
238 |
self.dataqueue = self.dataqueue[1:] |
self.dataqueue = self.dataqueue[1:] |
239 |
else: |
else: |
240 |
self.dataqueue = self.dataqueue[:i] + \ |
self.dataqueue = self.dataqueue[:i] + \ |
241 |
self.dataqueue[i+1:] |
self.dataqueue[i+1:] |
242 |
else: |
else: |
243 |
i = i + 1 |
i = i + 1 |
244 |
|
|
245 |
|
|
246 |
def _queue(self): |
def _queue(self): |
247 |
"""Simply return the contents of the input buffer.""" |
"""Simply return the contents of the input buffer.""" |
248 |
return self.dataqueue |
return self.dataqueue |
249 |
|
|
250 |
|
|
251 |
def _resetqueue(self): |
def _resetqueue(self): |
252 |
"""Clear the input buffer.""" |
"""Clear the input buffer.""" |
253 |
self.dataqueue = "" |
self.dataqueue = "" |
254 |
|
|
255 |
|
|
256 |
class ConnectionHandler: |
class ConnectionHandler: |
257 |
"""A server that listens to a TCP port and accepts connection |
"""A server that listens to a TCP port and accepts connection |
258 |
requests from clients. |
requests from clients. |
259 |
|
|
260 |
The ConnectionHandler has one 'callback' function that the user |
The ConnectionHandler has one 'callback' function that the user |
261 |
of this class must provide. It will be called every time a new |
of this class must provide. It will be called every time a new |
262 |
client requests a connection. |
client requests a connection. |
263 |
""" |
""" |
264 |
|
|
265 |
def __init__(self, host, port, new_connection_callback): |
def __init__(self, host, port, new_connection_callback): |
266 |
"""Creation of a new ConnectionHandler. |
"""Creation of a new ConnectionHandler. |
267 |
|
|
268 |
host -- the hostname or IP address of this host (optional) |
host -- the hostname or IP address of this host (optional) |
269 |
port -- the TCP port to listen in |
port -- the TCP port to listen in |
270 |
new_connection_callback -- a function or method to call |
new_connection_callback -- a function or method to call |
271 |
on new connection requests |
on new connection requests |
272 |
""" |
""" |
273 |
|
|
274 |
#Set up an array to contain the connections. |
#Set up an array to contain the connections. |
275 |
self.connected = [ ] |
self.connected = [ ] |
276 |
|
|
277 |
#Set up the listening socket. All incoming connections are picked up here, |
#Set up the listening socket. All incoming connections are picked up here, |
278 |
#then passed off to a new connection. |
#then passed off to a new connection. |
279 |
self.listener = socket(AF_INET, SOCK_STREAM) |
self.listener = socket(AF_INET, SOCK_STREAM) |
280 |
self.listener.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) |
self.listener.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) |
281 |
self.listener.bind((host, port)) |
self.listener.bind((host, port)) |
282 |
self.listener.listen(32) |
self.listener.listen(32) |
283 |
|
|
284 |
#This is the function that will be called to notify that a new |
#This is the function that will be called to notify that a new |
285 |
#connection has been requested, and may be accepted with accept() |
#connection has been requested, and may be accepted with accept() |
286 |
self.new_connection_callback = new_connection_callback |
self.new_connection_callback = new_connection_callback |
287 |
|
|
288 |
|
|
289 |
def accept(self, receive_callback, error_callback): |
def accept(self, receive_callback, error_callback): |
290 |
"""Accept a request and return a new connection with a client. |
"""Accept a request and return a new connection with a client. |
291 |
|
|
292 |
Use this only when the callback has been called to notify |
Use this only when the callback has been called to notify |
293 |
that a client is requesting connection.""" |
that a client is requesting connection.""" |
294 |
|
|
295 |
#We have a new connection, add it to connected[] |
#We have a new connection, add it to connected[] |
296 |
socket, address = self.listener.accept() |
socket, address = self.listener.accept() |
297 |
conn = Connection(self, socket, address, receive_callback, error_callback) |
conn = Connection(self, socket, address, receive_callback, error_callback) |
298 |
self.connected.append(conn) |
self.connected.append(conn) |
299 |
|
|
300 |
return conn |
return conn |
301 |
|
|
302 |
|
|
303 |
def run(self): |
def run(self): |
304 |
"""Cycle once through each connection, calling all the callbacks. |
"""Cycle once through each connection, calling all the callbacks. |
305 |
|
|
306 |
This is run from a loop within the main program. |
This is run from a loop within the main program. |
307 |
""" |
""" |
308 |
|
|
309 |
#We want to check both the list of active connections, and the listening socket. |
#We want to check both the list of active connections, and the listening socket. |
310 |
# ...so we create a variable that combines the two. Lets call it 'ready'. |
# ...so we create a variable that combines the two. Lets call it 'ready'. |
311 |
ready = self.connected[:] |
ready = self.connected[:] |
312 |
ready.append(self.listener) |
ready.append(self.listener) |
313 |
|
|
314 |
#We only want to check the ones that have incoming data, however. |
#We only want to check the ones that have incoming data, however. |
315 |
# ...which is what select() does. In this case, it returns the members |
# ...which is what select() does. In this case, it returns the members |
316 |
# of ready that have data waiting. |
# of ready that have data waiting. |
317 |
# |
# |
318 |
# The 0.1 is important. It is an optional argument, if it is omitted the |
# The 0.1 is important. It is an optional argument, if it is omitted the |
319 |
# process will block, meaning it will wait until there is data to be processed |
# process will block, meaning it will wait until there is data to be processed |
320 |
# before it continues. Specifying a number other than 0 will block for that many |
# before it continues. Specifying a number other than 0 will block for that many |
321 |
# seconds, to see if any data comes in. |
# seconds, to see if any data comes in. |
322 |
ready = select(ready, [], [], 0.1)[0] |
ready = select(ready, [], [], 0.1)[0] |
323 |
|
|
324 |
#Cycle through each connection once. |
#Cycle through each connection once. |
325 |
for conn in ready: |
for conn in ready: |
326 |
if conn is self.listener: |
if conn is self.listener: |
327 |
#We have a new connection request, notify to the client |
#We have a new connection request, notify to the client |
328 |
self.new_connection_callback(self) |
self.new_connection_callback(self) |
329 |
else: |
else: |
330 |
#Activate the connection |
#Activate the connection |
331 |
conn.data_available() |
conn.data_available() |
332 |
|
|
333 |
|
|
334 |
def close_all(self): |
def close_all(self): |
335 |
"""Close and destroy all connections.""" |
"""Close and destroy all connections.""" |
336 |
for conn in self.connected[:]: |
for conn in self.connected[:]: |
337 |
conn.close(1) |
conn.close(1) |
338 |
|
|
339 |
|
|
340 |
|
|
341 |
# Code for module unitary test. |
# Code for module unitary test. |
342 |
|
|
343 |
if (__name__ == '__main__'): |
if (__name__ == '__main__'): |
344 |
|
|
345 |
from random import randint |
from random import randint |
346 |
|
|
347 |
clients = [] |
clients = [] |
348 |
|
|
349 |
class Client: |
class Client: |
350 |
def data_received(self, conn): |
def data_received(self, conn): |
351 |
print "Received message from " + conn.ip + ":", conn.receive(), |
print "Received message from " + conn.ip + ":", conn.receive(), |
352 |
if conn.receive() == "quit\n": |
if conn.receive() == "quit\n": |
353 |
print "User from " + conn.ip + " has quit." |
print "User from " + conn.ip + " has quit." |
354 |
conn.close() |
conn.close() |
355 |
clients.remove(self) |
clients.remove(self) |
356 |
|
|
357 |
def error(self, conn): |
def error(self, conn): |
358 |
print "Connection closed to " + conn.ip + "." |
print "Connection closed to " + conn.ip + "." |
359 |
clients.remove(self) |
clients.remove(self) |
360 |
|
|
361 |
def new_client(handler): |
def new_client(handler): |
362 |
cli = Client() |
cli = Client() |
363 |
conn = handler.accept(cli.data_received, cli.error) |
conn = handler.accept(cli.data_received, cli.error) |
364 |
cli.conn = conn |
cli.conn = conn |
365 |
clients.append(cli) |
clients.append(cli) |
366 |
print "Connection accepted from " + conn.ip + "." |
print "Connection accepted from " + conn.ip + "." |
367 |
|
|
368 |
handler = ConnectionHandler('', 4000, new_client) |
handler = ConnectionHandler('', 4000, new_client) |
369 |
print "Connection handler active and listening on port 4000." |
print "Connection handler active and listening on port 4000." |
370 |
|
|
371 |
while 1: |
while 1: |
372 |
handler.run() |
handler.run() |
373 |
if len(clients) > 0: |
if len(clients) > 0: |
374 |
if randint(1, 50) == 1: |
if randint(1, 50) == 1: |
375 |
clients[randint(0, len(clients)) - 1].conn.send("hello\r\n") |
clients[randint(0, len(clients)) - 1].conn.send("hello\r\n") |