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().

socket_gethostname.py
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.

socket_gethostbyname.py
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.

socket_gethostbyname_ex.py
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.

socket_getfqdn.py
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.

socket_gethostbyaddr.py
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().

socket_getservbyname.py
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().

socket_getservbyport.py
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().

socket_getprotobyname.py
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.

socket_getaddrinfo.py
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.

socket_getaddrinfo_extra_args.py
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().

socket_address_packing.py
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.

socket_ipv6_address_packing.py
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