[Python] Underch4t v1.0 (Parte 3)

Iniciado por WhiZ, Enero 02, 2016, 01:33:12 PM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Enero 02, 2016, 01:33:12 PM Ultima modificación: Enero 02, 2016, 01:38:32 PM por WhiZ


UnderCh4t




Bienvenidos a la tercera entrega de UnderCh4t, un sencillo chat en python que nos ayudará a comprender algunos de conceptos de gran importancia para todo programador.

Si es la primera vez que escuchás hablar de este tutorial, te recomiendo que pases primero por estos posts:
    - No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    - No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Bien. Si hacemos un poco de memoria, en las entregas pasadas hemos estado enfocándonos en nuestro servidor. Más concretamente, hemos aprendido a crear un socket y a establecer una interfaz de conexión. Con todo esto, ya estamos en condiciones de poner a trabajar a nuestro servidor. Veamos cómo... ;)





PONIENDO AL SERVIDOR EN ESCUCHA



Debido a que esta vez las cosas van a ser un poco más complicadas, lo que voy a hacer es lo siguiente: primero, vamos a repasar las cosas que ya vimos en las entregas anteriores; en segundo lugar, vamos a poner el servidor en escucha y a atender las conexiones entrantes -es decir, los clientes que se conecten al servidor-.

Ya sabemos cómo crear el servidor y establecer la interfaz de conexión:
Código: python
# -*- coding: utf-8 -*-
import socket

# creamos el socket
mi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# establecemos interfaz de conexión
ip = "127.0.0.1"
puerto = 8000
mi_socket.bind((ip, puerto))


Bueno, muy fácil hasta aquí. Lo que viene ahora es poner el servidor en escucha. Sin embargo, antes me gustaría charlar algunas cosas. Sé que todavía no tocamos el tema de los clientes pero voy a ir adelantando algunas cosas (nada que los vaya a complicar, no se preocupen). Tal como hacemos con el servidor, al momento de crear un cliente debemos definir un socket. ¿Cómo lo hacemos? Tan simple como esto:

Código: python
import socket

mi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


Mmm... Esto me resulta bastante familiar, ¿no les parece? Sí. Esta misma línea la usamos para crear el socket en nuestro servidor. Interesante, ¿no?

Esto significa que cada vez que creamos un socket, éste sirve tanto para crear un servidor como un cliente, y debemos, por tanto, indicar qué rol tendrá el mismo en la conexión. En el caso del servidor (al cliente lo veremos en otro momento), lo hacemos con el método listen.

Tal y como vemos en la No tienes permitido ver los links. Registrarse o Entrar a mi cuenta, la sintaxis es la siguente:

Código: python
socket.listen(backlog)


El método listen, en pocas palabras, pone al socket en modo pasivo y establece el número de conexiones TCP entrantes que el sistema puede encolar.

Analicemos bien esto:

    - Poner al socket en modo pasivo: decimos que un socket se encuentra en modo pasivo cuando está listo para ser usado como un servidor y, por el contrario, en modo activo cuando será utilizado como cliente.

    - Encolar conexiones entrantes: cuando el servidor se pone en marcha sólo tiene que esperar a que los clientes se conecten a él. Cuando esto sucede, se atiende al cliente de forma individual y dedicada. Esto significa que si el servidor está conectado con un cliente, los demás deberán esperar a que ésta conexión finalice para ser atendidos. Podemos comparar esto con la caja de un supermercado: el cajero atiende a un cliente a la vez. Los demás deberán ponerse en fila y esperar su turno.

    - Establecer número máximo de conexiones en la cola: con el método listen podemos indicar cuál es el número máximo de clientes que vamos a permitir en nuestra cola (es el llamado backlog). Si volvemos al ejemplo del supermercado, el cajero puede indicar el número máximo de clientes que tendrá la cola. Si el máximo fuera de cinco personas, la sexta será notificada de tal situación y le negarán el acceso a la cola (sí, tendrá que irse a otra caja o volver a probar en otro momento).

Si llevamos esto al código, nos queda algo así:
Código: python
# -*- coding: utf-8 -*-
import socket

# creamos el socket
mi_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# establecemos interfaz de conexión
ip = "127.0.0.1"
puerto = 8000
mi_socket.bind((ip, puerto))

# ponemos al socket en modo pasivo y establecemos el número
# máximo de conexiones entrantes que el sistema puede encolar.
backlog = 5
mi_socket.listen(backlog)


Bien, ahora que ya hemos puesto al socket en modo pasivo (está escuchando: listen) y que hemos establecido el límite de conexiones a encolar (backlog), ya estamos en condiciones de recibir a nuestros clientes.





ACEPTANDO CONEXIONES ENTRANTES



Cuando decimos recibir a nuestros clientes, estamos hablando de aceptarlos. Justamente, para realizar esta tarea, existe el método accept. Para utilizar este método, sólo tenemos que escribir lo siguiente:

Código: python
conn, address = socket.accept()


Tal y como vemos en la No tienes permitido ver los links. Registrarse o Entrar a mi cuenta, para utilizar este método, es necesario haber creado una interfaz de conexión previamente. Una vez ejecutado, el método devuelve (return) una tupla de dos elementos:
    - conn: un nuevo objeto socket utilizado para enviar y recibir información de la conexión.
    - address: dirección vinculada al socket que se encuentra en el otro extremo de la conexión (es decir, la dirección del cliente). Como dijimos en la entrega anterior, una dirección de red se compone, en el caso de las familias AF_INET (IPv4), de una tupla con dos valores: IP y puerto.

Si volvemos al código anterior y evaluamos los valores retornados, tendríamos algo así:

Código: python
>>> print conn
<socket._socketobject object at 0x0000000001FBF660>
>>> print address
('127.0.0.1', 2580)


Como vemos, conn es un objeto socket y address una tupla con dos valores: el primero es la IP -en este caso, una Loop IP-, el segundo valor corresponde al puerto. Recuerden que estos dos valores (IP y puerto) corresponden al cliente que acabamos de recibir (aceptar). El objeto socket es el que nos permitirá comunicarnos con ese cliente.

Esto significa que si quiero enviarle información al cliente, tendré que hacerlo por medio de conn, y lo haremos a la IP 127.0.0.1 y al puerto 2580. Esto lo veremos con más detalle en las próximas entregas pero les comento esto ahora para que se vayan haciendo una idea ;)

Muy bien, en el día de hoy hemos aprendido a poner nuestro servidor en escucha (listen), indicando el número máximo de conexiones entrantes a encolar (backlog), y a aceptar cada una de estas conexiones entrantes (accept). Para poner en práctica todo esto, pueden crear un archivo llamado No tienes permitido ver los links. Registrarse o Entrar a mi cuenta (ahora les paso el contenido) y conectarse a él por medio de telnet o netcat (más adelante veremos cómo crear nuestro propio cliente).

[b[servidor.py[/b]
Código: python
# -*- coding: utf-8 -*-
import socket

# creamos servidor
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# creamos interfaz de conexión
server.bind(("0.0.0.0", 8080))

# ponemos al servidor en escucha
# (backlog: 5)
server.listen(5)

# recibimos conexiones entrantes
conn, address = server.accept()

# imprimimos los datos devueltos por
# el método accept
print conn
print address

# cerramos la conexión con el cliente
conn.close()

# cerramos el servidor
server.close()


Para finalizar, déjenme comentarles dos cosas. En primer lugar, hay un método nuevo que no habíamos visto todavía: close. Como sabemos, tanto server como conn son objetos socket. Esto significa que ambos presentan los mismos métodos. Este nuevo método, sirve para cerrar un socket. En este caso, primero cerramos el socket conn y luego el que creamos para el servidor.

En segundo lugar, por si no se dieron cuenta, en el código que acabamos de hacer tenemos un gran problema: sólo podemos recibir un cliente. Luego de crear la conexión, cerramos ambos sockets y se termina todo. Evidentemente, esto no nos sirve si queremos tener un chat funcional. En las próximas entregas veremos cómo prolongar la conexión con un cliente (bucles) y cómo interactuar con varios clientes a la vez (threads).

Nos vemos en la próxima entrega con más de UnderCh4t ;)

Saludos!
WhiZ


Duda: Al usar socket.accept() permito un cliente,posterior a recibirlo veo su IP:Puerto. Qué sucede si quiero prefiltrar IP's  previo a darle accept() para no tener que abrir/cerrar la petición del cliente? (por cualquier razón, quizá un baneo temporal)

Gracias por el código. A la espera del threading :D

Enero 02, 2016, 11:12:29 PM #2 Ultima modificación: Enero 03, 2016, 07:42:41 PM por WhiZ
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Duda: Al usar socket.accept() permito un cliente,posterior a recibirlo veo su IP:Puerto. Qué sucede si quiero prefiltrar IP's  previo a darle accept() para no tener que abrir/cerrar la petición del cliente? (por cualquier razón, quizá un baneo temporal)

Hola @No tienes permitido ver los links. Registrarse o Entrar a mi cuenta. Lamentablemente, eso no es posible, ni en python ni en ningún otro lenguaje. Lo único que podés hacer es algo así:
Código: python
import socket

blacklist = []

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 2236))

server.listen(5)
conn, address = server.accept()

if address[0] in blacklist:
    conn.close()


Saludos!
WhiZ