Как сделать сетевое приложение на python

Обновлено: 30.06.2024


Для работы с веб-сокетами требуется Python версии 3.6.1 и выше.

API в Python легко установить следующей командой:

Я не буду за т рагивать тему безопасности, и весь код будет на Python. Этого достаточно для понимания всеми, кто немного знаком с языком или с программированием в целом. Таким образом, в дальнейшем вам будет проще написать consumer, producer или даже сервер на другом языке или для фронтенд приложения.

Я надеюсь, что приведенные примеры будут вам полезны, и призываю разработчиков попробовать веб-сокеты хотя бы раз в своей карьере — они потрясающие. Это больше, чем REST, знаете ли!


Три самые важные строки я объясню детально, но, если вас не интересует синтаксис, можете смело пропустить эту часть.

async/await — это просто специальный синтаксис для комфортной работы с промисами. Промис — не более чем объект, представляющий собой возможную передачу или сбой асинхронной операции.

Вместо передачи обратных вызовов в функцию вы можете присоединить обратные вызовы к этому возвращенному объекту.

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

Имейте в виду: иногда я использую аббревиатуру для веб-сокетов (ws), чтобы сделать код более читаемым в статье, но всегда пишу имя полностью в рабочем коде (Например, ws можно прочесть как веб-сайт или веб-сервер. Этого следует избегать хорошему разработчику. В конце концов код должен читаться как хорошая книга).

async for — это что-то вроде синхронного цикла for, позволяющего асинхронное восприятие.


Для запуска этого простого consumer’а просто укажите имя хоста и порт, он запустится в постоянном режиме. Предельно просто. Не беспокойтесь, если нет цикла событий. asyncio создаст новый цикл и установит в качестве текущего.

Приведу пример producer’а, выдающего только одно значение. И он даже проще, чем comsumer:


Теперь нам нужен только способ выполнить эту сопрограмму-отправитель только один раз.

Конечно, у Python есть решение. Мы можем просто использовать цикл обработки событий так же, как мы делали это с consumer. Единственное отличие будет в том, что он будет запущен, пока мы не получим ответ от сервера. После получения ответа задача завершится.

В Python 3.7 стало еще лучше — теперь можно использовать функцию run для выполнения сопрограмм.

Сервер создается и определяет сопрограмму обработчика веб-сокета. Функция веб-сокета serve — это обёртка вокруг метода create_server() цикла обработки событий. Он создает и запускает сервер с create_server() и принимает обработчик веб-сокета в качестве аргумента.

Когда бы ни подключался клиент, сервер принимает соединение, создает WebSocketServerProtocol , осуществляет открывающее “рукопожатие” и передает обработчику соединения, определенному обработчиком веб-сокета. Как только обработчик заканчивает работу, нормально или с исключением, сервер выполняет закрывающее “рукопожатие” и закрывает соединение.


Последняя часть кода — самая длинная, оставайтесь с нами, мы почти закончили.

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

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


шрифт стремный, нету картинок. скучно ))


Не худший вариант, но у питона в стандартной либе есть гораздо более проработанные варианты (SimpleXMLRPCServer, например), надо было бы их упомянуть для справедливости.


Наука знает много гитик. статья не зря так называется, я пишу о чем знаю. О чем не знаю (SimpleXMLRPCServer, например) - не пишу;-)


Лучше запускать интерпретатор с ключиком '-u', вызов flush и комментарий выглядят несколько неуклюже.


Спасибо большое, поправил.



Найдите мне пожалуйста указание лицензии в ПРИМЕРАХ КОДА у Марка Лутца. Ы?


Данная версия программы Hello world распространяется под GPLv2+. LOL



Вам не нравится - не читайте. Токо не надо свое мнение выдавать за истину в конечной инстанции, лады?

if data.__class__ == str : send_string( connect, 'S'+data ) ничегонипонимаю


эквивалентно в некотором смысле type(data) is str


Я не выдаю, а ожидаю, и не мнение, а пояснения. Примерам же на главной просто не место.


Пояснения к чему именно Вы ожидаете?



- Пeтька, приборы? -200! -Что 200? -А что приборы??

Если хотите получить внятный ответ - внятно формулируйте вопрос. На вопрос по ссылке (че оно тут делает?) ответ - ЛЕЖИТ.


Десяток комментов спустя откройте глаза на мой первый коммент в третий раз. Там кроме риторического первого вопроса есть горестный печальный второй. А вы бросились доказывать со мной хором абсурдность ваших же действий.


Зачем она тут лежит?

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


А как же тематика сайта? Других ресурсов нет? Белые буквы вверху страницы глаза не режут?


А Вы считаете, что эта статья не подходит в раздел документация?


Да. Ибо документирует нетрадиционно и вообще не ОС Linux.


Вы отчасти правы, но ИМНО она ближе именно к документации, чем к какому либо другому разделу.


Да. Ибо документирует нетрадиционно и вообще не ОС Linux.

слушай, если тебя так что-то не устраивает - пожалуйся модераторам, они разберутся и примут решение, но вот только не надо устраивать плач ярославны, он в 10^30 раз менее уместен тут, нежели данная статья

интересно посмотреть, делал такое же. ссылка чтото не робит.


какая именно ссылка? У меня все робит;-)


Я ответ хотел, но его тут нет. Простите за затянувшуюся дискуссию.

А почему же не относится к тематике?Python это важный компонент дистрибутивов Linux. Далее сетевая тематика этот конек Linux как же об этом не писать? А разве кросплатформеннность была когда нибудь негативом?

И на правах юмора: когда это велосипеды не были частью Linux?)Кстати в велосипедостроении нет ничего плохого. Так что статье на главной быть должно)


в таких местах используют isinstance().

PS статью не читал.


> Клиент/серверные сетевые приложения на Python - записки дилетанта (часть 1)
На кой ляд на главной записки дилетантов постить? Был бы профи, слова не сказал ;)


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



Нет, в питоне как раз идеологически правильный и гораздо более простой вариант это делать сериализацию через pickle и передавать по сети.


По нику автора сначала подумал, что это федорчук забрел на лор


Статья интересная, но жуткое ШГ, нет сил читать.

В питоне потоки не работают из-за GIL, используйте multiprocessing


скоро потребуется спасибо!

слушайте есть хорошая дока про ООП в питоне? что нибудь общепринятое как разбиения на модули, защитные методы классов etc с практической т.з.


OMG - я хочу развидеть такой python style writing

вы молодцы и проделали большую работу, но такое впечатление, что экономили место. нечитабельно же ни хрена ;(


Статья годная. Да, иногда некоторые вещи лучше на коленке свинтить, если они для внутренних задач, чем курить маны к навороченным библиотекам/фреймворкам.

Стиль оформленя кода у автора специфический, но это не суть.


pdf? are u kidding me?

> я пишу о чем знаю

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

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


Прочитав статтью, пойму ли я как, напрмиер, отослать 2 цифры от 1 клиента к другому? (нужно для игры, для отсылания текущих координат)


AIv, статью надо писать с пафосом, важно раздувая щеки, вот как IBM_dw делает, а не то набегут разные и будут называть тебя чайником


Существует два низкоуровневых протокола, по которым передаются данные в компьютерных сетях, — это UDP (User Datagram Protocol) и TCP (Transmission Control Protocol). Работа с ними слегка различается, поэтому рассмотрим оба.

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

Переходим к практике

Писать код мы будем на современном Python 3. Вместе с Python поставляется набор стандартных библиотек, из которого нам потребуется модуль socket. Подключаем его.

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

На каждой из сторон первым делом создаем экземпляр класса socket и устанавливаем для него две константы (параметры).

Используем UDP

Сначала создадим место для обмена данными.

Мы создали объект s , который является экземпляром класса socket. Для этого мы вызвали метод из модуля socket с именем socket и передали ему два параметра — AF_INET и SOCK_DGRAMM . AF_INET означает, что используется IP-протокол четвертой версии. При желании можно использовать IPv6. Во втором параметре для наших целей мы можем указать одну из двух констант: SOCK_DGRAMM или SOCK_STREAM . Первая означает, что будет использоваться протокол UDP. Вторая — TCP.

Сторона сервера

Далее код различается для стороны сервера и клиента. Рассмотрим сначала сторону сервера.

Здесь s.bind(('127.0.0.1', 8888)) означает, что мы резервируем на сервере (то есть на нашей же машине) адрес 127.0.0.1 и порт 8888. На нем мы будем слушать и принимать пакеты информации. Здесь стоят двойные скобки, так как методу bind() передается кортеж данных — в нашем случае состоящий из строки с адресом и номера порта.

Резервировать можно только свободные порты. Например, если на порте 80 уже работает веб-сервер, то он будет нам мешать.

Далее метод recv() объекта s прослушивает указанный нами порт (8888) и получает данные по одному килобайту (поэтому мы задаем размер буфера 1024 байта). Если на него присылают датаграмму, то метод считывает указанное количество байтов и они попадают в переменную result .

Таким образом, сторона сервера имеет следующий вид:

Сторона клиента

Здесь все гораздо проще. Для отправки датаграммы мы используем метод класса socket (точнее, нашего экземпляра s ) под названием .sendto() :

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

Тестируем

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

Вывод на стороне сервера

Вывод на стороне сервера

На стороне клиента мы ничего увидеть не должны, и это логично, потому что мы ничего и не просили выводить.

Используем TCP

Пришло время познакомиться с TCP. Точно так же создаем класс s , но в качестве второго параметра будем использовать константу SOCK_STREAM .

Сторона сервера

Снова резервируем порт, на котором будем принимать пакеты:

Дальше появляется незнакомый нам ранее метод listen() . С его помощью мы устанавливаем некую очередь для подключенных клиентов. Например, с параметром .listen(5) мы создаем ограничение на пять подключенных и ожидающих ответа клиентов.

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

Страшновато? Начнем по порядку. Сначала мы создаем обработчик исключения KeyboardInterrupt (остановка работы программы с клавиатуры), чтобы сервер работал бесконечно, пока мы что-нибудь не нажмем.

Метод accept() возвращает пару значений, которую мы помещаем в две переменные: в addr будут содержаться данные о том, кто был отправителем, а client станет экземпляром класса socket . То есть мы создали новое подключение.

Теперь посмотрим вот на эти три строчки:

В них мы останавливаем прослушивание и освобождаем порт, только если сами остановим работу программы. Если прерывания не произошло, то выполняется блок else :

Сторона клиента

Далее мы отправляем пакет данных получателю методом send() :

В конце останавливаем прослушивание и освобождаем порт:

Код клиента будет выглядеть примерно так:

Тестируем

Запустим в двух разных консолях код сервера и код клиента. На выходе мы должны получить примерно то же самое, что и с протоколом UDP.

Вывод на стороне сервера

Вывод на стороне сервера

Успех! Поздравляю: теперь тебе открыты большие возможности. Как видишь, ничего страшного в работе с сетью нет. И конечно, не забываем, что раз мы эксперты в ИБ, то можем добавить шифрование в наш протокол.

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

Самодельный чат, вид со стороны сервера

Самодельный чат, вид со стороны сервера

Применяем знания на практике

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

Для работы с сервером я использую следующий код.

Здесь мы сохраняем байтовые данные в переменную data , а потом преобразуем их из кодировки ASCII в строчке st = data.decode("ascii") . Теперь в переменной st у нас хранится то, что нам прислал сервер. Отправлять ответ мы можем, только подав на вход строковую переменную, поэтому обязательно используем функцию str() . В конце у нее символ переноса строки — \n . Далее мы все кодируем в UTF-8 и методом send() отправляем серверу. В конце нам обязательно нужно закрыть соединение.

Делаем полноценный reverse shell

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

При этом добавить нам нужно только вызов функции subprocess . Что это такое? В Python есть модуль subprocess, который позволяет запускать в операционной системе процессы, управлять ими и взаимодействовать с ними через стандартный ввод и вывод. В качестве простейшего примера используем subprocess, чтобы запустить блокнот:

Здесь метод call() вызывает (запускает) указанную программу.

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

Продолжение доступно только участникам


В этой статье я бы хотел показать как написать простое приложение мессенджер менее чем в 150 строк.

Серверная часть

Начнём с сервера(наше приложение будет состоять из скриптов сервера и клиента), через который можно получать входящие запросы от клиентов, желающих общаться. Традиционно указываем путь до интерпретатора и импортируем необходимые модули. Конкретно socket и threading. Первый отвечает непосредственно за “общение” процесссов между собой, второй за многопоточность. О этих модулях подробно можно почитать например здесь - socket , threading.

Использование фреймворков, таких как Twisted и SocketServer, было возможным, но мне показалось это излишним для такого простого программного обеспечения, как наше.

Давайте обозначим константы, отвечающие например за адрес порта и размер буфера.

Это просто цикл, который всегда ждет входящих соединений и, как только он его получает, регистрирует соединение (печатает некоторые сведения о соединении) и отправляет подключенному клиенту приветствие. Затем он сохраняет адрес клиента и позже запускает поток обработки для этого клиента. Конечно, мы еще не определили для этого целевую функцию handle_client (), но вот как мы это сделаем:

Теперь пропишем функцию broadcast ():

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

В итоге получаем вот такой код для серверной части:

Теперь приступим к наиболее интересной части нашего приложения - к клиенту. В качестве gui будем использовать tkinter, т.к в нём довольно легко построить несложное приложение. Традиционно импортируем модуль tkinter, а также модули использовавшиеся ранее при написании серверной части программы.

Мы определяем еще одну функцию, которая будет вызываться, когда мы решим закрыть окно с GUI. Это своего рода функция очистки до закрытия, которая закрывает соединение с сокетом до закрытия графического интерфейса:

Это устанавливает в поле ввода значение , а затем вызывает send (). Начнем с определения виджета верхнего уровня и установки его заголовка, как и в любой другой программе на tkinter:

“Упаковываем” наши элементы и размечаем их расположение в окне:

И да, мы также упаковываем все то, что создали только сейчас. Кроме того, не забудьте использовать функцию очистки on_closing (), которая должна вызываться, когда пользователь хочет закрыть окно GUI:

И вот мы подходим к завершению. Мы еще не написали код для подключения к серверу. Для этого мы должны запросить у пользователя адрес сервера. Я сделал это, просто используя input (), чтобы пользователь встретился с подсказкой командной строки, запрашивающей адрес хоста перед запуском окна с графическим интерфейсом. В будущем можно добавить виджет для этой цели. А пока вот так:

Вот и всё! Теперь наш скрипт клиентской части выглядит вот так:

Да, наше приложение не может тягаться с такими гигантами как: telegram, viber, клиентами xmpp/jabber; однако нам удалось создать простой чат, который каждый может развить в что-то своё: сделать уклон в безопасность(например шифруя передаваемые пакеты) или в хороший ux/ui. Получилась своего рода база для чего-то большего и это круто. Спасибо за прочтение, буду рад любым замечаниям и пожеланиям. Традиционно исходный код программы доступен в моём репозитории на github.

Читайте также: