Пишем свой веб-сервер на Python: сокеты
Подпишись на обновления блогa, чтобы не пропустить следующий пост!
Оглавление
- Что определяет хорошего разработчика ПО?
- Что же такое веб-сервер?
- Как общаться с клиентами по сети
- Простейший TCP сервер
- Простейший TCP клиент
- Заключение
- Cсылки по теме
Лирическое отступление: что определяет хорошего разработчика?

Разработка ПО — это инженерная дисциплина. Если вы хотите стать действительно профессиональным разработчиком, то необходимо в себе развивать качества инженера, а именно: системный подход к решению задач и аналитический склад ума. Для вас должно перестать существовать слово магия. Вы должны точно знать как и почему работают системы, с которыми вы взаимодействуете (между прочим, полезное качество, которое находит применение и за пределами IT).
К сожалениею (или к счастью, ибо благоприятно складывается на уровне доходов тех, кто осознал), существует огромное множество людей, которые пишут код без должного понимания важности этих принципов. Да, такие горе-программисты могут создавать работающие до поры до времени системы, собирая их из найденных в Интернете кусочков кода, даже не удосужившись прочитать, как они реализованы. Но как только возникает первая нестандартная проблема, решение которой не удается найти на StackOverflow, вышеупомянутые персонажи превращаются в беспомощных жертв кажущейся простоты современной разработки ПО.
Для того, чтобы не оказаться одним из таких бедолаг, необходимо постоянно инвестировать свое время в получение фундаментальных знаний из области Computer Science. В частности, для прикладных разработчиков в большинстве случаев таким фундаментом является операционная система, в которой выполняются созданные ими программы.
Веб-фреймворки и контейнеры приложений рождаются и умирают, а инструменты, которыми они пользуются, и принципы, на которых они основаны, остаются неизменными уже десятки лет. Это означает, что вложение времени в изучение базовых понятий и принципов намного выгоднее в долгосрочной перспективе. Сегодня мы рассмотрим одну из основных для веб-разработчика концепций — сокеты. А в качестве прикладного аспекта, мы разберемся, что же такое на самом деле веб-сервер и начнем писать свой.
Что такое веб-сервер?
Начнем с того, что четко ответим на вопрос, что же такое веб-сервер?
В первую очередь — это сервер. А сервер — это процесс (да, это не железка), обслуживающий клиентов. Сервер — фактически обычная программа, запущенная в операционной системе. Веб-сервер, как и большинство программ, получает данные на вход, преобразовывает их в соответствии с бизнес-требованиями и осуществляет вывод данных. Данные на вход и выход передаются по сети с использованием протокола HTTP. Входные данные — это запросы клиентов (в основном веб-браузеров и мобильных приложений). Выходные данные — это зачастую HTML-код подготовленных веб-страниц.

На данном этапе логичными будут следующие вопросы: что такое HTTP и как передавать данные по сети? HTTP — это простой текстовый (т.е. данные могут быть прочитаны человеком) протокол передачи информации в сети Интернет. Протокол — это не страшное слово, а всего лишь набор соглашений между двумя и более сторонами о правилах и формате передачи данных. Его рассмотрение мы вынесем в отдельную тему, а далее попробуем понять, как можно осуществлять передачу данных по сети.
Как компьютеры взаимодействуют по сети
В Unix-подобных системах принят очень удобный подход для работы с различными устройствами ввода/вывода — рассматривать их как файлы. Реальные файлы на диске, мышки, принтеры, модемы и т.п. являются файлами. Т.е. их можно открыть, прочитать данные, записать данные и закрыть.

При открытии файла операционной системой создается т.н. файловый дескриптор. Это некоторый целочисленный идентификатор, однозначно определяющий файл в текущем процессе. Для того, чтобы прочитать или записать данные в файл, необходимо в соответсвующую функцию (например, read() или write() ) передать этот дескриптор, чтобы четко указать, с каким файлом мы собираемся взаимодействовать.
int fd = open("/path/to/my/file", . ); char buffer[1024]; read(fd, buffer, 1024); write(fd, "some data", 10); close(fd);
Очевидно, что т.к. общение компьютеров по сети — это также про ввод/вывод, то и оно должно быть организовано как работа с файлами. Для этого используется специальный тип файлов, т.н. сокеты.
Сокет — это некоторая абстракция операционной системы, представляющая собой интерфейс обмена данными между процессами. В частности и по сети. Сокет можно открыть, можно записать в него данные и прочитать данные из него.

Т.к. видов межпроцессных взаимодействий с помощью сокетов множество, то и сокеты могут иметь различные конфигурации: сокет характеризуется семейством протоколов (IPv4 или IPv6 для сетевого и UNIX для локального взаимодействия), типом передачи данных (потоковая или датаграммная) и протоколом (TCP, UDP и т.п.).
Далее будет рассматриваться исключительно клиент-серверное взаимодействие по сети с использованием сокетов и стека протоколов TCP/IP.
Предположим, что наша прикладная программа хочет передать строку «Hello World» по сети, и соответствующий сокет уже открыт. Программа осуществляет запись этой строки в сокет с использованием функции write() или send() . Как эти данные будут переданы по сети?
Т.к. в общем случае размер передаваемых программой данных не ограничен, а за один раз сетевой адаптер (NIC) может передать фиксировнный объем информации, данные необходимо разбить на фрагменты, не превышающие этот объем. Такие фрагменты называются пакетами. Каждому пакету добавляется некоторая служебная информация, в частности содержащая адреса получателя и отправителя, и они начинают свой путь по сети.

Адрес компьютера в сети — это т.н. IP-адрес. IP (Internet Protocol) — протокол, который позволил объединить множество разнородных сетей по всеми миру в одну общую сеть, которая называется Интернет. И произошло это благодаря тому, что каждому компьютеру в сети был назначен собственный адрес.
В силу особенности маршрутизации пакетов в сети, различные пакеты одной и той же логической порции данных могут следовать от отправителя к получателю разными маршрутами. Разные маршруты могут иметь различную сетевую задержку, следовательно, пакеты могут быть доставлены получателю не в том порядке, в котором они были отправлены. Более того, содержимое пакетов может быть повреждено в процессе передачи.

Вообще говоря, требование получать пакеты в том же порядке, в котором они были отправлены, не всегда является обязательным (например, при передаче потокового видео). Но, когда мы загружаем веб-страницу в браузере, мы ожидаем, что буквы на ней будут расположены ровно в том же порядке, в котором их нам отправил веб-сервер. Именно поэтому HTTP протокол работает поверх надеждного протокола передачи данных TCP, который будет рассмотрен ниже.
Чтобы организовать доставку пакетов в порядке их передачи, необходимо добавить в служебную информацию каждого пакета его номер в цепочке пакетов и на принимающей стороне делать сборку пакетов не в порядке их поступления, а в порядке, определенном этими номерами. Чтобы избежать доставки поврежденных пакетов, необходимо в каждый пакет добавить контрольную сумму и пакеты с неправильной контрольной суммой отбрасывать, ожидая, что они будут отправлены повторно.
Этим занимается специальный протокол потоковой передачи данных — TCP.
TCP — (Transmission Control Protocol — протокол управления передачей) — один из основных протоколов передачи данных в Интернете. Используется для надежной передачи данных с подтверждением доставки и сохранением порядка пакетов.

В силу того, что передачей данных по сети по протоколу TCP на одном и том же компьютере может заниматься одновременно несколько программ, для каждого из таких сеансов передачи данных необходимо поддерживать свою последовательность пакетов. Для этого TCP вводит понятие соединения. Соединение — это просто логическое соглашение между принимающей и передающей сторонами о начальных и текущих значениях номеров пакетов и состоянии передачи. Соединение необходимо установить (обменявшись несколькими служебными пакетами), поддерживать (периодически передавать данные, чтобы не наступил таймаут), а затем закрыть (снова обменявшись несколькими служебными пакетами).
Итак, IP определяет адрес компьютера в сети. Но, в силу наличия TCP соединений, пакеты могут принадлежать различным соединениям на одной и той же машине. Для того, чтобы различать соединения, вводится понятие TCP-порт. Это всего лишь пара чисел (одно для отправителя, а другое для получателя) в служебной информации пакета, определяющая, в рамках какого соединения должен рассматриваться пакет. Т.е. адрес соединения на этой машине.
Простейший TCP сервер
Теперь перейдем к практике. Попробуем создать свой собственный TCP-сервер. Для этого нам понадобится модуль socket из стандартной библиотеки Python.
Основная проблема при работе с сокетами у новичков связана с наличием обязательного магического ритуала подготовки сокетов к работе. Но имея за плечами теоретические знания, изложенные выше, кажущаяся магия превращается в осмысленные действия. Также необходимо отметить, что в случае с TCP работа с сокетами на сервере и на клиенте различается. Сервер занимается ожиданием подключений клиентов. Т.е. его IP адрес и TCP порт известны потенциальным клиентам заранее. Клиент может подключиться к серверу, т.е. выступает активной стороной. Сервер же ничего не знает об адресе клиента до момента подключения и не может выступать инициатором соединения. После того, как сервер принимает входящее соединения клиента, на стороне сервера создается еще один сокет, который является симметричным сокету клиента.
Итак, создаем серверный сокет:
# python3 import socket serv_sock = socket.socket(socket.AF_INET, # задамем семейство протоколов 'Интернет' (INET) socket.SOCK_STREAM, # задаем тип передачи данных 'потоковый' (TCP) proto=0) # выбираем протокол 'по умолчанию' для TCP, т.е. IP print(type(serv_sock)) #
А где же обещанные int fd = open(«/path/to/my/socket») ? Дело в том, что системный вызов open() не позволяет передать все необходимые для инициализации сокета параметры, поэтому для сокетов был введен специальный одноименный системный вызов socket() . Python же является объектно-ориентированным языком, в нем вместо функций принято использовать классы и их методы. Код модуля socket является ОО-оберткой вокрут набора системных вызовов для работе с сокетами. Его можно представить себе, как:
class socket: # Да, да, имя класса с маленькой буквы :( def __init__(self, sock_familty, sock_type, proto): self._fd = system_socket(sock_family, sock_type, proto) def write(self, data): # на самом деле вместо write используется send, но об этом ниже system_write(self._fd, data) def fileno(self): return self._fd
Т.е. доступ к целочисленному файловому дескриптору можно получить с помощью:
print(serv_sock.fileno()) # 3 или другой int
Так мы работаем с серверным сокетом, а в общем случае на серверной машине может быть несколько сетевых адаптеров, нам необходимо привязать созданный сокет к одному из них:
serv_sock.bind(('127.0.0.1', 53210)) # чтобы привязать сразу ко всем, можно использовать ''
Вызов bind() заставляет нас указать не только IP адрес, но и порт, на котором сервер будет ожидать (слушать) подключения клиентов.
Далее необходимо явно перевести сокет в состояние ожидания подключения, сообщив об этом операционной системе:
backlog = 10 # Размер очереди входящих подключений, т.н. backlog serv_sock.listen(backlog)
После этого вызова операционная система готова принимать подключения от клиентов на этом сокете, хотя наш сервер (т.е. программа) — еще нет. Что же это означает и что такое backlog?
Как мы уже выяснили, взаимодействие по сети происходит с помощью отправки пакетов, а TCP требует установления соединения, т.е. обмена между клиентом и сервером несколькими служебными пакетами, не содержащими реальных бизнес-данных. Каждое TCP соединение обладает состоянием. Упростив, их можно представить себе так:
СОЕДИНЕНИЕ УСТАНАВЛИВАЕТСЯ -> УСТАНОВЛЕНО -> СОЕДИНЕНИЕ ЗАКРЫВАЕТСЯ
Таким образом, параметр backlog определяет размер очереди для установленных, но еще не обработанных программой соединений. Пока количество подключенных клиентов меньше, чем этот параметр, операционная система будет автоматически принимать входящие соединения на серверный сокет и помещать их в очередь. Как только количество установленных соединений в очереди достигнет значения backlog, новые соединения приниматься не будут. В зависимости от реализации (GNU Linux/BSD), OC может явно отклонять новые подключения или просто их игнорировать, давая возможность им дождаться освобождения места в очереди.
Теперь необходимо получить соединение из этой очереди:
client_sock, client_addr = serv_sock.accept()
В отличие от неблокирующего вызова listen() , который сразу после перевода сокета в слушающее состояние, возвращает управление нашему коду, вызов accept() является блокирующим. Это означает, что он не возвращает управление нашему коду до тех пор, пока в очереди установленных соединений не появится хотя бы одно подключение.
На этом этапе на стороне сервера мы имеем два сокета. Первый, serv_sock , находится в состоянии LISTEN , т.е. принимает входящие соединения. Второй, client_sock , находится в состоянии ESTABLISHED , т.е. готов к приему и передаче данных. Более того, client_sock на стороне сервера и клиенсткий сокет в программе клиента являются одинаковыми и равноправными участниками сетевого взаимодействия, т.н. peer’ы. Они оба могут как принимать и отправлять данные, так и закрыть соединение с помощью вызова close() . При этом они никак не влияют на состояние слушающего сокета.
Пример чтения и записи данных в клиентский сокет:
while True: data = client_sock.recv(1024) if not data: break client_sock.sendall(data)
И опять же справедливый вопрос — где обещанные read() и write() ? На самом деле с сокетом можно работать и с помощью этих двух функций, но в общем случае сигнатуры read() и write() не позволяют передать все возможные параметры чтения/записи. Так, например, вызов send() с нулевыми флагами равносилен вызову write() .
Немного коснемся вопроса адресации. Каждый TCP сокет определяется двумя парами чисел: (локальный IP адрес, локальный порт) и (удаленный IP адрес, удаленный порт) . Рассмотрим, какие адреса на данный момент у наших сокетов:
serv_sock: laddr (ip=, port=53210) raddr (ip=0.0.0.0, port=*) # т.е. любой client_sock: laddr (ip=, port=51573) # случайный порт, назначенный системой raddr (ip=, port=53210) # адрес слушающего сокета на сервере
Полный код сервера выглядит так:
# python3 import socket serv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto=0) serv_sock.bind(('', 53210)) serv_sock.listen(10) while True: # Бесконечно обрабатываем входящие подключения client_sock, client_addr = serv_sock.accept() print('Connected by', client_addr) while True: # Пока клиент не отключился, читаем передаваемые # им данные и отправляем их обратно data = client_sock.recv(1024) if not data: # Клиент отключился break client_sock.sendall(data) client_sock.close()
Подключиться к этому серверу можно с использованием консольной утилиты telnet , предназначенной для текстового обмена информацией поверх протокола TCP:
telnet 127.0.0.1 53210 > Trying 192.168.0.1. > Connected to 192.168.0.1. > Escape character is '^]'. > Hello > Hello
Простейший TCP клиент
На клиентской стороне работа с сокетами выглядит намного проще. Здесь сокет будет только один и его задача только лишь подключиться к заранее известному IP-адресу и порту сервера, сделав вызов connect() .
# python3 import socket client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_sock.connect(('127.0.0.1', 53210)) client_sock.sendall(b'Hello, world') data = client_sock.recv(1024) client_sock.close() print('Received', repr(data))
Заключение
Запоминать что-то без понимания, как это работает — злое зло не самый разумный подход для разработчика. Работа с сокетами тому отличный пример. На первый взгляд может показаться, что уложить в голове последовательность приготовления клиентских и серверных сокетов к работе практически не возможно. Это происходит из-за того, что не сразу понятен смысл производимых манипуляций. Однако, понимая, как осуществляется сетевое взаимодействие, API сокетов сразу становится прозрачным и легко оседает в подкорке. А с точки зрения полезности полученных знаний, я считаю. что понимание принципов сетевого взаимодействия жизненно важно для разработки и отладки действительно сложных веб-проектов.
Другие статьи из серии:
- Пишем свой веб-сервер на Python: процессы, потоки и асинхронный I/O
- Пишем свой веб-сервер на Python: протокол HTTP
- Пишем свой веб-сервер на Python: стандарт WSGI
- Пишем свой веб-сервер на Python: фреймворк Flask
Ссылки по теме
Справочная информация:
Литература
- Beej’s Guide to Network Programming — отличные основы
- UNIX Network Programming — продвинутый уровень
Как создать веб-сервер Python?
Python остается одним из лучших языков программирования для изучения в 2021 году для работы с приложениями для внутренней веб-разработки, машинного обучения, научного моделирования, системных операций. Он считается одним из наиболее доступных языков программирования с динамически типизированным английским синтаксисом и множеством библиотек.
Мы покажем вам, как создать собственный веб-сервер Python для локального тестирования. Весь процесс занимает всего несколько минут и несколько строк кода.
Но сначала давайте рассмотрим, что такое веб-сервер.
Что такое веб-сервер?
В инфраструктуре Интернета сервер является частью модели клиент-сервер. Когда клиентский браузер посещает веб-страницу, он отправляет HTTP-запрос на сервер, содержащий файлы, необходимые для работы веб-сайта. Сервер слушает запрос клиента, обрабатывает его и отвечает необходимыми файлами для представления веб-страницы. Этот контент может быть HTML (текст и мультимедиа, которые вы видите на веб-сайте) и JSON (приложения).
Вы, конечно, стыкались с кодами ошибок сервера во время просмотра Интернета — «файл не найден» или 404 — более популярный вариант. В этих случаях у сервера возникают проблемы с доступом к определенным файлам. При ошибке 404 конкретный файл отсутствует.
У веб-серверов есть больше нюансов, включая классификацию на статические и динамические веб-серверы. Например, статические веб-серверы возвращают файлы только в том виде, в котором они есть, без дополнительной обработки. Динамические веб-серверы представляют базы данных и серверы приложений, к которым вы можете перейти после того, как освоите статические серверы.
Как создать простой веб-сервер на Python?
Запуск веб-сервера Python выполняется быстро и просто, и вам потребуется всего несколько минут, чтобы начать работу. Все, что требуется, — это одна строка кода, чтобы запустить на вашем компьютере простейший из локальных серверов.
При локальном тестировании ваша система становится сервером для клиента, которым является ваш браузер, а файлы хранятся локально в вашей системе. Модуль, который вы будете использовать для создания веб-сервера, — это HTTP-сервер Python. Здесь есть одно предостережение: его можно использовать только как статический файловый сервер. Вам понадобится веб-фреймворк Python, такой как Django, для запуска динамических веб-серверов.
Перейдем к коду, который выглядит следующим образом:
python -m http .server
Введите это в командную строку, и вы должны увидеть сообщение «сервер запущен» и «сервер остановлен», когда вы закроете сервер.
И вот он — ваш первый веб-сервер на Python! По общему признанию, это простая задача, которая делает не что иное, как открытие веб-сервера на порте по умолчанию вашей системы 8000. Порт также можно изменить, указав номер порта в конце строки, например:
python -m http .server 8080
Однако гораздо интереснее и познавательнее создать собственный веб-сервер. В конце концов, лучший способ изучить Python — это практический подход: кодировать, отлаживать, исправлять и повторять.
Создание собственного веб-сервера с использованием Python
Пользовательский веб-сервер позволяет вам делать больше, чем встроенный веб-сервер. Код, представленный ниже, научит вас некоторых важным функциям и процессам. Не пугайтесь длины кода — здесь задействовано лишь несколько ключевых концепций. Вам не нужно вводить все это вручную, чтобы проверить это самостоятельно, но обратите внимание на важность этих концепций.
from http.server import HTTPServer, BaseHTTPRequestHandler # Встроенная библиотека Python
import time hostName = "localhost" serverPort = 8080 #You can choose any available port; by default, it is 8000 Class MyServer(BaseHTTPRequestHandler): def do_GET(self): //the do_GET method is inherited from BaseHTTPRequestHandler self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write(bytes("https://testserver.com ", "utf-8")) self.wfile.write(bytes("Request: %s
" % self.path, "utf-8")) self.wfile.write(bytes("", "utf-8")) self.wfile.write(bytes("This is an example web server.
", "utf-8")) self.wfile.write(bytes("", "utf-8")) if __name__ == "__main__": webServer = HTTPServer((hostName, serverPort), MyServer) print("Server started http://%s:%s" % (hostName, serverPort)) #Server starts try: webServer.serve_forever() except KeyboardInterrupt: pass webServer.server_close() #Executes when you hit a keyboard interrupt, closing the server print("Server stopped.")
Прежде чем мы перейдем к критическим частям, давайте быстро рассмотрим несколько моментов. Класс MyServer записывает в выходной поток (wfile), который отправляется в качестве ответа клиенту с помощью «self.wfile.write ()». Здесь мы пишем элементарную HTML-страницу на лету.
Мы рассмотрим некоторые из наиболее важных моментов, происходящих здесь, а именно:
- Модуль http.server
- Классы HTTPServer и BaseHTTPRequestHandler, производные от библиотеки http.server
- Метод do_GET
HTTP-сервер — это стандартный модуль библиотеки Python, в котором есть классы, используемые для взаимодействия клиент-сервер. Это два класса — HTTPServer и BaseHTTPRequestHandler. Последний обращается к серверу через первый. HTTPServer хранит адрес сервера как переменные экземпляра, а BaseHTTPRequestHandler вызывает методы для обработки запросов.
Код начинается с основной функции. Затем вызывается класс MyServer, и BaseHTTPRequestHandler вызывает метод do_GET () для удовлетворения запросов. Когда вы прерываете программу, сервер закрывается.
Заключение
Чтобы ваш веб-сервер работал с Python, не нужно много времени. Это основная идея, которую вы должны усвоить. Создание сервера — это небольшой, но важный шаг вперед на пути к созданию полнофункциональных приложений.
Попробуйте код самостоятельно и, возможно, даже найдите проекты Python, включающие реализацию сервера. Есть много доступных проектов, в которых используется эта концепция, поэтому хорошо знать, как реализовать ее в более широком контексте.
Зарегистрируйтесь на Портале
и получите красивый адрес своей странички вида: senior.ua/sergey.ivanov
Как создать веб-сервер на Python
Создание веб-сервера на Python — несложная, но увлекательная задача, которая может стать отличным стартом для начинающих разработчиков. В этой статье мы разберем, как создать простой веб-сервер на языке программирования Python с использованием популярных библиотек.
Шаг 1: Выбор библиотеки
Для создания веб-сервера на Python существует несколько популярных библиотек:
- Flask — легковесный, удобный и легко настраиваемый фреймворк
- Django — мощный, но более сложный фреймворк с большим количеством функций «из коробки»
- FastAPI — современный, быстрый и асинхронный фреймворк
Для нашего примера выберем Flask, так как он идеально подходит для создания небольших веб-серверов и требует минимальных настроек.
Шаг 2: Установка и настройка Flask
Для начала установим Flask с помощью команды:
pip install Flask
Теперь создадим файл с нашим веб-сервером, например, app.py . В этом файле импортируем Flask и создадим экземпляр приложения:
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello, World!" if __name__ == "__main__": app.run()
Здесь мы создаем приложение Flask, определяем один маршрут (главную страницу) и функцию hello() , которая возвращает приветственное сообщение. Также, если файл запущен как основной, то запускается веб-сервер с нашим приложением.
Python-разработчик: новая работа через 9 месяцев
Получится, даже если у вас нет опыта в IT

Шаг 3: Запуск веб-сервера
Для запуска нашего веб-сервера выполним следующую команду в терминале:
python app.py
После запуска вы увидите сообщение о том, что сервер работает на http://127.0.0.1:5000/ . Откройте этот адрес в вашем браузере и увидите сообщение «Hello, World!».
Поздравляем! Вы только что создали свой первый веб-сервер на Python с использованием Flask!
Шаг 4: Дальнейшее развитие
Теперь, когда у вас есть базовый веб-сервер, вы можете добавить больше функциональности, например:
- Реализовать поддержку разных HTTP-методов (GET, POST, PUT, DELETE)
- Добавить обработку запросов с данными (формы, JSON)
- Создать систему аутентификации и авторизации
- Интегрировать базу данных для хранения и обработки данных
Не забывайте изучать документацию и искать примеры в интернете, чтобы углубить свои знания и навыки в создании веб-серверов на Python!
Как написать свой сервер на python
Для создания сервера на языке Python, как и для клиента, также используется класс socket , однако общая работа будет несколько отличаться от работы с сокетом клиента. Рассмотрим определение и работу с сокетом сервера поэтапно.
Привязка сервера
Сервер прослушивает входящие подключения, некоторым образом обрабатывает их и отправляет ответ. Чтобы сервер начал свою работу, вначале нам надо определить для него адрес, по которому он будет прослушивать подключения. Для этого применяется метод bind()
socket.bind(address)
Данный метод принимает адрес, по которому будет запущен сервер. По умолчанию адрес представляет кортеж из двух элементов:
(host, port)
Первый элемент — хост в виде строки. Это может быть, например, IP-адрес в виде «127.0.0.1» или название локального хоста. Второй параметр — числовой номер порта. Порт представляет 2-х байтное значение от 0 до 65535. Поскольку по одному и то же адресу (на одной и той же машине) может быть запущено несколько различных сетевых приложений, то порт позволяет разграничить эти приложения. Например:
import socket server = socket.socket() # создаем объект сокета сервера hostname = socket.gethostname() # получаем имя хоста локальной машины port = 12345 # устанавливаем порт сервера server.bind((hostname, port)) # привязываем сокет сервера к хосту и порту
Здесь сервер будет запускаться на порту 12345. Следует учитывать, что не все порты могут быть свободны. Но, как правило, занятых портов не так много.
В качестве адреса используем имя текущего хоста. Для его получения применяется функция socket.gethostname() (обычно это имя текущего компьютера).
Прослушивание подключений
После привязки сервера его надо запустить на прослушивание подключений. Для этого применяется метод listen()
socket.listen([backlog])
Он принимает параметр backlog — максимальное количество входящих подключений в очереди, разрешенное для сокета. То есть, когда будут покдлючаться клиенты, они будут попадать в очередь и ждать, пока сервер не обработает текущего клиента. Если в очереди уже есть указанное количество клиентов, ожидающих обработки сервером, то все новые клиенты отклоняются. Например:
import socket server = socket.socket() # создаем объект сокета сервера hostname = socket.gethostname() # получаем имя хоста локальной машины port = 12345 # устанавливаем порт сервера server.bind((hostname, port)) # привязываем сокет сервера к хосту и порту server.listen(5) # начинаем прослушиваение входящих подключений
Получение и обработка клиента
Для получения входящих подключений применяется метод accept() . Этот метод возращает кортеж из двух элементов
(conn, address)
Первый элемент — conn представляет еще один объект socket, через который сервер взаимодействует с клиентом. Второй элемент — address — адрес подключившегося клиента. Стоит отметить, что после завершения взаимодействия с клиентом сокет conn надо закрыть методом close()
Используя первый элемент кортежа — conn можно отправлять клиенту сообщения или наоборот получать данные. Для получения данных у сокета применяется метод socket.recv()
bytes = socket.recv(bufsize)
В качестве обязательного параметра он принимает максимальный размер буфера в байтах, которые могут быть получены за раз от другого сокета. Возвращаемое значение — набор байтов, полученых от другого сокета.
Для отправки данных применяется метод socket.send() , который в качестве параметра получает набор отправляемых данных.
socket.send(bytes)
Теперь посмотрим все на примере. Пусть в файле server.py будет определен сервер со следующим кодом:
import socket server = socket.socket() # создаем объект сокета сервера hostname = socket.gethostname() # получаем имя хоста локальной машины port = 12345 # устанавливаем порт сервера server.bind((hostname, port)) # привязываем сокет сервера к хосту и порту server.listen(5) # начинаем прослушиваение входящих подключений print("Server starts") con, addr = server.accept() # принимаем клиента print("connection: ", con) print("client address: ", addr) message = "Hello Client!" # сообщение для отправки клиенту con.send(message.encode()) # отправляем сообщение клиенту con.close() # закрываем подключение print("Server ends") server.close()
Здесь сервер принимает клиента, выводит информацию о его подключении и отправляет клиенту в ответ строку «Hello Client!».
А в файле client.py определим следующий код клиента:
import socket client = socket.socket() # создаем сокет клиента hostname = socket.gethostname() # получаем хост сервера port = 12345 # устанавливаем порт сервера client.connect((hostname, port)) # подключаемся к серверу data = client.recv(1024) # получаем данные с сервера print("Server sent: ", data.decode()) # выводим данные на консоль client.close() # закрываем подключение
Поскольку у нас сервер и клиент будут запускать на одном и том же комптьютере, то для определения адреса сервера для подключения применяется функция socket.gethostname() и порт 12345 — так же как и в коде сервера. После соединения с сервером получаем от него данные и выводим на консоль.
Сначала запустим код сервера. А затем запустим код клиента. В результате сервер примет подключение, выведет информацию о нем на консоль и отправит клиенту сообщение:
c:\python>python server.py Server starts connection: client address: ('192.168.0.102', 61824) Server ends
В частности, здесь видим, что сервер и клиент запущены по адресу 192.168.0.102, причем клиент использует порт 61824.
А клиент получит от сервера сообщение и выведет его на консоль:
c:\python>python client.py Server sent: Hello Client!
Обработка множества клиентов
В данном случае сервер обслуживает одного клиента и прекращает работу. Если мы хотим, чтобы сервер обрабатывал множество клиентов, то мы можем использовать бесконечный цикл:
import socket from datetime import datetime server = socket.socket() # создаем объект сокета сервера hostname = socket.gethostname() # получаем имя хоста локальной машины port = 12345 # устанавливаем порт сервера server.bind((hostname, port)) # привязываем сокет сервера к хосту и порту server.listen(5) # начинаем прослушиваение входящих подключений print("Server running") while True: con, addr = server.accept() # принимаем клиента print("client address: ", addr) message = datetime.now().strftime("%H:%M:%S") # отправляем текущее время con.send(message.encode()) # отправляем сообщение клиенту con.close() # закрываем подключение
В данном случае для примера с помощью встроенного модуля datetime и функции datetime.now().strftime() получаем текущее время в виде строки, которая затем отправляется клиенту. В итоге при запросе клиент будет получать текущее время.
Двунаправленная связь
В примерах выше связь была однонаправленная — сервер отправлял данные, а клиент получал их. Рассмотрим простейшую двунаправленную связь, когда и клиент и сервер и отправляют, и получают данные. Пусть сервер получает от клиента некоторую строку, инвертирует ее и отправляет обратно клиенту:
import socket server = socket.socket() # создаем объект сокета сервера hostname = socket.gethostname() # получаем имя хоста локальной машины port = 12345 # устанавливаем порт сервера server.bind((hostname, port)) # привязываем сокет сервера к хосту и порту server.listen(5) # начинаем прослушиваение входящих подключений print("Server running") while True: con, _ = server.accept() # принимаем клиента data = con.recv(1024) # получаем данные от клиента message = data.decode() # преобразуем байты в строку print(f"Client sent: ") message = message[::-1] # инвертируем строку con.send(message.encode()) # отправляем сообщение клиенту con.close() # закрываем подключение
А клиент пусть определяет код для ввода строки с консоли и ее отправки на сервер:
import socket client = socket.socket() # создаем сокет клиента hostname = socket.gethostname() # получаем хост локальной машины port = 12345 # устанавливаем порт сервера client.connect((hostname, port)) # подключаемся к серверу message = input("Input a text: ") # вводим сообщение client.send(message.encode()) # отправляем сообщение серверу data = client.recv(1024) # получаем данные с сервера print("Server sent: ", data.decode()) client.close() # закрываем подключение
Результат работы. Клиент:
c:\python>python client.py Input a text: hello Server sent: olleh c:\python>
c:\python>python server.py Server running Client sent: hello