Автор оригинала: 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
Смотрите также
- Википедия: IPv6 – статья, в которой обсуждается Интернет-протокол версии 6 (IPv6).
- Википедия: Сетевая модель OSI – статья, описывающая семиуровневую модель сетевой реализации.
- Назначенные номера интернет-протокола – список стандартных имен и номеров протоколов.