Рубрики
Без рубрики

Адресация, семейства протоколов и типы сокетов

Автор оригинала: Doug Hellmann.

сокет – это конечная точка канала связи, используемого программами для передачи данных туда и обратно локально или через Интернет. У сокетов есть два основных свойства, управляющих способом отправки данных: семейство адресов контролирует используемый протокол сетевого уровня OSI, а тип сокета контролирует протокол транспортного уровня.

Python поддерживает три семейства адресов. Самый распространенный, AF_INET , используется для IPv4-адресации в Интернете. Адреса IPv4 имеют длину четыре байта и обычно представлены как последовательность из четырех чисел, по одному на октет, разделенных точками (например, 10.1.1.5 и 127.0.0.1 ). Эти значения чаще называют «IP-адресами». В настоящее время почти вся сеть Интернет осуществляется с использованием IP версии 4.

AF_INET6 используется для IPv6-адресации в Интернете. IPv6 – это версия Интернет-протокола «следующего поколения», поддерживающая 128-битные адреса, формирование трафика и функции маршрутизации, недоступные в IPv4. Внедрение IPv6 продолжает расти, особенно с распространением облачных вычислений и дополнительных устройств, добавляемых в сеть из-за проектов Интернета вещей.

AF_UNIX – это семейство адресов для доменных сокетов Unix (UDS), протокола межпроцессного взаимодействия, доступного в POSIX-совместимых системах. Реализация UDS обычно позволяет операционной системе передавать данные напрямую от процесса к процессу, минуя сетевой стек. Это более эффективно, чем использование AF_INET , но поскольку файловая система используется в качестве пространства имен для адресации, UDS ограничивается процессами в той же системе. Привлекательность использования UDS по сравнению с другими механизмами IPC, такими как именованные каналы или общая память, заключается в том, что интерфейс программирования такой же, как и для IP-сети, поэтому приложение может использовать преимущества эффективной связи при работе на одном хосте, но использовать тот же код при отправке данных по сети.

Примечание

Константа AF_UNIX определена только в системах, где поддерживается UDS.

Тип сокета обычно либо SOCK_DGRAM для передачи дейтаграмм, ориентированных на сообщения, либо SOCK_STREAM для транспорта, ориентированного на поток. Сокеты дейтаграмм чаще всего связаны с UDP, протоколом пользовательских дейтаграмм . Они обеспечивают ненадежную доставку отдельных сообщений. Потоковые сокеты связаны с TCP, протоколом управления передачей . Они обеспечивают потоки байтов между клиентом и сервером, обеспечивая доставку сообщений или уведомление об отказе с помощью управления тайм-аутом, повторной передачи и других функций.

Большинство протоколов приложений, которые доставляют большой объем данных, например HTTP, построены на основе TCP, потому что это упрощает создание сложных приложений, когда упорядочивание и доставка сообщений обрабатываются автоматически. UDP обычно используется для протоколов, в которых порядок менее важен (поскольку сообщения являются самодостаточными и часто небольшими, например, поиск имени через DNS), или для многоадресной передачи (отправка одних и тех же данных нескольким хосты). И UDP, и TCP могут использоваться с адресацией IPv4 или IPv6.

Примечание

Модуль Python socket поддерживает другие типы сокетов, но они используются реже, поэтому здесь не рассматриваются. См. Дополнительную информацию в документации стандартной библиотеки.

Поиск хостов в сети

socket включает функции для взаимодействия со службами доменных имен в сети, чтобы программа могла преобразовать имя хоста сервера в его числовой сетевой адрес. Приложениям не нужно явно преобразовывать адреса перед их использованием для подключения к серверу, но при сообщении об ошибках может быть полезно включать числовой адрес, а также используемое значение имени.

Чтобы узнать официальное имя текущего хоста, используйте gethostname () .

socket_gethostname.py

import socket

print(socket.gethostname())

Возвращаемое имя будет зависеть от сетевых настроек для текущей системы и может измениться, если оно находится в другой сети (например, портативном компьютере, подключенном к беспроводной локальной сети).

$ python3 socket_gethostname.py

apu.hellfly.net

Используйте gethostbyname () , чтобы обратиться к API разрешения имени хоста операционной системы и преобразовать имя сервера в его числовой адрес.

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

Если конфигурация DNS текущей системы включает в себя один или несколько доменов в поиске, аргумент имени не обязательно должен быть полностью определенным именем (т. Е. Не нужно включать имя домена, а также базовое имя хоста). Если имя не может быть найдено, возникает исключение типа socket.error .

$ 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

Для доступа к дополнительной информации об именах для сервера используйте gethostbyname_ex () . Он возвращает каноническое имя хоста сервера, любые псевдонимы и все доступные IP-адреса, которые можно использовать для доступа к нему.

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

Наличие всех известных IP-адресов для сервера позволяет клиенту реализовать свои собственные алгоритмы балансировки нагрузки или переключения при отказе.

$ 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

Используйте getfqdn () , чтобы преобразовать частичное имя в полное доменное имя.

socket_getfqdn.py

import socket

for host in ['apu', 'pymotw.com']:
    print('{:>10} : {}'.format(host, socket.getfqdn(host)))

Возвращаемое имя не обязательно будет совпадать с входным аргументом каким-либо образом, если вход является псевдонимом, например www здесь.

$ python3 socket_getfqdn.py

       apu : apu.hellfly.net
pymotw.com : apache2-echo.catalina.dreamhost.com

Когда адрес сервера доступен, используйте gethostbyaddr () , чтобы выполнить «обратный» поиск имени.

socket_gethostbyaddr.py

import socket

hostname, aliases, addresses  socket.gethostbyaddr('10.9.0.10')

print('Hostname :', hostname)
print('Aliases  :', aliases)
print('Addresses:', addresses)

Возвращаемое значение – это кортеж, содержащий полное имя хоста, любые псевдонимы и все IP-адреса, связанные с этим именем.

$ python3 socket_gethostbyaddr.py

Hostname : apu.hellfly.net
Aliases  : ['apu']
Addresses: ['10.9.0.10']

Поиск сервисной информации

Помимо IP-адреса, каждый адрес сокета включает целое число номер порта . Многие приложения могут работать на одном и том же хосте, прослушивая один IP-адрес, но только один сокет одновременно может использовать порт по этому адресу. Комбинация IP-адреса, протокола и номера порта однозначно идентифицирует канал связи и гарантирует, что сообщения, отправленные через сокет, будут доставлены в правильное место назначения.

Некоторые номера портов заранее выделены для определенного протокола. Например, связь между почтовыми серверами с использованием SMTP происходит через порт номер 25 с использованием TCP, а веб-клиенты и серверы используют порт 80 для HTTP. Номера портов для сетевых служб со стандартизованными именами можно найти с помощью 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))

Хотя стандартизированная служба вряд ли изменит порты, поиск значения с помощью системного вызова вместо жесткого кодирования становится более гибким при добавлении новых служб в будущем.

$ python3 socket_getservbyname.py

  http : 80
 https : 443
   ftp : 21
gopher : 70
  smtp : 25
  imap : 143
 imaps : 993
  pop3 : 110
 pop3s : 995

Чтобы отменить поиск порта службы, используйте 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)

Обратный поиск полезен для построения URL-адресов служб из произвольных адресов.

$ 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/

Номер, присвоенный транспортному протоколу, можно получить с помощью 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)))

Значения номеров протоколов стандартизированы и определены как константы в socket с префиксом IPPROTO_ .

$ python3 socket_getprotobyname.py

icmp ->  1 (socket.IPPROTO_ICMP =  1)
 udp -> 17 (socket.IPPROTO_UDP  = 17)
 tcp ->  6 (socket.IPPROTO_TCP  =  6)

Поиск адресов серверов

getaddrinfo () преобразует базовый адрес службы в список кортежей со всей информацией, необходимой для установления соединения. Содержимое каждого кортежа будет различаться и содержать разные сетевые семейства или протоколы.

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

Эта программа демонстрирует, как найти информацию о соединении для 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 () принимает несколько аргументов для фильтрации списка результатов. Значения host и port , указанные в примере, являются обязательными аргументами. Необязательными аргументами являются family , socktype , proto и flags . Необязательные значения должны быть либо 0 , либо одной из констант, определенных 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',
    familysocket.AF_INET,
    typesocket.SOCK_STREAM,
    protosocket.IPPROTO_TCP,
    flagssocket.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()

Поскольку flags включает AI_CANONNAME , каноническое имя сервера, которое может отличаться от значения, используемого для поиска, если у хоста есть какие-либо псевдонимы, включается в результаты этого время. Без флага значение канонического имени остается пустым.

$ 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)

Представления IP-адресов

Сетевые программы, написанные на C, используют тип данных struct sockaddr для представления IP-адресов в виде двоичных значений (вместо строковых адресов, обычно встречающихся в программах Python). Чтобы преобразовать IPv4-адреса между представлением Python и представлением C, используйте inet_aton () и 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()

Четыре байта в упакованном формате можно передать библиотекам C, безопасно передать по сети или компактно сохранить в базе данных.

$ 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

Связанные функции inet_pton () и inet_ntop () работают как с адресами IPv4, так и с IPv6, создавая соответствующий формат на основе переданного параметра семейства адресов.

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

Адрес IPv6 уже является шестнадцатеричным значением, поэтому преобразование упакованной версии в серию шестнадцатеричных цифр дает строку, аналогичную исходному значению.

$ 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

Смотрите также