Клиент-серверные приложения
Встроенный модуль socket в Python представляет функциональность для взаимодействия по сети. Этот модуль определяет низкоуровневый интерфейс для отправки и получения запросов в виде класса socket . И вне зависимости, что мы создаем — программу сервера или клиента, для отправки запросов нам надо создать объект сокета:
import socket sock = socket.socket()
После окончания работы с сокетом его следует закрыть с помощью метода close()
import socket client = socket.socket() client.close()
Дальнейшие действия с сокетом зависят от того, какой именно сокет мы создаем — для сервера или для клиента. В данном случае мы рассмотрим создание простейшего клиента на сокетах Python.
Подключение к серверу
Для подключения к серверу применяется метод connect()
socket.connect(address)
В качестве параметра он получает адрес сервера. Адрес обычно представляется в виде кортежа
(host, port)
Первый элемент — хост в виде строки. Это может быть, например, IP-адрес в виде «127.0.0.1» или название локального хоста. Второй параметр — числовой номер порта. Порт представляет 2-х байтное значение от 0 до 65535. Например:
import socket client = socket.socket() # подключаемся по адресу www.python.org и порту 80 client.connect(("www.python.org", 80)) print("Connected. ") client.close() # закрываем подключение
Здесь пытаемся подключиться по адресу «www.python.org» и порту 80. То есть фактически мы пытаемся подключиться к обычному веб-сайту www.python.org , который работает на порту 80 (обычно веб-сервер запускается на порту 80). После подключения просто выводим строку на консоль и закрывает сокет.
Взаимодействие с сервером
После установки соединения мы можем отправлять и получать данные от сервера. Для отправки данных применяется метод socket.send() , который в качестве параметра получает набор отправляемых данных.
socket.send(bytes)
Для получения данных у сокета применяется метод socket.recv()
bytes = socket.recv(bufsize[, flags])
В качестве обязательного параметра он принимает максимальный размер буфера в байтах, которые могут быть получены за раз от другого сокета. Размер буфера лучше делать кратным 2, например, 512, 1024 и т.д. Возвращаемое значение — набор байтов, полученых от другого сокета.
Например, отправим данные на сервер «www.python.org» и получим от него ответ:
import socket client = socket.socket() # подключаемся по адресу www.python.org и порту 80 client.connect(("www.python.org", 80)) # данные для отправки - стандартные заголовки протокола http message = "GET / HTTP/1.1\r\nHost: www.python.org\r\nConnection: close\r\n\r\n" print("Connecting. ") client.send(message.encode()) # отправляем данные серверу data = client.recv(1024) # получаем данные с сервера print("Server sent: ", data.decode()) # выводим данные на консоль client.close() # закрываем подключение
В данном случае отправляется строка со стандартными заголовками протокола HTTP, которые пониманиет веб-сайт. Формат запроса HTTP включает прежде всего линию запроса ( «GET / HTTP/1.1\r\nHost: www.python.org ), которая состоит из типа запроса, пути к запрошенному ресурсу и специфической версии протокола. То есть здесь в сообщении мы указываем, что отправляется запрос типа GET по пути «/» (то есть к корню сайта www.python.org»). При этом применяется протокол HTTP/1.1. Линия запроса должна завершаться двойным набором символов каретки и перевода строки \r\n.
Кроме того, запрос HTTP может содержать заголовки. Так, в данном случае отправляем заголовок «Host», который указывает на адрес хоста. В данном случае это «www.python.org:80». И также в данном случае отправляем заголовок «Connection», который имеет значение «close» — это значение предписывает серверу закрыть подключение.
Поскольку мы можем послать только байты, а не строки, то при отправке переводим строку в набор байтов. Для этого применяется метод encode() , который возвращает объект класса bytes
client.send(message.encode())
После отправки серверу сообщения пытаемся получить от него данные с помощью метода recv() . При этом буфер для получаемых данных будет иметь размер в 1024 байта. Результат метода — полученные данные. Однако сокет получает данные в виде набора байт (точнее объекта bytes ). Чтобы их преобразовать в строку, применяется метод decode() :
data = client.recv(1024) # получаем данные с сервера print("Server sent: ", data.decode()) # выводим данные на консоль
И если мы запустим данную программу на Python, то получим ответ напродобие следующего:
Connected. Server sent: HTTP/1.1 301 Moved Permanently Connection: close Content-Length: 0 Server: Varnish Retry-After: 0 Location: https://www.python.org/ Accept-Ranges: bytes Date: Tue, 02 May 2023 17:52:32 GMT Via: 1.1 varnish X-Served-By: cache-bma1653-BMA X-Cache: HIT X-Cache-Hits: 0 X-Timer: S1683049953.637569,VS0,VE0 Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Низкоуровневый сетевой интерфейс в Python
Модуль socket обеспечивает доступ к интерфейсу сокета BSD. Он доступен во всех современных системах Unix, Windows, MacOS и, возможно, на дополнительных платформах.
Он включает в себя функции создания объекта сокета Socket , который и обрабатывает канал данных, а также функции, связанных с сетевыми задачами, такими как преобразование имени сервера в IP адрес и форматирование данных для отправки по сети.
Примечание. Поведение модуля может зависеть от платформы, поскольку выполняются вызовы API сокетов операционной системы.
Интерфейс Python представляет собой прямую трансляцию системного вызова Unix и интерфейса библиотеки для сокетов в объектно-ориентированный стиль Python. Функция socket.socket() возвращает объект Socket , методы которого реализуют различные системные вызовы сокетов.
Типы параметров функций модуля несколько более высокоуровневые, чем в интерфейсе языка C: как и в случае операций чтения/записи с файлами, распределение буфера при операциях приема данных происходит автоматически, а длина буфера неявно определяется операциями отправки.
Сокеты можно настроить для работы в качестве сервера и прослушивания входящих сообщений или для подключения к другим приложениям в качестве клиента. После подключения обоих концов сокета TCP/IP обмен данными становится двунаправленным.
Пример создания и использования сокетов на примере TCP/IP сервер и клиента.
Этот пример, основанный на стандартной документации библиотеки, принимает входящие сообщения и передает их обратно отправителю. Он начинается с создания сокета TCP/IP, а затем метод sock.bind() используется для связывания сокета с адресом сервера.
Примечание. Для успешного тестирования примера, код клиента и сервера необходимо запускать в разных окнах терминала. Код сервера запускается первым.
TCP/IP сервер.
# test-server.py import socket import sys # создаемTCP/IP сокет sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Привязываем сокет к порту server_address = ('localhost', 10000) print('Старт сервера на <> порт <>'.format(*server_address)) sock.bind(server_address) # Слушаем входящие подключения sock.listen(1) while True: # ждем соединения print('Ожидание соединения. ') connection, client_address = sock.accept() try: print('Подключено к:', client_address) # Принимаем данные порциями и ретранслируем их while True: data = connection.recv(16) print(f'Получено: data.decode()>') if data: print('Обработка данных. ') data = data.upper() print('Отправка обратно клиенту.') connection.sendall(data) else: print('Нет данных от:', client_address) break finally: # Очищаем соединение connection.close()
Вызов метода sock.listen(1) переводит сокет в режим сервера, а метод sock.accept() ожидает входящего соединения. Целочисленный аргумент у метода .listen — это количество соединений, которые система должна поставить в очередь в фоновом режиме, прежде чем отклонять новых клиентов. В этом примере предполагается, что одновременно будет работать только одно соединение.
Метод sock.accept() возвращает открытое соединение между сервером и клиентом вместе с адресом клиента. На самом деле соединение представляет собой другой сокет на другом порту (назначенный ядром). Данные считываются из соединения с помощью метод sock.recv() и передаются с помощью sock.sendall() .
Когда общение с клиентом завершено, соединение необходимо очистить с помощью sock.close() . В этом примере используется блок try/finally , чтобы гарантировать, что метод sock.close() всегда вызывается, даже в случае ошибки.
Клиентская программа настраивает свой сокет иначе, чем сервер. Вместо привязки к порту и прослушивания он использует метод sock.connect() для подключения сокета непосредственно к удаленному адресу.
TCP/IP клиент.
# test-client.py import socket import sys # СоздаемTCP/IP сокет sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Подключаем сокет к порту, через который прослушивается сервер server_address = ('localhost', 10000) print('Подключено к <> порт <>'.format(*server_address)) sock.connect(server_address) try: # Отправка данных mess = 'Hello Wоrld!' print(f'Отправка: mess>') message = mess.encode() sock.sendall(message) # Смотрим ответ amount_received = 0 amount_expected = len(message) while amount_received amount_expected: data = sock.recv(16) amount_received += len(data) mess = data.decode() print(f'Получено: data.decode()>') finally: print('Закрываем сокет') sock.close()
После установления соединения данные могут быть отправлены через сокет с помощью метода sock.sendall() и получены с помощью sock.recv() , как и на сервере. Когда все сообщения отправлены, а копия получена, то сокет закрывается, чтобы освободить порт.
Работа клиента и сервера вместе.
Клиент и сервер должны запускаться в отдельных окнах терминала, чтобы они могли взаимодействовать друг с другом. Выходные данные сервера показывают входящее соединение и данные, а также ответ, отправленный обратно клиенту.
Старт сервера на localhost порт 10000 Ожидание соединения. Подключено к: ('127.0.0.1', 34800) Получено: Hello Wоrld! Обработка данных. Отправка обратно клиенту. Получено: Нет данных от: ('127.0.0.1', 34800) Ожидание соединения. .
Выходные данные клиента показывают исходящее сообщение и ответ сервера.
Подключено к localhost порт 10000 Отправка: Hello Wоrld! Получено: HELLO WоRLD! Закрываем сокет
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Советы по программированию сокетов
- Константы, определяемые модулем socket
- Семейства сокетов, поддерживаемых модулем socket
- Функция socket() модуля socket, создает новый сокет
- Функция create_connection() модуля socket
- Функция create_server() модуля socket
- Функция socketpair() модуля socket
- Функция fromfd() модуля socket
- Функция fromshare() модуля socket
- Объект Socket модуля socket
- Функция close() модуля socket
- Функция getaddrinfo() модуля socket
- Функция getfqdn() модуля socket
- Функция gethostbyname() модуля socket
- Функция gethostbyname_ex() модуля socket
- Функция gethostname() модуля socket
- Функция gethostbyaddr() модуля socket
- Функция getnameinfo() модуля socket
- Функция getprotobyname() модуля socket
- Функция getservbyname() модуля socket
- Функция getservbyport() модуля socket
- Функция has_dualstack_ipv6() модуля socket
- Функции getdefaulttimeout() и setdefaulttimeout() модуля socket
- Функции CMSG_LEN и CMSG_SPACE модуля socket
- Функция sethostname() модуля socket
- Функция if_nameindex() модуля socket
- Функции if_nametoindex() и if_indextoname модуля socket
- Функции различных преобразований модуля socket
- Ошибки и исключения, определяемые модулем socket
Сокеты в Python для начинающих
В далеком для меня 2010 году я писал статью для начинающих про сокеты в Python. Сейчас этот блог канул в небытие, но статья мне показалась довольно полезной. Статью нашел на флешке в либровском документе, так что это не кросспост, не копипаст — в интернете ее нигде нет.
Что это
Для начала нужно разобраться что такое вообще сокеты и зачем они нам нужны. Как говорит вики, сокет — это программный интерфейс для обеспечения информационного обмена между процессами. Но гораздо важнее не зазубрить определение, а понять суть. Поэтому я тут постараюсь рассказать все как можно подробнее и проще.
Существуют клиентские и серверные сокеты. Вполне легко догадаться что к чему. Серверный сокет прослушивает определенный порт, а клиентский подключается к серверу. После того, как было установлено соединение начинается обмен данными.
Рассмотрим это на простом примере. Представим себе большой зал с множеством небольших окошек, за которыми стоят девушки. Есть и пустые окна, за которыми никого нет. Те самые окна — это порты. Там, где стоит девушка — это открытый порт, за которым стоит какое-то приложение, которое его прослушивает. То есть, если, вы подойдете к окошку с номером 9090, то вас поприветствуют и спросят, чем могут помочь. Так же и с сокетами. Создается приложение, которое прослушивает свой порт. Когда клиент устанавливает соединение с сервером на этом порту именно данное приложение будет ответственно за работу этим клиентом. Вы же не подойдете к одному окошку, а кричать вам будут из соседнего 🙂
После успешной установки соединения сервер и клиент начинают обмениваться информацией. Например, сервер посылает приветствие и предложение ввести какую-либо команду. Клиент в свою очередь вводит команду, сервер ее анализирует, выполняет необходимые операции и отдает клиенту результат.
Сервер
Сейчас создайте два файла — один для сервера, а другой для клиента.
В Python для работы с сокетами используется модуль socket:
import socket
Прежде всего нам необходимо создать сокет:
sock = socket.socket()
Здесь ничего особенного нет и данная часть является общей и для клиентских и для серверных сокетов. Дальше мы будем писать код для сервера. Это вполне логично — зачем нам писать клиентское приложение, если некуда подключаться 🙂
Теперь нам нужно определиться с хостом и портом для нашего сервера. Насчет хоста — мы оставим строку пустой, чтобы наш сервер был доступен для всех интерфейсов. А порт возьмем любой от нуля до 65535. Следует отметить, что в большинстве операционных систем прослушивание портов с номерами 0 — 1023 требует особых привилегий. Я выбрал порт 9090. Теперь свяжем наш сокет с данными хостом и портом с помощью метода bind, которому передается кортеж, первый элемент (или нулевой, если считать от нуля) которого — хост, а второй — порт:
sock.bind(('', 9090))
Теперь у нас все готово, чтобы принимать соединения. С помощью метода listen мы запустим для данного сокета режим прослушивания. Метод принимает один аргумент — максимальное количество подключений в очереди. Напряжем нашу бурную фантазию и вспомним про зал с окошками. Так вот этот параметр определяет размер очереди. Если он установлен в единицу, а кто-то, явно лишний, пытается еще подстроится сзади, то его пошлют 🙂 Установим его в единицу:
sock.listen(1)
Ну вот, наконец-то, мы можем принять подключение с помощью метода accept, который возвращает кортеж с двумя элементами: новый сокет и адрес клиента. Именно этот сокет и будет использоваться для приема и посылке клиенту данных.
conn, addr = sock.accept()
Вот и все. Теперь мы установили с клиентом связь и можем с ним «общаться». Т.к. мы не можем точно знать, что и в каких объемах клиент нам пошлет, то мы будем получать данные от него небольшими порциями. Чтобы получить данные нужно воспользоваться методом recv, который в качестве аргумента принимает количество байт для чтения. Мы будем читать порциями по 1024 байт (или 1 кб):
while True: data = conn.recv(1024) if not data: break conn.send(data.upper())
Как мы и говорили для общения с клиентом мы используем сокет, который получили в результате выполнения метода accept. Мы в бесконечном цикле принимаем 1024 байт данных с помощью метода recv. Если данных больше нет, то этот метод ничего не возвращает. Таким образом мы можем получать от клиента любое количество данных.
Дальше в нашем примере для наглядности мы что-то сделаем с полученными данными и отправим их обратно клиенту. Например, с помощью метода upper у строк вернем клиенту строку в верхнем регистре.
Теперь можно и закрыть соединение:
conn.close()
Собственно сервер готов. Он принимает соединение, принимает от клиента данные, возвращает их в виде строки в верхнем регистре и закрывает соединение. Все просто 🙂 В итоге у вас должно было получиться следующее:
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket sock = socket.socket() sock.bind(('', 9090)) sock.listen(1) conn, addr = sock.accept() print 'connected:', addr while True: data = conn.recv(1024) if not data: break conn.send(data.upper()) conn.close()
Клиент
Думаю, что теперь будет легче. Да и само клиентское приложение проще — нам нужно создать сокет, подключиться к серверу послать ему данные, принять данные и закрыть соединение. Все это делается так:
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket sock = socket.socket() sock.connect(('localhost', 9090)) sock.send('hello, world!') data = sock.recv(1024) sock.close() print data
Думаю, что все понятно, т.к. все уже разбиралось ранее. Единственное новое здесь — это метод connect, с помощью которого мы подключаемся к серверу. Дальше мы читаем 1024 байт данных и закрываем сокет.
- Python
- Программирование
Введение в сетевое программирование. Сокеты.¶
Сокет — это программный интерфейс для обеспечения информационного обмена между процессами.
- Серверный — сокет, который принимает сообщения.
- Клиентский — сокет, который отправляет сообщения.
- Потоковые (на основе TCP, в коде обозначаются SOCK_STREAM ) — сокеты с установленным соединением на основе протокола TCP, передают поток байтов, который может быть двунаправленным — т.е. приложение может и получать и отправлять данные.
- Дейтаграммные (на основе UDP, в коде обозначаются SOCK_DGRAM ) — сокеты, не требующие установления явного соединения между ними. Сообщение отправляется указанному сокету и, соответственно, может получаться от указанного сокета.
Сокет состоит из IP-адреса и порта.
IP-адрес — уникальный сетевой адрес узла в компьютерной сети, построенной по протоколу IP. В версии протокола IPv4 IP-адрес имеет длину 4 байта (например, 192.168.0.3), а в версии протокола IPv6 IP-адрес имеет длину 16 байт (например, 2001:0db8:85a3:0000:0000:8a2e:0370:7334). IP-адрес должен быть уникален.
Порт — натуральное число, записываемое в заголовках протоколов транспортного уровня (TCP, UDP и др.). Порт используется для определения процесса-получателя пакета в пределах одного хоста.
В python для работы с сокетами используется встроенная библиотека socket . Одной из основных функций модуля является функция socket() , которая возвращает объект типа сокет, обладающий соответствующими функциями для работы с соединением.:
class socket.socket sock = socket.socket()
- socket.bind(address) — Привязывает сокет к адресу address (инициализирует IP-адрес и порт). Сокет не должен быть привязан до этого.
- socket.listen([backlog]) — Переводит сервер в режим приема соединений. Параметр«backlog (int)« – количество соединений, которые будет принимать сервер.
- socket.accept() — Принимает соединение и блокирует приложение в ожидании сообщения от клиента. В результате возвращает кортеж:
- conn : объект соединения (сокет), который можно использовать для отправки/получения данных;
- address : адрес клиента.
Работа с сокетом во многом схожа с работой с файловым объектом. Принцип — открыли соединение — считали данные — закрыли соединение.
Создание серверного сокета: .. code
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # создаем сокет sock.bind(('', 55000)) # связываем сокет с портом, где он будет ожидать сообщения sock.listen(10) # указываем сколько может сокет принимать соединений print('Server is running, please, press ctrl+c to stop') while True: conn, addr = sock.accept() # начинаем принимать соединения print('connected:', addr) # выводим информацию о подключении data = conn.recv(1024) # принимаем данные от клиента, по 1024 байт print(str(data)) conn.send(data.upper()) # в ответ клиенту отправляем сообщение в верхнем регистре conn.close() # закрываем соединение
Создание клиентского сокета: .. code
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # создаем сокет sock.connect(('localhost', 55000)) # подключемся к серверному сокету sock.send(bytes('Hello, world', encoding = 'UTF-8')) # отправляем сообщение data = sock.recv(1024) # читаем ответ от серверного сокета sock.close() # закрываем соединение print(data)
Обратите внимание, клиент отправляет байтовую строку, а не обычную. Сервер так же принимает в качестве сообщения байтовую строку и должен в ответе вернуть объект того же типа.
Задания¶
- Реализовать чат без графического интерфейса, который позволит обмениваться сообщениями только между клиентом и сервером. Клиент должен получать сообщения сервера в том числе.
- С помощью модуля easygui (см. полезные ссылки), добавьте в разработанный чат простой графический интерфейс.
- Разработайте приложение, которое будет запрашивать у пользователя название файла, а затем отправлять содержимое этого файла серверу. Сервер будет подсчитывать количество слов и возвращать ответ.
- Добавьте к чату из задачи 2 чат-бота на стороне сервера. Добавьте 4-5 фраз, которые сервер будет отправлять по определённым условиям.
Полезные ссылки:¶
- Easygui
- Файловый ввод-вывод
- Работа с файлами в Python
© Copyright Revision 07b34d26 .
Built with Sphinx using a theme provided by Read the Docs.
Read the Docs v: latestVersions latest Downloads html On Read the Docs Project Home Builds Free document hosting provided by Read the Docs.
