(http://i61.tinypic.com/wt86ty.jpg)
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:(http://upload.wikimedia.org/wikipedia/commons/thumb/9/98/Tcp-handshake.svg/376px-Tcp-handshake.svg.png)
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:
import socket
Bien, ahora, necesitamos crear un socket.
Para esto, necesitamos una variable que sirva como un socket:
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.
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:
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?
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():
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:
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;
sc.close()
mi_socket.close()
El método close() detiene y elimina los socket creados.
Código completo:
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:
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()
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()
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()
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:
socket_cliente.close()
Código completo:
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.
# 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:
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.
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'