К старту курса по Fullstack-разработке на Python делимся заключительной частью руководства по программированию сокетов, эта часть посвящена устранению неполадок и справочным сведениям. К концу руководства вы освоите основные функции и методы модуля Python socket, научитесь применять пользовательский класс для отправки сообщений и данных между конечными точками и работать со всем этим в собственных клиент-серверных приложениях. Что-то будет «отказываться» работать, это неизбежно. И что тогда делать? Не переживайте, это случается с каждым. Надеемся, что с помощью этого руководства, отладчика и любимой поисковой системы вы сможете продолжить работу с частью исходного кода. Иначе вам прямая дорога в модуль socket на Python. Обязательно прочитайте всю документацию по каждой вызываемой функции или методу. А чтобы почерпнуть какие-то идеи, загляните ещё в справочный раздел. Иногда дело не в исходном коде. Он может быть правильным, а проблема связана с другим хостом, клиентом или сервером. Или сетью. А возможно, проводится «атака посредника», в качестве которого используется роутер, брандмауэр или какое-то другое сетевое устройство. Для решения таких проблем нужны дополнительные инструменты. Ниже приводятся инструменты и утилиты, которые могут в этом пригодиться или хотя бы подвести к решению. Отправкой запроса проверки связи по ICMP в ping проверяется, работает ли хост и подключён ли он к сети. Взаимодействие ping со стеком протоколов TCP/IP прямое, поэтому его работа не зависит от каких бы то ни было приложений, запускаемых на хосте. Вот пример запуска ping на macOS: Статистика внизу может быть полезной, тчобы найти разрыв постоянного подключения и ответить на вопрос, теряются ли пакеты? Какова временнáя задержка? Можно посмотреть и время приёма-передачи. Если между вами и другим хостом есть брандмауэр, запрос проверки связи в ping может быть запрещён. Политики соблюдения этого запрета реализуются администраторами брандмауэра, для которых обнаружение их хостов нежелательно. Если это ваш случай (и добавлены правила брандмауэра для взаимодействия хостов), убедитесь, что этими правилами также разрешается передача сообщений по ICMP между хостами. ICMP — это протокол, применяемый в ping, а также протокол TCP и другие более низкоуровневые протоколы, используемые для передачи сообщений об ошибках. Причина странного поведения или медленных подключений может быть в ICMP. ICMP-сообщения идентифицируются по типу и коду. Чтобы дать вам представление о важности их информации, приведём несколько таких сообщений: Информацию о фрагментации и ICMP-сообщениях см. в статье об обнаружении наименьшего MTU на пути следования пакета в сети. Это пример того, чтó может быть причиной странного поведения. В разделе «Просмотр состояния сокета» вы узнали, как можно использовать netstat для отображения информации о сокетах и их текущем состоянии. Эта утилита доступна на macOS, Linux и Windows. В том разделе столбцы Recv-Q и Send-Q в выводе примера отсутствовали. В них показано количество байтов, которые хранятся в сетевых буферах и поставлены в очередь на передачу или приём, но по какой-то причине не считаны или не записаны в удалённом или локальном приложении. То есть они остаются в ожидании в сетевых буферах, очередях ОС. Одна из причин — в приложении ограничено использование ресурсов ЦП либо нельзя вызвать socket.recv() или socket.send() и обработать байты. Или это проблемы с сетью, которые отражаются на передаче данных, например перегрузка сети или неисправное сетевое оборудование / кабели. Посмотрим, сколько данных вы сможете отправить, прежде чем увидите ошибку. Попробуйте тестовый клиент с подключением к тестовому серверу и многократным вызовом socket.send(). В тестовом сервере никогда не вызывается socket.recv(), а лишь принимается подключение. Это становится причиной заполнения сетевых буферов на сервере, после чего в клиенте выдаётся ошибка. Сначала запустите сервер: Затем, чтобы увидеть ошибку, запустите клиент: Вот вывод netstat, когда в клиенте и на сервере продолжается выполнение. При этом в клиенте многократно выводится приведённое выше сообщение об ошибке: Первая запись — это сервер (у Local Address порт 65432): Обратите внимание на 408300 в Recv-Q. Вторая запись — это клиент (у Foreign Address порт 65432): Обратите внимание на 269868 в Send-Q. В клиенте, конечно, пытались записать байты, но на сервере они не считывались. Это стало причиной заполнения очереди сетевого буфера как на стороне приёма на сервере, так и на стороне отправки в клиенте. Если работаете с Windows, обязательно ознакомьтесь с набором утилит Windows Sysinternals. Одна из них — TCPView.exe. Это графический netstat для Windows. Кроме адресов, номеров портов и состояния сокета, в ней нарастающим итогом показывается число отправленных и полученных пакетов и байтов. Как и в случае с утилитой lsof в Unix, здесь вы получаете имя и идентификатор процесса. Другие параметры отображения см. в меню.  Иногда нужно увидеть, что происходит при передаче по сети. Забудьте, чтó там в журнале приложения или какое значение возвращается из библиотечного вызова. Вам нужно видеть, чтó отправляется или получается в сети на самом деле. Здесь как с отладчиками: когда нужно увидеть это, они незаменимы. Wireshark — это анализатор сетевых протоколов и приложение для захвата трафика, запускаемое на macOS, Linux, Windows и других ОС. Существует две версии: wireshark с графическим интерфейсом и текстовая tshark для терминала. Захват трафика — это отличный способ понаблюдать за поведением приложения в сети и узнать: чтó, как часто и сколько в нём отправляется и получается. А ещё увидеть, когда клиент или сервер закрывается, когда в них прерывается подключение или прекращается отправка ответов. Эта информация может очень пригодиться при устранении проблем. В интернете много хороших руководств и других ресурсов по основам использования Wireshark и TShark. Вот пример захвата трафика в интерфейсе «внутренней петли» с помощью Wireshark:  А вот тот же пример с tshark: Далее приводим вам в помощь справочные материалы по программированию сокетов. Этот раздел может использоваться в качестве справочника общего содержания с дополнительной информацией и ссылками на внешние ресурсы. Следующее взято из документации к модулю socket на Python: Вот типичные ошибки, с которыми вы можете столкнуться при работе с сокетами: socket.AF_INET и socket.AF_INET6 — это семейства адресов и протоколов для первого аргумента в socket.socket(). В API предполагается адрес определённого формата — в зависимости от того, создан сокет с помощью socket.AF_INET или socket.AF_INET6. Ниже приведена выдержка из документации Python к модулю socket о значении host в кортеже адресов: Подробнее см. в документации по семействам сокетов на Python. В этом руководстве применяются сокеты IPv4. Если в вашей сети поддерживается IPv6, попробуйте по возможности протестировать и использовать эту версию. Это легко делается с функцией socket.getaddrinfo(). Её аргументы host и port преобразуются в последовательность из пяти кортежей со всеми аргументами, необходимыми для создания сокета, подключённого к этому сервису. В socket.getaddrinfo() принимаются и интерпретируются переданные IPv6-адреса и имена хостов, которые разрешаются не только в IPv4, но и в IPv6-адреса. В следующем примере возвращается информация об адресе для TCP-подключения к example.org в порте 80: На вашем компьютере результаты могут отличаться, если IPv6 не включена. Возвращаемые выше значения можно передавать в socket.socket() и socket.connect(). В документации Python к модулю socket, в разделе с примерами, содержится пример клиента и сервера. Кстати, этот раздел примени́м в основном к использованию имён хостов с .bind() и .connect() или .connect_ex(), когда задействуется интерфейс «внутренней петли» (localhost). Но примени́м он и в любой момент, когда вы используете имя хоста и ожидается его разрешение в определённый адрес. Причём имя хоста имеет особое значение для приложения, что отражается на его поведении или делаемых в нём допущениях. Это отличается от типичного сценария, когда в клиенте имя хоста используется для подключения к серверу, разрешённому при помощи DNS, например www.example.com. Следующее взято из документации к модулю socket на Python: Стандартным именем localhost предусматривается его разрешение в 127.0.0.1 или ::1, интерфейс «внутренней петли». В вашей системе наверняка так и будет. А может, и нет. Это зависит от того, как она сконфигурирована для разрешения имён. Как и во всём, что связано с ИТ, всегда имеются исключения, и нет никаких гарантий, что при использовании имени localhost произойдёт подключение к интерфейсу «внутренней петли». Например, на Linux см. файл конфигурации man nsswitch.conf диспетчера службы имён. На macOS и Linux стóит ещё заглянуть в файл /etc/hosts. На Windows см. C:\Windows\System32\drivers\etc\hosts. В файле hosts имеется статическая таблица сопоставления имён и адресов в простом текстовом формате. DNS — это совершенно иная часть пазла. Примечательно, что с июня 2018 года существует черновой вариант RFC Let ‘localhost’ be localhost («Пусть ‘localhost’ будет localhost»), где обсуждаются соглашения, допущения и вопросы безопасности, связанные с применением имени localhost. Важно понимать: когда в приложении используются имена хостов, в качестве адресов может возвращаться буквально всё что угодно. Не делайте допущений относительно имени, если в приложении важна безопасность. Представляет это повод для беспокойства или нет — зависит от приложения и окружения. Независимо от того, используете вы имена хостов или нет, если в приложении нужна поддержка безопасных подключений через шифрование и аутентификацию, то вам, вероятно, стóит попробовать TLS. Но это — тема отдельной статьи и в настоящем руководстве не рассматривается. Чтобы начать с ним работу, см. документацию по модулю ssl на Python. Это тот же протокол, который применяется в браузерах для безопасного подключения к сайтам. Интерфейсы, IP-адреса, разрешения имён… Переменных много. Что же делать? Если у вас нет процесса рассмотрения сетевых приложений, используйте эти рекомендации: Для клиентов или серверов, если нужно аутентифицировать хост, к которому подключаетесь, стóит попробовать TLS. Блокирующий вызов — это функция или метод сокета, на которых работа приложения временно приостанавливается. Например, .accept(), .connect(), .send() и .recv() блокируются, то есть немедленного возвращения в них не происходит. Прежде чем от блокирующих вызовов возвращается значение, они находятся в ожидании завершения системных (ввод-вывод). Поэтому источник вызова блокируется до их завершения, или истечения тайм-аута, или возникновения другой ошибки. Чтобы в блокирующих вызовах сокетов происходило немедленное возвращение значений, такие вызовы можно настроить на неблокируемый режим. При этом, чтобы обрабатывать готовую операцию сокета, нужно как минимум провести рефакторинг или перепроектирование приложения. Поскольку в вызове значения возвращаются немедленно, данные могут быть не готовы. Вызываемая сторона находится в сети в ожидании — ей не хватило времени завершить свою работу. В этом случае текущий статус — это errno-значение socket.EWOULDBLOCK. Неблокируемый режим поддерживается с помощью .setblocking(). По умолчанию сокеты всегда создаются в блокируемом режиме. Описание трёх режимов см. в примечаниях по тайм-аутам сокетов. В TCP примечательно то, что в клиенте или на сервере совершенно допустимо закрывать свою сторону подключения, пока другая остаётся открытой. Это называется «полуоткрытым» подключением. Желательно оно или нет — решается в приложении. Вообще говоря, нежелательно. В этом состоянии со стороны, чей конец подключения закрыт, данные больше отправляться не смогут. Они будут только приниматься. Этот подход рекомендуется не как обязательный, но в HTTP в качестве примера используется заголовок с именем Connection для стандартизации того, как в приложениях должны закрываться или сохраняться открытыми подключения. Подробнее см. в разделе 6.3 RFC 7230 о протоколе передачи гипертекста (HTTP/1.1), синтаксисе и маршрутизации сообщений. При разработке и написании приложения и его протокола прикладного уровня рекомендуется заранее продумать закрытие подключений. Иногда это просто и очевидно, а иногда могут потребоваться создание прототипов и предварительное тестирование. Всё зависит от приложения и от того, как обрабатывается цикл сообщений с ожидаемыми данными. Просто убедитесь, что сокеты всегда своевременно закрываются после завершения работы. Подробнее о том, как порядок байтов хранится в памяти разных ЦП, см. в соответствующей статье «Википедии». При интерпретации отдельных байтов это не проблема. Но при обработке нескольких байтов, считываемых и обрабатываемых как одно значение, например 4-байтовое целое число, порядок байтов нужно изменить на противоположный, если взаимодействовать с компьютером, на котором другой порядок следования байтов. Порядок байтов важен и для текстовых строк, представленных в виде многобайтовых последовательностей, таких как «Юникод». Если только вы не используете всегда true, строгий ASCII и не контролируете реализацию клиента и сервера, вам наверняка лучше использовать «Юникод» с кодировкой вроде UTF-8 или той, которой поддерживается маркер последовательности байтов. Важно явно определить кодировку, применяемую в протоколе прикладного уровня. Это можно сделать, установив в качестве обязательной для всего текста UTF-8 или использовав заголовок content-encoding, в котором эта кодировка указана. Так приложение избавляется от необходимости определять кодировку, которую по возможности следует избегать. Это становится проблематичным, когда имеются данные, которые хранятся в файлах или базе данных, и недоступны метаданные, в которых указана их кодировка. Когда данные передаются на другую конечную точку, в ней придётся попробовать определить кодировку. Идеи для обсуждения см. в статье «Википедии» о «Юникоде», в которой упоминается о RFC 3629: UTF-8, формат преобразования ISO 10646: А вывод из этого такой: всегда сохранять кодировку, используемую для обрабатываемых в приложении данных, если она может меняться. То есть пытаться каким-то образом сохранять кодировку в виде метаданных, если это не всегда UTF-8 или какая-то другая кодировка с маркером последовательности байтов. Затем можно отправить её в заголовке вместе с данными, чтобы сообщить получателю, что это такое. В TCP/IP применяется обратный порядок байтов, который называется сетевым. Сетевой порядок используется для представления целых чисел на нижних уровнях стека протоколов, таких как IP-адреса и номера портов. В модуль socket на Python включены функции, в которых целые числа преобразуются в порядок байтов сети и хоста и обратно: Кроме того, чтобы с помощью строк формата упаковывать и распаковывать двоичные данные, можно использовать модуль struct: В этом руководстве рассмотрено много вопросов! Сети и сокеты — это большие темы. Если они вам в новинку, пусть вас не смущают все эти термины и сокращения. Чтобы понять, как здесь всё сочетается, нужно ознакомиться с кучей подробностей. Но в Python начинаешь лучше понимать целое, осваивая отдельные части и проводя с ними больше времени. Теперь вы можете задействовать свой пользовательский класс и, опираясь на него, проще и быстрее создавать собственные приложения с сокетами. Ознакомиться с примерами можно по ссылке ниже: Поздравляем с тем, что добрались до конца! Вы уже готовы применять сокеты в собственных приложениях. Желаю вам успехов в разработке сокетов. Научим вас аккуратно работать с данными, чтобы вы прокачали карьеру и стали востребованным IT-специалистом. Новогодняя акция — скидки до 50% по промокоду HABR:  Data Science и Machine Learning Python, веб-разработка Мобильная разработка Java и C# От основ — в глубину А также