miércoles, 13 de enero de 2010

Sockets en Python

Untitled Document.md

Introduccion a los sockets

programar en red con python puede caer en 2 categorias, usar un modulo de
protocolo o programar el protocolo desde cero.
los sockets son una extension del sistema de entrada/salida del sistema
operativo que permite la comunicacion entre procesos y maquinas.
un socket permite usar llamadas del sistema operativo para hablar con otras
maquinas y/o procesos sobre la maquina que los ha llamado.

algunas llamadas al sistema en *nix que funcionan con descriptores de ficheros
incluyen a open(), read(), write(), close(). un descriptor de fichero
tipicamente refiere a un fichero o una entidad de tipo fichero; pueden ser,
directorios, bloques, socket, fifo’s.

python provee una interface de socket del sistema operativo, el modulo socket,
si es que se piensa diseñar el protocolo desde cero.

Creación de un socket

la creación de un socket es diferente de un cliente a un server.

creación de un cliente

primero, se tiene que crear el objeto socket, luego se le tiene que definir la
familia del protocolo (como será transmitida la data), y el tipo desocket
(protocolo usado para transmitir la data).

familias de procolo: socket.AF_UNIX, socket.AF_INET, socket.AF_INET6
Tipo de socket: socket.SOCK_STREAM, socket.SOCK_DGRAM, socket.SOCK_RAW,
socket.SOCK_RDM, socket.SOCK_SEQPACKET

Segundo, hay que conectar el objeto socket al servidor remoto. El método connect()
envia una tupla conteniendo el host remoto | dirección IP y el puerto remoto.

    host = "www.example.com"
    port = 80
    #Creación del objeto socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#Conectandolo hacia un host remoto
    s.connect(host, port)

Creación de un server

La creación de un server es parecido al de un cliente. Primero se tiene que
crear el objeto socket, luego añadir las opciones de socket setsockopt();
además, se tiene que asociar un puerto a una interface de red y ponerlo a
escuchar por conexiones. Finalmente, se aceptan las conexion de los clientes
usando socket.accept() en una iteración infinita hasta que el programa
termine o levante una excepción. Los servers usan la misma interface socket que
los clientes.

    host = ' '
    port = 9876

    #Creacion del objeto 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    #Definir las opciones del socket
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    #Asociarlo a un puerto + interface
    s.bind(host, port)

    #Ponerlo a escuchar por conexiones
    s.listen(3)

Comunicando con sockets

Python provee 2 maneras para enviar data a través de la red: objetos de socket,
y objetos tipo fichero usando socket.makefile().

Los objetos de socket se usan cuando hay una necesidad de controlar el tamaño,
los tiempo de espera, u algun otro detalle de la data que esta siendo escrita o leida.

Los objetos de fichero son mejores si se trabaja con protocolos orientados a
conexión ya que pueden manejar el análisis sintáctico de la data recibida que
ha sido garantizada por el protocolo.

Errores en la transmisión pueden ocurrir por varios factores tales como, la
desconexión de un servidor, error de direccionamiento, etc.
Por ello python define 4 posibles excepciones:

  • socket.error para problemas generales de entrada/salida

  • socket.gaierror para error de búsqueda de información de direccionamiento.

  • socket.herror para errores de direccionamiento para funciones que usan h_errno en C.

  • socket.timeout para manejar tiempo de espera después de haber llamado settimeout() sobre un socket.

Los errores con los objetos tipo ficheros en realidad hacen una llamada al
socket del sistema, asi que se aplican los mismos errores que los objetos de
socket. También se debe evitar el bloqueo mutuo entre el emisor y el receptor,
por ello hay que asegurar envio/recibo de data con socket.shutdown() antes de
recibir/enviar nueva data.

Sobre tiempos de espera para detectar y manejar errores es utilizando timeouts,
con esto se puede conseguir que python detecte inactividad en el sistema cuando
hay comunicación entre el cliente y el servidor. Para habilitar timeouts,
llamamos a socket.settimeout() enviando el tiempo limite en segundos, luego
si existe un problema de tiempo de espera se llamará socket.timeout como excepción.

También se pueden realizar transmisiones de un solo sentido usando llamadas a
socket.shutdown(), con esto logramos sockets de una sola via (half open sockets).
Esto es útil si se requiere asegurar que toda la data escrita ha sido
transmitida, o preveer deadlocks en una comunicación. Al finalizar el llamado a
shutdown y asegurar que toda la data ha sido transmitida en esa via, el
socket no podrá ser usado nuevamente.

La llamada a socket.shutdown() requiere un argumento para indicar el modo de
funcionamiento o modo de bloqueo.

  • 0 previene lecturas.

  • 1 previene escrituras.

  • 2 previene lecturas y escrituras.

CODIGO DE EJEMPLO:

El siguiente ejemplo lo he realizado orientado a conexión y sin conexión, en
ambos el servidor recibe como parámetro un fichero del arbol *nix para ser
listado y enviado al cliente; para ejecutar el fichero sera necesario 2 consolas
abiertas o el servidor corriendo en 2do plano.

    #./[script] [fichero]
    #./serverTCP.py
    #./clienteTCP.py /etc/

Clientes con python

  • Cliente TCP

#!/usr/bin/python

#Cliente objeto socket

    import socket, sys
    host = "127.0.0.1"
    port = 10101
    filename = sys.argv[1]

    #Creacion del objeto socket (IPv4, TCP)
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except socket.error, e:
        print "Error creando el socket: %s" %e

    #Conectandolo hacia un host remoto
    try:
        s.connect((host, port))
    except socket.gaierror, e:
        print "Error conectando al servidor %s" %e
    try:
        s.sendall(filename)
    except socket.error, e:
        print "Error enviando datos: %s" %e
    while 1:
        try:
            buf = s.recv(2048)
        except socket.error, e:
            print "Error reciviendo datos: %s" %e
        if not len(buf):
            break
        sys.stdout.write(buf)
  • Cliente UDP
    #!/usr/bin/python
    import socket, sys
    host="127.0.0.1"
    port=10101
    message=sys.argv[1]
    try:
        s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    except socket.error, e:
        print "Error creando el socket: %s:" %e
    try:
        s.sendto(message,(host,port))
    except socket.error, e:
        print "Error enviando datos: %s" %e
    s.settimeout(5)
    while 1:
        try:
            message, address=s.recvfrom(2048)
        except socket.timeout:
            print "Cerrado por inactividad"
            sys.exit(1)
        except socket.error, e:
            print "Error recibiendo datos: %s" %e
        if not len(message):
            break
        sys.stdout.write(message+"\n")

Servers con python

  • Server TCP

      #!/usr/bin/python 
      import socket, pickle, os 
      host = "" 
      port = 10101 
    
      #Creacion del objeto socket(IPv4, TCP) 
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
      #Definir las opciones del socket 
      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
      #Asociarlo a un puerto + interface 
      s.bind((host, port)) 
    
      #Ponerlo a escuchar por conexiones 
      s.listen(3) 
      print "Corriendo sobre puerto %d; Ctrl-C para interrumpir" % port
      while 1: 
          try: 
              csock, caddr = s.accept() 
          except KeyboadInterrupt: 
              raise 
          except: 
              traceback.print_exc() 
      continue 
    
      #Manejando la conexion 
      try: 
          print "Cliente conectado", csock.getpeername()
      except KeyboadInterrupt:
          raise
      except:
          traceback.print_exc()
      while 1:
          try:
              data = csock.recv(2048)
              if not len(data):
                  break
              dir=os.listdir(data)
              for dir in dir:
                  csock.sendall(dir+"\n")
                  csock.shutdown(1)
          except KeyboadInterrupt:
              raise
          except:
              traceback.print_exc()
              csock.close()
    
  • Server UDP

      #ServerUDP.py
      #!/usr/bin/python
    
      import socket, os
      host=""
      port=10101
      s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      s.bind((host,port))
      print "Corriendo sobre puerto %d; Ctrl-C para interrumpir" % port
      while 1:
          try:
              message, address = s.recvfrom(2048)
              print "Got data from", address
              dir=os.listdir(message)
              for listdir in dir:
                  s.sendto(listdir, address)
          except KeyboardInterrupt:
              raise
          except:
              traceback.print_exc()