В предыдущих видеоуроках мы рассмотрели то,
как устроены процессы и потоки в Python.
В следующих видеолекциях мы будем изучать то,
как устроены сокеты и как работают сетевые программы,
и те знания, которые мы приобрели в предыдущих видеоуроках,
понадобятся нам для организации взаимодействия по сети.
Итак, давайте разберем, что такое сокеты, как они устроены
и попробуем написать свою первую программу клиент-сервер,
которая будет обмениваться данными между собой.
Сокеты — это, прежде всего, кросс-платформенный механизм
для обмена данными между отдельными процессами.
Эти процессы могут работать на разных серверах,
они могут быть написаны на разных языках, и, прежде всего,
программа на Python, которая использует механизм сокетов,
она осуществляет системные вызовы
и взаимодействие с ядром операционной системы.
Как правило, для организации сетевого взаимодействия
нужен сервер, который изначально создает некое соединение
и начинает «слушать» все запросы, которые поступают в него
и программа-клиент, которая присоединяется к серверу
и отправляет ему нужные данные.
Давайте рассмотрим пример серверной программы.
Для того чтобы создать сокет,
мы должны импортировать модуль socket.
Далее мы должны создать объект типа socket из модуля socket.
В него необходимо передать некоторые параметры.
В данном случае это некоторое семейство
— мы используем address family,
мы используем конкретную константу AF_INET, а также тип сокета.
В данном примере мы используем потоковый сокет.
Полную информацию по типам сокетов, по типам address family
можно посмотреть в документации на Python,
либо в документации про то,
как устроена сеть в операционной системе Linux.
Итак, мы создали объект socket — это потоковый сокет.
Далее мы должны вызвать метод bind.
В метод bind мы должны передать некую адресную пару — это host,
в данном случае мы передаем 127.0.0.1,
и порт 127.0.0.1 будет означать, что наш сервер будет слушать
все входящие соединения только локально на одной машине.
Если мы укажем пустую строчку, либо адрес 0.0.0.0,
то наш сервер будет слушать входящие соединения со всех интерфейсов.
Порт — это некая целочисленная константа,
существуют некоторые зарезервированные порты,
например, 80-й порт, обычно на нем работает HTTP-сервер,
43-й порт, 443-й порт.
Как правило, порты с номерами до 2,000 являются системными,
и мы должны использовать адреса больше значений 2,000,
но максимальное значение для порта — это 65,535.
Итак, системный вызов bind зарегистрировал
нашу адресную пару в операционной системе.
Двигаемся дальше.
Далее, для того чтобы начать принимать соединения,
мы должны вызвать метод listen.
У метода listen есть необязательный параметр
— это так называемый backlog, или размер очереди входящих соединений,
которые еще не обработаны, для которых не был вызван метод accept.
Если наш сервер будет не успевать принимать входящие соединения,
то все эти соединения будут копиться в этой очереди,
и если она превысит это максимальное значение,
то операционная система выдаст ошибку
ConnectionRefused для клиентской программы.
Двигаемся дальше.
Мы создали сокет, зарегистрировали адресную пару, вызвали метод listen.
Далее мы должны вызвать метод accept,
для того чтобы начать принимать входящее клиентское соединение.
Системный вызов accept по умолчанию заблокируется,
до тех пор, пока не появится клиентское соединение.
Итак, если клиент вызовет метод connect, то наш метод accept
вернет нам объект, который будет являться полнодуплексным каналом.
У этого объекта будут доступны
методы записи в этот канал и методы чтения.
В нашем примере мы в бесконечном цикле
будем вызывать чтение из нашего полнодуплексного канала.
Если мы ничего не прочитали, это будет означать,
что клиент закрыл соединение и нам необходимо тоже прекратить работу.
В качестве обработки наших данных, которые мы прочитали с канала,
мы просто выводим эти данные в консоль.
После того как мы закончили работу с нашим клиентом,
мы вызываем метод close для нашего объекта,
который представляет собой полнодуплексный канал,
и также закрываем сокет,
который слушает новые соединения со стороны клиента.
Давайте рассмотрим код на стороне клиента.
Для того чтобы установить соединение с сервером,
мы должны создать объект типа socket.socket.
По умолчанию создается потоковый сокет
с семейством address family AF_INET.
После этого мы должны вызвать метод connect.
Connect заблокируется до тех пор,
пока сервер со своей стороны не вызовет метод accept.
После того как системный вызов connect отработал,
наш сокет готов к работе, и для него можно вызывать
методы send, sendall или recv,
для того чтобы получать данные с сервера.
То есть, по сути, мы получили такой же полнодуплексный канал,
с которым можно работать, отправлять и получать данные.
После того как мы завершили работу с нашим клиентским сокетом,
необходимо вызвать метод close.
В Python существует более короткая запись
для создания клиентского сокета — это вызов метода модуля
socket create_connection.
В create_connection мы передаем адресную пару,
необязательный timeout.
Про timeout мы еще с вами будем говорить в следующих видео.
Этот вызов возвращает нам проключенное соединение,
готовое для того, чтобы делать отправку или прием данных.
Давайте попробуем запустить наш код и посмотрим,
как он работает на самом деле.
Нам потребуется код нашего сервера.