В этой серии проектов мы собираемся создать групповой чат с помощью Python. Один очень важный инструмент, который мы собираемся здесь использовать, называется asyncio. Он позволит нам воспользоваться преимуществами асинхронного программирования. Асинхронное программирование особенно хорошо подходит для проектов, которым необходимо одновременно выполнять множество различных задач. В групповом чате мы можем ожидать, что сервер должен будет иметь возможность одновременно манипулировать несколькими людьми, печатающими сообщения, отправляющими уведомления о прочтении и рассылающими уведомления отдельным пользователям. Прежде чем мы перейдем непосредственно к коду, давайте сделаем шаг назад и посмотрим, что мы создаем.

Ссылка на часть 0x02: Ведение журнала

Ссылку на Github и другую полезную документацию/справки можно найти ниже в разделе «Соус, ссылки и документация»!

Что такое асинхронное программирование?

Во-первых, давайте вернемся к концепции, о которой вы, возможно, не знали, но с которой вы уже знакомы; синхронное программирование. Синхронное программирование — это выполнение кода, в котором каждая задача выполняется одна за другой. Если бы вы делали PB&J, вы бы достали хлеб. Затем вы бы положили арахисовое масло на один ломтик. Потом ты положил арахисовое масло. Тогда вы бы достали желе из холодильника. Затем вы бы положили желе на другой ломтик. Тогда бы ты их соединил. Большинство людей сначала узнают и освоятся с синхронным программированием.

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

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

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

Начиная

В этом проекте мы будем работать только с нашим файлом Server.py. Давайте продолжим и добавим следующее в начало нашего файла:

Создание сервера

Для начала мы создадим конструктор нашего сервера, который будет принимать IP-адрес хоста, порт и цикл обработки событий asyncio для обработки наших задач:

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

Теперь мы можем быстро проверить несколько вещей, чтобы убедиться, что наша программа работает должным образом:

Все идет нормально! На приведенном выше снимке экрана показано, что наша программа работает только в том случае, если пользователь предоставляет правильное количество аргументов. Вероятно, можно было бы добавить дополнительные проверки, чтобы убедиться, что IP-адрес хоста и порт получены в надлежащих форматах, но ради экономии времени мы не будем беспокоиться об этом здесь. На скриншоте также видно, что при правильных аргументах сервер строится, о чем свидетельствует наше сообщение print!

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

Asyncio примет три аргумента:

  • Аргумент 1: Обратный вызов принят клиентом — функция, которую мы определим, определяющую, как наш цикл обработки событий обрабатывает новые клиентские подключения к серверу.
  • Аргумент 2: IP — IP-адрес, указанный в __init__ выше.
  • Аргумент 3: порт — порт, который мы указали в __init__ выше.

Asyncio будет использовать эти аргументы для создания нашего сервера сокетов. Мы сохраним этот объект сервера в файле self.server.

Двигаясь вперед, мы берем наш серверный объект и запускаем наш цикл обработки событий со строк 10–11.

Я пошел дальше и бросил этот участок кода в блок try/except. В случае ошибки я пока просто выведу это на экран. Я также добавил уловку для KeyboardInterrupt, потому что подумал, что может быть полезно знать, произойдет ли / когда сервер выйдет из строя из-за CTRL + C (который я, вероятно, буду часто использовать). В случае возникновения какого-либо исключения сервер выключится. На данный момент это будет конец нашей программы, потому что цикл событий больше не будет выполняться.

Возвращаясь к нашему обратному вызову Client Accepted, указанному в аргументе 1 функции asyncio.start_server(), вы, возможно, заметили, что у нас не определено свойство self.accept_client(). Теперь займемся кодированием нашего клиента, принимающего и обрабатывающего функции.

Я продолжу и предоставлю код для этих двух вместе:

Вы также можете заметить, что handle_client() не является обычной функцией, потому что у нее есть ключевое слово «async». Как вы могли догадаться, это означает, что это асинхронная функция. Эта функция может выполняться в фоновом режиме, если нам нужно «ждать» каких-либо результатов. В этой функции мы делаем именно это в строке 31, когда ждем ввода от клиента. Имейте в виду, что у клиента не всегда может быть сообщение для нашего сервера, поэтому, когда наш код достигает этой строки, он будет «ждать» в фоновом режиме, пока результаты не будут готовы для обработки.

На данный момент наш сервер теперь способен принимать и обрабатывать несколько клиентов. Чтобы проверить это локально, я буду подключаться к локальному хосту (127.0.0.1) через порт 10000. Я буду использовать netcat в отдельных терминалах для подключения к моему серверу. Мы можем увидеть это в действии здесь:

В нашем тесте мы видим, что все работает именно так, как мы запрограммировали до сих пор. Однако вы можете заметить, что клиенты не могут видеть сообщения друг друга. На самом деле, на данный момент только сервер получает какую-либо значимую информацию. Это потому, что нам нужно будет продолжать добавлять к нашему серверу дополнительные функции, такие как широковещательная рассылка сообщений всем клиентам. Также может быть полезно изменить наш код, чтобы помочь нам определить, кто наши пользователи, когда они общаются. Это все функции, которые я планирую осветить в будущих постах, но это хорошее начало. Оставайтесь с нами для следующего выпуска!

Вывод 0x01

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

В части 0x02 я, вероятно, сделаю небольшой шаг назад от сервера/клиента и добавлю систему ведения журнала на наш сервер. Этот код очень быстро станет очень большим, поэтому наличие хорошей системы ведения журнала на раннем этапе поможет нам отладить любые проблемы, которые могут возникнуть в процессе.

Ссылка на часть 0x02: Ведение журнала

Соус, ссылки и документация

Github: https://github.com/D3ISM3/Python-Asynchronous-Server-Client
Документация по Python Asyncio: https://docs.python.org/3/library/asyncio.html

Первоначально опубликовано на https://testingonprod.com 10 октября 2021 г.