/[ambar]/ambar/connection.py
ViewVC logotype

Diff of /ambar/connection.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 2.2 by pabloruiz, Thu Aug 29 18:51:20 2002 UTC revision 2.3 by pabloruiz, Tue Nov 5 22:58:22 2002 UTC
# Line 1  Line 1 
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")

Legend:
Removed from v.2.2  
changed lines
  Added in v.2.3

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26