[Mini-Guia] Sockets en Python (3.4)

Iniciado por Sajuuk, Mayo 22, 2015, 05:28:40 AM

Tema anterior - Siguiente tema

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

Mayo 22, 2015, 05:28:40 AM Ultima modificación: Mayo 22, 2015, 09:37:45 PM por Barlan





La wikipedia nos dice:
CitarSocket designa un concepto abstracto por el cual dos programas (posiblemente situados en computadoras distintas) pueden intercambiar cualquier flujo de datos, generalmente de manera fiable y ordenada.

Yo lo veo más como un vehiculo, por el que se transportan datos desde el punto A (cliente) al punto B (servidor), e incluso también del punto B (servidor) al punto A (cliente). Nuestro vehículo está hecho de 2 piezas fundamentales: Dirección IP, y Puerto. Por ultimo, necesitan de aceite (flujo de datos), gasolina (conexión), y tener licencia de conducir (conexión fiable [ahorita lo explico]).



Un socket está compuesto por 2 cosas:

  • Una dirección IP (si utilizamos la familia de protocolos TCP/IP [(Transmission Control Protocol), Protocolo de Control de Transmisión])
  • Un puerto (éste identifica un programa entre todos los que se conectan a Internet o comparten recursos)

Yo me dedicaré a explicar el protocolo TCP/IP, ya que es el más utilizado (y el único que he manejado xD).



Pues bien, el protocolo TCP/IP nos garantiza 3 cosas:

  • Una conexión segura
  • Envío completo de octetos (la información, pues)
  • Garantiza que los octetos llegarán al destino en el orden que fueron enviados

Lo cual no ocurre en la alternativa de protocolo: UDP (User Datagram Protocol).
UDP solo nos garantiza que el mensaje llegue a su destino, sin importar si fué el primero en ser enviado o no, dandole más importancia al mensaje. Parece algo despistado, pero no lo es. UDP es usado en el streaming de audio (ya que la información es lo más importante).



Representación gráfica de un intento de conexión con TCP/IP:


No me detendré a explicar a fondo los segmentos SYN, y ACK, pero los "tantearé"

  • El cliente envía una solicitud al servidor (esto es SYN, enviar un número de secuencia con el que se espera trabajar. Se inicia con un 1, significando que se está esperando respuesta del otro extremo) pidiendole permiso para establecer una conexión
  • El servidor leerá la solicitud, y dependiendo de su funcionamiento y de la solicitud, aceptará o la rechazará. Envía la desición al cliente.
  • El cliente interpretará la respuesta del servidor, y si obtuvo el permiso, envía una confirmación (esto es ACK, confirmar la llegada del mensaje), y comienza el flujo de datos hacia el servidor (ahora SYN vale 0, ya que se completó el proceso)

Esto tiene un nombre: three-way handshake.
¿Por que? Porque son 3 pasos para poder establecer conexión cliente-servidor.




Creando un socket en Python
[SERVER]



En Python, contamos con una librería que contiene las clases y funciones para manejar un socket. La librería se llama socket:

Comenzamos importandola:
Código: python

import socket


Bien, ahora, necesitamos crear un socket.
Para esto, necesitamos una variable que sirva como un socket:
Código: python

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


Bien, dentro de la librería, hay una clase llamada socket. En ella, nosotros seleccionaremos los parametros para crear un socket bajo el protocolo TCP/IP.
"socket.AF_INET, socket.SOCK_STREAM" son los parametros que nos crearán un objeto socket TCP/IP.

Los tipos de sockets en Python son los siguientes:

  • SOCK_STREAM: Este protocolo nos da una comunicación fiable de dos direcciones en un flujo de datos(TCP)
  • SOCK_DGRAM: Este protocolo nos da una conexión no fiable. (UDP)
  • SOCK_RAW: este protocolo es para acceder a los campos e interfaces internos de la red.
  • SOCK_RDM: Este protocolo garantiza la llegada de paquetes pero no garantiza el orden de llegada
  • SOCK_SEQPACKET: datagramas fiables y secundarios, de longitud fija, basado en la conexión.
  • SOCK_PACKET: Coloca el socket en modo promiscuo en la que recibe todos los paquetes de la red.



Ahora, necesitamos una IP, y un puerto.
Para esto, contamos con el método bind. Este método nos permite designarle una IP y un puerto a nuestro socket, ya que por obvias razones, no podemos crear un socket bajo TCP sin una IP y un puerto. Es como andar por ahí sin tener un nombre y una cuenta en underc0de.
Código: python

mi_socket.bind(("127.0.0.1", 9999))


bind toma una tupla con los datos a designar.
A nuestro objeto socket le hemos designado la IP 127.0.0.1 (Esta IP es especial, ya que es usada como una alternativa al localhost, es decir, usaremos una IP local, en lugar de una pública [la que usa nuestra computadora para Internet (Facebook, Google, underc0de, etc...]).
También, le hemos designado un puerto: 9999.

Es importante saber que es recomendable usar un puerto bastante alejado de los puertos 21, 22, 80 ... etc, ya que estos son puertos especiales que nuestro Sistema Operativo usa para comunicarse por Internet y por red local. Así que trata de seleccionar un puerto más alla de... 5000?



Bien, ahora, especificaremos cuantas "orejas" tendrá nuestro socket:
Código: python
mi_socket.listen(5)


Digo orejas porque, cada oreja estará escuchando a través del puerto que seleccionamos, esperando a que una conexión de un cliente llegue.
Al nosotros colocar un 5 entre los paréntesis, le estamos diciendo a nuestro socket que estamos esperando 5 posibles clientes.

Si llegamos a tener 5 clientes conectados, automáticamente no permitirá otro más.



Entonces, de repente llega una conexión, y nuestro servidor tiene que tomarla.
¿Qué metodo utilizaremos?
Código: python

sc, addr = mi_socket.accept()



  • sc: El objeto socket cliente. Este es nuetro cliente, representado con una variable llamada sc (puedes ponerle otro nombre, pero recomiendo dejarselo así)
  • addr: Su IP

El método accept() espera una conexión. Cuando llega, este acepta la conexión, y procedemos a la parte que todos queremos.



Necesitamos recibir los datos que nuestro cliente nos está enviando.
Para esto, socket cuenta con un método llamado recv():
Código: python

recibido = sc.recv(1024)


La variable recibido contendrá lo que el cliente envie hacia nosotros.

Bien, lo que está entre paréntesis es el limite de bytes que se esperan como respuesta. Si se excede el límite, no se recibe ni se acepta el resto del mensaje, dejandolo "mocho" (o cortado).



Ahora, ya tenemos lo que el cliente nos está diciendo, pero, ¿cómo le respondo?
Entonces, el método send() llega para salvarnos:
Código: python

nuestra_respuesta = "Hola cliente, yo soy el servidor. Unete a underc0de!"
sc.send(nuestra_respuesta.encode('utf-8'))


El método send() envía una respuesta (texto u otros datos) al cliente.

Usamos la variable sc (cliente) para enviarle un mensaje.
Luego, antes de enviarle el mensaje, necesitamos codificarlo bajo algún estandar.
Generalmente usamos "utf-8" (Español, con esto, podemos enviar caracteres especiales como la ñ).

Entonces, tomamos nuestro mensaje, y seguido de un punto ( . ) escribimos encode, seguido de parentesis y el tipo de codificación a usar.



Entonces, cuando terminemos nuestro trabajo, necesitaremos cerrar nuestro objeto socket cliente, y también cerrar nuestro socket servidor;
Código: python

sc.close()
mi_socket.close()


El método close() detiene y elimina los socket creados.


Código completo:
Código: python

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 9999))
s.listen(5)

print ("Servidor de Chat\n")

while True:
print ("Esperando conexión...")
sc, addr = s.accept()
print ("Cliente conectado desde: ", addr)

while True:
recibido = sc.recv(1024)
if recibido == "quit":
break
print ("Recibido: ", recibido)

nuestra_respuesta = "Hola cliente, yo soy el servidor. Unete a underc0de!"
sc.send(nuestra_respuesta.encode('utf-8'))

print ("Adios")
sc.close()
s.close()


OJO: Usamos "while True" para mantener el socket abierto, incluso si ya enviamos una respuesta. Esto evitará que se cierre, y no podamos seguir enviando/recibiendo información.

Y LISTO. Creamos un socket bajo el protocolo TCP/IP (el más usado en la Internet de este planeta y en Andromeda).




Creando un socket en Python
[CLIENTE]



Crear un socket cliente es un poco más fácil, ya que nos ahorramos unas cuantas lineas de código.

Para esto, importamos la libreria socket, y creamos el socket cliente:
Código: python

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


Bien, ahora, no necesitamos ni bind, ni listen, ni nada.
Tan solo ocupamos un método: connect()
Código: python

socket_cliente.connect(("127.0.0.1", 9999))


El método connect() toma la tupla con los datos: IP a conectarse, y el puerto donde está escuchando el servidor.



Ahora, ya establecimos una conexión con el servidor, entonces ya podemos enviar mensajes.
Volvemos a usar el método send()
Código: python

while True:
mensaje = "Hola, soy el cliente, y ya me uní a underc0de!"

socket_cliente.send(mensaje.encode("utf-8"))


Usamos nuestro objeto cliente para enviarlo.
Volvemos a codificar el mensaje en utf-8, para poder incluir caracteres que en el idimoa inglés no hay.



Entonces, nosotros querremos recibir las respuestas del servidor. Para esto, volvemos a recv()
Código: python

recibido = socket_cliente.recv(1024)
print("Recibido: ", recibido)


El socket cliente estará esperando un máximo de 1024 bytes como respuesta.
Imprimimos lo que el servidor nos envió.



Y por ultimo, para terminar el programa cerramos el socket cliente:
Código: python

socket_cliente.close()



Código completo:
Código: python

import socket

socket_cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_cliente.connect(("localhost", 5000))

while True:
mensaje = str(input(">> "))
socket_cliente.send(mensaje.encode('utf-8'))

recibido = socket_cliente.recv(1024)
print("Recibido: ", recibido)

print ("Adios")
socket_cliente.close()





Creando un pequeño Port Scanner



Un Port Scanner (o escaner de puertos) se conectará a una ip, y a un rango determinado de puertos, con la finalidad de descubrir puertos abiertos por donde un tercero puede atacar un sistema.

Código: python

# Coded by Barlan. 2015
import socket
import sys

if len(sys.argv) != 4:
print("[!] Use: scan.py [IP_to_scan] [Initial_Port] [Final_Port]")
sys.exit(1)

def connect(IP, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.setdefaulttimeout(0.6)
try:
s.connect((IP, port))
return(1)
except:
return(2)

IP = str(sys.argv[1])
ip = int(sys.argv[2])
fp = int(sys.argv[3])
print("[*] Connecting to %s, scanning from %s to %s ..." % (IP, ip, fp))

for port in range(ip, fp+1):
e = connect(IP, port)
if e == 2:
print("[-] %s closed." % port)
else:
print("[+] %s open." % port)

print("Finished!")


Bien, importamos la librería sys para usar argumentos.



Entonces nos vamos a la función connect.
Esta función espera una IP y un puerto.
Entonces en ella creamos un socket cliente.

(El método setdefaulttimeout() sirve para declarar cuantos segundos podemos esperar a que el servidor nos conteste. Esto es útil cuando no queremos esperar mucho por una conexión. SI te pones a pensar, esto es lo que usan en los videojuegos para evitar tener a un jugador ocupando un espacio)

Llegamos al bloque try/except.
Enviamos una solicitud de conexión a la IP.
SI la conexión fué exitosa (osea, que el puerto está abierto), retorna un valor 1.

En caso contrario (que el puerto esté cerrado) retorna un valor 2.



Entonces, salimos de la función y nos vamos a la linea 23:
Código: python

for port in range(ip, fp+1):
e = connect(IP, port)
if e == 2:
print("[-] %s closed." % port)
else:
print("[+] %s open." % port)


Por cada numero dentro del rango del puerto inicial hasta el final, ejecutar la función connect (pasandole una IP, y un puerto).
Si la función retorna un valor 1, entonces nos imprime el puerto que está abierto.

Pero si nos retorna un valor 2, entonces nos imprime el puerto que está cerrado.




Si creen que pueda añadirse más contenido favor de reportarmelo.  :)

Te ha hablado Barlan y te deseo... buenas noches.


al fin entiendo esta webonada xD ,gracias

Gracias, está muy bien detallado, ahora ya que hablás de comunicación entre dos máquinas, podrías agregar como enviar "ping"  en un segmento de red para saber si la máquina está inactiva o no!
Saludos y muy buen trabajo.

Cuando introduces 'quit' no sale del bucle while. He visto el codigo varias veces y a mi parecer esta todo correcto, lo que no entiendo es porque el break no rompe el bucle y se imprime 'Adios'

Por fin entiendo los sockets gracias

Enviado desde mi GT-I8190N mediante Tapatalk