Direcciones, familias de protocolo y tipos de conectores¶
Un conector es un punto final de un canal de comunicación utilizado por los programas para pasar datos de un lado a otro localmente o a través de Internet. Sockets tienen dos propiedades principales que controlan la forma en que envían los datos: la familia de direcciones controla el protocolo de capa de red OSI utilizado y el tipo de conector controla el protocolo de la capa de transporte.
Python soporta tres familias de direcciones. La más común, AF_INET
, se usa
para el direccionamiento de Internet IPv4. Las direcciones IPv4 tienen una
longitud de cuatro bytes y generalmente se representan como secuencia de cuatro
números, uno por octeto, separados por puntos (por ejemplo, 10.1.1.5
y
127.0.0.1
). Estos valores son más comúnmente referidos como «direcciones
IP». En este momento, casi todas las redes de Internet están hechas utilizando
IP versión 4.
AF_INET6
se utiliza para el direccionamiento de Internet IPv6. IPv6 es la
versión «siguiente generación» del protocolo de Internet, y soporta direcciones
de 128 bits, configuración de tráfico y funciones de enrutamiento no
disponibles en IPv4. La adopción de IPv6 continúa creciendo, especialmente con
la proliferación de la computación en la nube y los dispositivos adicionales
que se agregan por proyectos del Internet de las cosas.
AF_UNIX
es la familia de direcciones para Unix Domain Sockets (UDS), un
protocolo de comunicación entre procesos disponible en sistemas POSIX. La
implementación de UDS generalmente permite al sistema operativo pasar datos
directamente de proceso a proceso, sin ir a través de la red. Esto es más
eficiente que usar AF_INET
, pero porque el sistema de archivos se utiliza
como espacio de nombres para el direccionamiento, UDS está restringido a en
procesos el mismo sistema. El atractivo de utilizar UDS en lugar de otros
mecanismos de comunicación entre procesos, como los pipes con nombre o memoria
compartida es que la interfaz de programación es la misma que para redes IP, y
la aplicación pueda aprovechar de comunicación eficiente cuando se ejecuta en
un solo host, pero usar el mismo código que se usa para enviar datos a través
de la red.
Nota
La constante AF_UNIX
solo se define en sistemas donde UDS es soportado-
El tipo de conector suele ser SOCK_DGRAM
para transporte de datagramas
orientados a mensajes o SOCK_STREAM
para el transporte orientado a flujo.
Los conectores de datagramas se asocian con mayor frecuencia con UDP, el
Protocolo de datagramas de usuario. Estos proporcionan entrega no confiable
de mensajes individuales. Sockets orientados al flujo están asociados con TCP,
Protocolo de Control de Transmisión. Estos proporcionan flujos de bytes entre
el cliente y servidor, garantizando la entrega de mensajes o notificación de
fallos. a través de la gestión de tiempo de espera, la retransmisión y otras
características.
La mayoría de los protocolos de aplicación que entregan una gran cantidad de datos, como HTTP, están construidos sobre TCP porque simplifica la creación de aplicaciones complejas cuando se maneja automáticamente el pedido y la entrega de mensajes. UDP se usa comúnmente para protocolos donde el orden es menos importante (ya que los mensajes son autónomos y, a menudo, pequeños, como búsquedas de nombre a través de DNS), o para multidifusión (enviar los mismo datos a varios hosts). Tanto UDP como TCP se pueden usar directamente con cualquiera IPv4 o IPv6.
Nota
El módulo Python socket
admite otros tipos de conectores, pero se utilizan
con menos frecuencia, por lo que no se tratan aquí. Consulta la documentación
de la biblioteca estándar para más detalles.
Buscando hosts en la red¶
socket
incluye funciones para interactuar con el servicio de nombres de
dominio en la red para que un programa pueda convertir el nombre de host de un
servidor en su dirección de red numérica. Las aplicaciones no necesitan
convertir direcciones explícitamente antes de usarlas para conectarse a un
servidor, pero puede ser útil cuando se informan errores incluir la dirección
numérica, así como el nombre del valor que se utiliza.
Para encontrar el nombre oficial del host actual, usa gethostname()
.
import socket
print(socket.gethostname())
El nombre devuelto dependerá de la configuración de red para el actual sistema, y puede cambiar si está en una red diferente (como una portátil conectada a una LAN inalámbrica).
$ python3 socket_gethostname.py
apu.hellfly.net
Utiliza gethostbyname()
para consultar la interfaz de programación de
resolución de nombres de host del sistema operativo y convertir el nombre de un
servidor a su numérico dirección.
import socket
HOSTS = [
'apu',
'pymotw.com',
'www.python.org',
'nosuchname',
]
for host in HOSTS:
try:
print('{} : {}'.format(host, socket.gethostbyname(host)))
except socket.error as msg:
print('{} : {}'.format(host, msg))
Si la configuración de DNS del sistema actual incluye uno o más dominios en la
búsqueda, el argumento de nombre no necesita ser un nombre completamente
calificado (es decir, no es necesario incluir el nombre de dominio como así
como el nombre de host base). Si no se puede encontrar el nombre, una
excepción de tipo socket.error
se levanta.
$ python3 socket_gethostbyname.py
apu : 10.9.0.10
pymotw.com : 66.33.211.242
www.python.org : 151.101.32.223
nosuchname : [Errno 8] nodename nor servname provided, or not
known
Para acceder a más información sobre nombres de un servidor, usa
gethostbyname_ex()
. Esta devuelve el nombre de host canónico del servidor,
cualquier alias y todas las direcciones IP que pueden ser utilizadas para
alcanzarlo.
import socket
HOSTS = [
'apu',
'pymotw.com',
'www.python.org',
'nosuchname',
]
for host in HOSTS:
print(host)
try:
name, aliases, addresses = socket.gethostbyname_ex(host)
print(' Hostname:', name)
print(' Aliases :', aliases)
print(' Addresses:', addresses)
except socket.error as msg:
print('ERROR:', msg)
print()
Tener todas las direcciones IP conocidas para un servidor le permite a un cliente implementar su balanceo de carga propio o algoritmos de conmutación por error.
$ python3 socket_gethostbyname_ex.py
apu
Hostname: apu.hellfly.net
Aliases : ['apu']
Addresses: ['10.9.0.10']
pymotw.com
Hostname: pymotw.com
Aliases : []
Addresses: ['66.33.211.242']
www.python.org
Hostname: prod.python.map.fastlylb.net
Aliases : ['www.python.org', 'python.map.fastly.net']
Addresses: ['151.101.32.223']
nosuchname
ERROR: [Errno 8] nodename nor servname provided, or not known
Usa getfqdn()
para convertir un nombre parcial a un nombre completo de
dominio.
import socket
for host in ['apu', 'pymotw.com']:
print('{:>10} : {}'.format(host, socket.getfqdn(host)))
El nombre devuelto no necesariamente coincidirá con el argumento de entrada de
ninguna manera si la entrada es un alias, como www
lo es aquí.
$ python3 socket_getfqdn.py
apu : apu.hellfly.net
pymotw.com : apache2-echo.catalina.dreamhost.com
Cuando la dirección de un servidor esté disponible, usa gethostbyaddr()
para hacer una búsqueda «inversa» por el nombre.
import socket
hostname, aliases, addresses = socket.gethostbyaddr('10.9.0.10')
print('Hostname :', hostname)
print('Aliases :', aliases)
print('Addresses:', addresses)
El valor de retorno es una tupla que contiene el nombre de host completo, cualquier alias, y todas las direcciones IP asociadas con el nombre.
$ python3 socket_gethostbyaddr.py
Hostname : apu.hellfly.net
Aliases : ['apu']
Addresses: ['10.9.0.10']
Encontrar información de servicio¶
Además de una dirección IP, cada dirección de conector incluye un número entero número de puerto. Muchas aplicaciones pueden ejecutarse en el mismo host, escuchando en una sola dirección IP, pero solo un conector a la vez puede usar un puerto en esa dirección. La combinación de dirección IP, protocolo y puerto. identifica de forma única un canal de comunicación y asegura que los mensajes enviados a través de un conector lleguen al destino correcto.
Algunos de los números de puerto están asignados previamente para un protocolo
específico. Por ejemplo, la comunicación entre servidores de correo
electrónico mediante SMTP ocurre sobre el número de puerto 25 usando TCP, y los
clientes y servidores web usan el puerto 80 para HTTP. Los números de puerto
para servicios de red con nombres estandarizados se pueden buscar con
getservbyname()
.
import socket
from urllib.parse import urlparse
URLS = [
'http://www.python.org',
'https://www.mybank.com',
'ftp://prep.ai.mit.edu',
'gopher://gopher.micro.umn.edu',
'smtp://mail.example.com',
'imap://mail.example.com',
'imaps://mail.example.com',
'pop3://pop.example.com',
'pop3s://pop.example.com',
]
for url in URLS:
parsed_url = urlparse(url)
port = socket.getservbyname(parsed_url.scheme)
print('{:>6} : {}'.format(parsed_url.scheme, port))
Aunque es poco probable que un servicio estandarizado cambie los puertos, buscar el valor con una llamada al sistema en lugar de una codificación fija es más flexible cuando se agreguen nuevos servicios en el futuro.
$ python3 socket_getservbyname.py
http : 80
https : 443
ftp : 21
gopher : 70
smtp : 25
imap : 143
imaps : 993
pop3 : 110
pop3s : 995
Para revertir la búsqueda del puerto de servicio, usa getservbyport()
.
import socket
from urllib.parse import urlunparse
for port in [80, 443, 21, 70, 25, 143, 993, 110, 995]:
url = '{}://example.com/'.format(socket.getservbyport(port))
print(url)
La búsqueda inversa es útil para construir direcciones URL a servicios desde direcciones arbitrarias.
$ python3 socket_getservbyport.py
http://example.com/
https://example.com/
ftp://example.com/
gopher://example.com/
smtp://example.com/
imap://example.com/
imaps://example.com/
pop3://example.com/
pop3s://example.com/
El número asignado a un protocolo de transporte se puede recuperar con
getprotobyname()
.
import socket
def get_constants(prefix):
"""Create a dictionary mapping socket module
constants to their names.
"""
return {
getattr(socket, n): n
for n in dir(socket)
if n.startswith(prefix)
}
protocols = get_constants('IPPROTO_')
for name in ['icmp', 'udp', 'tcp']:
proto_num = socket.getprotobyname(name)
const_name = protocols[proto_num]
print('{:>4} -> {:2d} (socket.{:<12} = {:2d})'.format(
name, proto_num, const_name,
getattr(socket, const_name)))
Los valores para los números de protocolo están estandarizados y definidos como
constantes en socket
con el prefijo IPPROTO_
.
$ python3 socket_getprotobyname.py
icmp -> 1 (socket.IPPROTO_ICMP = 1)
udp -> 17 (socket.IPPROTO_UDP = 17)
tcp -> 6 (socket.IPPROTO_TCP = 6)
Buscar direcciones de servidor¶
getaddrinfo()
convierte la dirección básica de un servicio en una lista de
tuplas con toda la información necesaria para hacer una conexión. El contenido
de cada tupla variará, conteniendo diferentes familias de redes o protocolos.
import socket
def get_constants(prefix):
"""Create a dictionary mapping socket module
constants to their names.
"""
return {
getattr(socket, n): n
for n in dir(socket)
if n.startswith(prefix)
}
families = get_constants('AF_')
types = get_constants('SOCK_')
protocols = get_constants('IPPROTO_')
for response in socket.getaddrinfo('www.python.org', 'http'):
# Unpack the response tuple
family, socktype, proto, canonname, sockaddr = response
print('Family :', families[family])
print('Type :', types[socktype])
print('Protocol :', protocols[proto])
print('Canonical name:', canonname)
print('Socket address:', sockaddr)
print()
Este programa muestra cómo buscar la información de conexión para
www.python.org
.
$ python3 socket_getaddrinfo.py
Family : AF_INET
Type : SOCK_DGRAM
Protocol : IPPROTO_UDP
Canonical name:
Socket address: ('151.101.32.223', 80)
Family : AF_INET
Type : SOCK_STREAM
Protocol : IPPROTO_TCP
Canonical name:
Socket address: ('151.101.32.223', 80)
Family : AF_INET6
Type : SOCK_DGRAM
Protocol : IPPROTO_UDP
Canonical name:
Socket address: ('2a04:4e42:8::223', 80, 0, 0)
Family : AF_INET6
Type : SOCK_STREAM
Protocol : IPPROTO_TCP
Canonical name:
Socket address: ('2a04:4e42:8::223', 80, 0, 0)
getaddrinfo()
toma varios argumentos para filtrar el resultado lista. Los
valores host
y port
dados en el ejemplo son argumentos requeridos. Los
argumentos opcionales son family
, socktype
, proto
, y flags
.
Los valores opcionales deben ser ya sea 0
o una de las constantes definidas
por socket
.
import socket
def get_constants(prefix):
"""Create a dictionary mapping socket module
constants to their names.
"""
return {
getattr(socket, n): n
for n in dir(socket)
if n.startswith(prefix)
}
families = get_constants('AF_')
types = get_constants('SOCK_')
protocols = get_constants('IPPROTO_')
responses = socket.getaddrinfo(
host='www.python.org',
port='http',
family=socket.AF_INET,
type=socket.SOCK_STREAM,
proto=socket.IPPROTO_TCP,
flags=socket.AI_CANONNAME,
)
for response in responses:
# Unpack the response tuple
family, socktype, proto, canonname, sockaddr = response
print('Family :', families[family])
print('Type :', types[socktype])
print('Protocol :', protocols[proto])
print('Canonical name:', canonname)
print('Socket address:', sockaddr)
print()
Como flags
incluye AI_CANONNAME
, el nombre canónico del servidor, que
puede ser diferente del valor utilizado para la búsqueda si el host tiene algún
alias, esta vez se incluye en los resultados. Sin la bandera, el valor del
nombre canónico se deja vacía.
$ python3 socket_getaddrinfo_extra_args.py
Family : AF_INET
Type : SOCK_STREAM
Protocol : IPPROTO_TCP
Canonical name: prod.python.map.fastlylb.net
Socket address: ('151.101.32.223', 80)
Representaciones de direcciones IP¶
Los programas de red escritos en C usan el tipo de datos struct sockaddr
para representar direcciones IP como valores binarios (en lugar de direcciones
de cadena que normalmente se encuentran en los programas de Python). Para
convertir direcciones IPv4 entre la representación de Python y la
representación de C, usa inet_aton()
y inet_ntoa()
.
import binascii
import socket
import struct
import sys
for string_address in ['192.168.1.1', '127.0.0.1']:
packed = socket.inet_aton(string_address)
print('Original:', string_address)
print('Packed :', binascii.hexlify(packed))
print('Unpacked:', socket.inet_ntoa(packed))
print()
Los cuatro bytes en el formato empaquetado se pueden pasar a las bibliotecas de C, ser transmitidos de forma segura a través de la red, o guardados en una base de datos de forma compacta.
$ python3 socket_address_packing.py
Original: 192.168.1.1
Packed : b'c0a80101'
Unpacked: 192.168.1.1
Original: 127.0.0.1
Packed : b'7f000001'
Unpacked: 127.0.0.1
Las funciones relacionadas inet_pton()
e inet_ntop()
funcionan con
direcciones IPv4 e IPv6, produciendo el formato apropiado basado en el
parámetro de familia de direcciones pasado.
import binascii
import socket
import struct
import sys
string_address = '2002:ac10:10a:1234:21e:52ff:fe74:40e'
packed = socket.inet_pton(socket.AF_INET6, string_address)
print('Original:', string_address)
print('Packed :', binascii.hexlify(packed))
print('Unpacked:', socket.inet_ntop(socket.AF_INET6, packed))
Una dirección IPv6 ya es un valor hexadecimal, por lo que convertir la versión empaquetada a una serie de dígitos hexadecimales produce una cadena similar al valor original.
$ python3 socket_ipv6_address_packing.py
Original: 2002:ac10:10a:1234:21e:52ff:fe74:40e
Packed : b'2002ac10010a1234021e52fffe74040e'
Unpacked: 2002:ac10:10a:1234:21e:52ff:fe74:40e
Ver también
- Wikipedia: IPv6 – Artículo sobre el protocolo de Internet versión 6 (IPv6).
- Wikipedia: OSI Networking Model – Artículo que describe el modelo de siete capas de implementación de redes.
- Números de protocolo de Internet asignados – Lista de nombres y números de protocolo estándar