MicroPython:Библиотеки/usocket

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Модуль usocket – сокеты Беркли[1]

В этом модуле реализована часть функционала соответствующего модуля CPython. Более подробно читайте в документации к CPython о модуле socket.

В этом модуле реализован доступ к интерфейсу сокетов Беркли.

Отличие от CPython: В целях эффективности и совместимости в сокетных объектах MicroPython напрямую реализован интерфейс потокового (файлообразного) объекта. В CPython вам необходимо преобразовывать сокет в файлообразный объект при помощи метода makefile(). В MicroPython этот метод тоже поддерживается, но является пустой операцией (noop), поэтому используйте его в ситуациях, где нужна совместимость с CPython.

Форматы для адреса сокета

Нативный формат адреса сокета в модуле usocket – это скрытый тип данных, возвращаемый функцией getaddrinfo(). С помощью этой функции из текстовых (и числовых) адресов извлекаются данные, которые затем используются для создания сокетов.

sockaddr = usocket.getaddrinfo('www.micropython.org', 80)[0][-1]
# Функцию getaddrinfo() нужно использовать даже для числовых адресов:
sockaddr = usocket.getaddrinfo('127.0.0.1', 80)[0][-1]
# Теперь мы можем воспользоваться этим адресом:
sock.connect(addr)

Использование функции getaddrinfo() – это самый эффективный (и в плане памяти, и в плане вычислительной мощности) и портативный способ для работы с адресами.

Впрочем, в модуле socket (речь не о MicroPython’овском модуле usocket, которому посвящена эта статья) есть CPython-совместимый способ для указания адресов при помощи кортежей (подробнее см. ниже). В некоторые MicroPython-порты модуль socket может быть встроен по умолчанию, в некоторые его надо будет установить из библиотеки «micropython-lib» (как в случае Unix-порта), а некоторые порты по-прежнему поддерживают только числовые адреса в кортежном формате и требуют, чтобы для обработки доменных имен использовалась функция getaddrinfo().

Итого:

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

Кортежные форматы адресов в модуле socket:

  • IPv4: (ipv4_address, port), где ipv4_address – это строка с числовым IPv4-адресом, в котором числа разделены точками (например, «8.8.8.8»), а port – это номер порта (целое число) в диапазоне 1-65535. Доменные имена в ipv4_address указывать нельзя, сначала их нужно обработать с помощью функции usocket.getaddrinfo().
  • IPv6: (ipv6_address, port, flowinfo, scopeid), где ipv6_address – это строка с числовым IPv6-адресом, в котором числа разделены двоеточиями (например, «2001:db8::1»), а port – это номер порта (целое число) в диапазоне 1-65535. В flowinfo должно быть «0». В scopeid задается идентификатор области видимости (для адресов типа «link-local»). Доменные имена в ipv6_address указывать нельзя, сначала их нужно обработать с помощью функции usocket.getaddrinfo(). Наличие поддержки IPv6-адресов зависит от используемого MicroPython-порта.

Функции

  • usocket.socket(af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP, /) – создает новый сокет при помощи заданного семейства адресов, типа сокета и номера протокола. В большинстве случаев proto указывать не нужно (и не рекомендуется, потому что некоторые MicroPython-порты могут опускать константы типа IPPROTO_*). Нужный протокол будет автоматически выбран аргументом type:
# Создаем сокет STREAM TCP:
socket(AF_INET, SOCK_STREAM)
# Создает сокет DGRAM UDP:
socket(AF_INET, SOCK_DGRAM)
  • usocket.getaddrinfo(host, port, af=0, type=0, proto=0, flags=0, /) – преобразовывает аргументы host и port в 5-элементный кортеж, содержащий все необходимые данные для создания сокета, подключенного к этому сервису. Аргументы af, type и proto – они обозначают то же самое, что и в функции socket() – используются для фильтрации того, какие типы адресов будут возвращаться. Если в этих параметрах не задать ничего или задать «0», getaddrinfo() может вернуть все комбинации адресов (что потребует фильтрации на стороне пользователя).

Возвращаемый 5-элементный кортеж будет иметь следующую структуру: (family, type, proto, canonname, sockaddr) В примере ниже демонстрируется, как подключиться к заданному URL:

s = usocket.socket()
# Это подразумевает, что если не будет указан тип («type»),
# будет возвращен адрес для SOCK_STREAM, что может быть неправдой
s.connect(usocket.getaddrinfo('www.micropython.org', 80)[0][-1])

Рекомендованное использование фильтрующих аргументов:

s = usocket.socket()
# Гарантированно вернет адрес, к которому 
# можно будет подключиться для выполнения потоковых операций
s.connect(usocket.getaddrinfo('www.micropython.org', 80, 0, SOCK_STREAM)[0][-1])

Отличие от CPython: Если в этой функции возникнет ошибка, CPython возбудит исключение socket.gaierror (подкласс OSError). В MicroPython нет socket.gaierror, поэтому он напрямую вызовет OSError. Помните, что коды ошибок getaddrinfo() формируют отдельное пространство имен, которое может не соответствовать кодам ошибок из модуля uerrno. С целью отличить коды ошибок getaddrinfo() от других кодов ошибок они представлены в виде отрицательных чисел, тогда как стандартные системные ошибки – это положительные числа (доступ к кодам ошибок осуществляется через свойство e.args[0] в объекте исключения). Использование отрицательных чисел – это временная мера, и в будущем от нее, возможно, откажутся.

  • usocket.inet_ntop(af, bin_addr) – преобразовывает двоичный сетевой адрес bin_addr заданного семейства адресов af в его текстовую версию:
>>> usocket.inet_ntop(usocket.AF_INET, b"\x7f\0\0\1")
'127.0.0.1'
  • usocket.inet_pton(af, txt_addr) – преобразовывает текстовый сетевой адрес txt_addr заданного семейства адресов af в его двоичную версию:
>>> usocket.inet_pton(usocket.AF_INET, "1.2.3.4")
b'\x01\x02\x03\x04'

Константы

  • usocket.AF_INET и usocket.AF_INET6 – типы семейств адресов. Наличие зависит от используемого MicroPython-порта.
  • usocket.SOCK_STREAM и usocket.SOCK_DGRAM – типы сокетов.
  • usocket.IPPROTO_UDP и usocket.IPPROTO_TCP – типы интернет-протоколов. Наличие зависит от используемого MicroPython-порта. Задавать их в функции usocket.socket() не нужно, потому что сокет типа SOCK_STREAM автоматически выберет IPPROTO_TCP, а SOCK_DGRAM – IPPROTO_UDP. Следовательно, эти константы нужны лишь как аргументы для setsockopt().
  • usocket.SOL_* – уровни параметров сокета (аргумент для функции setsockopt()). Перечень уровней зависит от используемого MicroPython-порта.
  • usocket.SO_* – параметры сокета (аргумент для функции setsockopt()). Перечень параметров зависит от используемого MicroPython-порта.
  • usocket.IPPROTO_SEC – специальная протокольная константа для создания SSL-совместимого сокета. Используется исключительно для WiPy.

Класс «socket»

Методы

  • socket.close() – помечает сокет как закрытый и высвобождает все ресурсы, которые он использовал. После этого все будущие операции на этом сокетном объекте будут терпеть неудачу. На том конце соединения будет получен индикатор EOF (от англ. «end-of-file», то есть «конец файла») – если он поддерживается протоколом.

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

  • socket.bind(address) – привязывает сокет к адресу address. Привязываемый сокет не должен быть уже привязан.
  • socket.listen([backlog]) – включает режим прослушивания. Если задан аргумент backlog, в нем должен быть как минимум «0» (если меньше, то «0» будет задан автоматически). В этом аргументе задается максимальное количество подключений в очереди прежде, чем система начнет отклонять новые соединения. Если аргумент backlog задан не будет, в нем будет автоматически выставлено некое разумное значение.
  • socket.accept() – принимает подключение. Сокет уже должен быть привязан к адресу, а также должен быть в режиме прослушивания. Возвращаемым значением будет (conn, address), где conn – это новый сокетный объект, который можно использовать для отправки и получения данных на этом подключении, а address – это адрес, привязанный к сокету на другом конце соединения.
  • socket.connect(address) – подключает к удаленному сокету с адресом address.
  • socket.send(bytes) – отправляет данные сокету. Сокет должен быть подключен к удаленному сокету. Возвращает количество отправленных байтов, которое может быть меньше размера отправленных данных (операция «короткой» записи).
  • socket.sendall(bytes) – отправляет все данные на сокет. Сокет должен быть подключен к удаленному сокету. В отличие от send(), этот метод попробует отправить все данные, отправляя их последовательно один фрагмент за другим.

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

  • socket.recv(bufsize) – получает данные от сокета. Возвращаемое значение – это объект bytes, содержащий полученные данные. В аргументе bufsize задается максимальное количество единовременно получаемых данных.
  • socket.sendto(bytes, address) – отправляет данные сокету. Сокет не должен быть подключен к удаленному сокету, т.к. сокет назначения задан в аргументе address.
  • socket.recvfrom(bufsize) – получает данные от сокета. Возвращаемым значением будет (bytes, address), где bytes – это объект с полученными данными, а address – это адрес сокета, отправившего данные.
  • socket.setsockopt(level, optname, value) – задает значение для заданного параметра сокета. Все необходимые символьные константы определены в модуле socket (SO_* и т.д.). В аргументе value может быть целое число или объект вроде bytes, представляющий буфер с данными.
  • socket.settimeout(value) – задает таймаут для блокирования сокетных операций. В аргументе value можно задать неотрицательное число с плавающей точкой (это секунды) или None. Если задать в нем ненулевое значение, последующие сокетные операции будут возбуждать исключение OSError, если не будут выполнены в пределах заданного таймаута. Если задать в нем ноль, сокет будет переключен в неблокирующий режим. Если задать в нем None, сокет будет переключен в блокирующий режим.

Этот метод поддерживается не всеми MicroPython-портами. Более портативное и универсальное решение – это воспользоваться объектом, создаваемым с помощью uselect.poll(). Это позволит ждать событий одновременно на нескольких объектах (не только на сокетах, но и на более универсальных потоковых объектах, поддерживающих опросы). Пример:

# Вместо:
s.settimeout(1.0)  # время в секундах
s.read(10)  # может не успеть в заданный таймаут

# Используйте:
poller = uselect.poll()
poller.register(s, uselect.POLLIN)
res = poller.poll(1000)  # время в миллисекундах
if not res:
    # в «s» по-прежнему нет данных для считывания,
    # т.е. таймаут для операции прошел

Отличие от CPython: В случае истечения таймаута CPython возбуждает исключение socket.timeout, которое является подклассом OSError. MicroPython возбуждает OSError напрямую. Если вы, чтобы поймать исключение, используете except OSError:, ваш код будет работать и в MicroPython, и в CPython.

  • socket.setblocking(flag) – переключает сокет в блокирующий или неблокирующий режим. Если в аргументе flag задать False, сокет будет переключен в неблокирующий режим, а если True, то в блокирующий.

Данный метод можно использовать как замену settimeout():

    • sock.setblocking(True) – это эквивалент sock.settimeout(None)
    • sock.setblocking(False) – это эквивалент sock.settimeout(0)
  • socket.makefile(mode='rb', buffering=0, /) – возвращает файловый объект, привязанный к сокету. Тип возвращаемого объекта зависит от того, что было задано в аргументах makefile(). Поддерживаются только двоичные режимы «rb», «wb» и «rwb». CPython-аргументы encoding, errors и newline не поддерживаются.

Отличия от CPython: Во-первых, MicroPython не поддерживает буферные потоки, поэтому аргумент buffering будет игнорироваться и восприниматься, как если бы там был «0» (без буфера). Во-вторых, закрытие файлового объекта, возвращенного методом makefile(), тоже закроет оригинальный сокет.

  • socket.read([size]) – считывает данные из сокета; размер считываемых данных задается в аргументе size. Возвращает объект bytes. Если аргумент size не задан, метод socket.read() прочтет все данные, имеющиеся в сокете, пока не получит EOF. По этой причине данные не будут возвращены, пока сокет не будет закрыт. Эта функция пытается прочесть именно столько данных, сколько было запрошено (никаких «коротких считываний»). Но если сокет работает в неблокирующем режиме, это может не получиться, и тогда метод вернет меньше данных.
  • socket.readinto(buf[, nbytes]) – считывает байты в буфер buf. В опциональном аргументе nbytes задается то, сколько именно байтов нужно прочесть. В противном случае количество считанных байтов не будет превышать размер буфера. Как и read(), этот метод работает по принципу «никаких коротких считываний».

Возвращаемое значение: количество байтов, что были считаны и сохранены в buf.

  • socket.readline() – считывает строку. Считывание заканчивается на символе новой строки.

Возвращаемое значение: прочитанная строка.

  • socket.write(buf) – записывает буфер с байтами в сокет. Эта функция попытается записать все данные в сокет (никаких «коротких записей»). Но если сокет работает в неблокирующем режиме, это может не получиться, и в результате функция вернет значение, которое будет меньше, чем размер буфера buf.

Возвращаемое значение: количество записанных байтов.

  • Исключение usocket.error – в MicroPython этого исключения НЕТ.

Отличие от CPython: В CPython когда-то было исключение socket.error, которое устарело. Оно является псевдонимом OSError. В MicroPython используйте OSError напрямую.


<syntaxhighlight lang="python">

См.также

Внешние ссылки