Найти в Дзене
ИнфоКомм

Модель OSI. Уровни приложений и транспортный уровень. Клиент-серверное взаимодействие.

Привет, дорогой читатель!

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

Листинг серверной реализации:

https://gist.github.com/alex944591/c09f4cf992b37ed79eac707b4a5d8d13

1. На нулевом этапе клиент-серверного взаимодействия необходимо наличие главного сокета – серверного. Именно он может принять соединение от клиента. Если подзабыли, что такое сокет, то почитать можно тут:

- TCP/IP (Часть 1) просто о сложном;

- TCP/IP (Часть 2) просто о сложном;

- TCP/IP (Часть 3) просто о сложном;

- TCP/IP (Часть 4) просто о сложном.

Запускаем серверное приложение и проверяем средствами ОС наличие серверного сокета:

Серверный сокет в состоянии прослушивания
Серверный сокет в состоянии прослушивания

Обратите внимание на столбец State. Этот столбец отображает текущее состояние серверного сокета. На общей схеме:

2. Первым этапом клиент-серверного взаимодействия является инициация TCP соединения клиентом. Для успешного TCP соединения необходимо трехэтапное рукопожатие (TCP three-way/triple handshake). В качестве клиента будем использовать ncat (в режиме PowerShell), а за процессом рукопожатия подглянем с помощью Wireshark:

ncat -p 65001 127.0.0.1 5001

triple handshake в дампе трафика
triple handshake в дампе трафика

На общей схеме это выглядит так:

-4

Если теперь взглянуть на столбец State то увидим следующее:

-5

Как же дело обстоит в самом серверном приложении? А давайте заглянем в режиме отладки:

Перед соединением, в нашей синхронной реализации клиент-серверного взаимодействия, приложение входит в бесконечный цикл while True и блокируется в точке ожидания подключения:

-6

И как только выполняем команду ncat -p 65001 127.0.0.1 5001, то режим отладчика PyCharm заполняет контекст и убирает блокировку переходя к исполнению следующей строки кода:

-7

Давайте немного разберемся с параметрами подключения:

fd – файловый дескриптор;

AddressFamily.AF_INET – указание на используемый протокол сетевого уровня - IPv4;

SocketFind.SOCK_STREAM - указание на используемый протокол транспортного уровня - TCP;

laddr (локальный серверный сокет) – 127.0.0.1: 5001

raddr (клиентский сокет) - 127.0.0.1: 65001

в результате установления соединения, в переменную client_socket попадает объект типа socket.

Этому объекту ОС выделяет буфер:

-8

Т.е. сервер выделяет для взаимодействия с клиентом входящий буфер (recv), для приема данных от клиента и исходящий буфер (send), для передачи данных клиенту. Представьте себе это как почтовый ящик с двумя ячейками. С точки зрения сервера мы даем клиенту доступ к обоим ячейкам. Если у клиентского сокета есть что-то для сервера, то он кладет данные в ячейку recv. Сервер же в свою очередь данные для клиента помещает в ячейку send.

Вот хорошая картинка из книги Мэттью Фаулера «Asyncio и конкурентное программирование на Python»:

Запись и чтение из/в клиентский сокет.
Клиент подключается к серверному сокету. Затем сервер создает новый сокет для взаимодействия с клиентом
Запись и чтение из/в клиентский сокет. Клиент подключается к серверному сокету. Затем сервер создает новый сокет для взаимодействия с клиентом

3. Когда на транспортном уровне установлено соединение, а прикладному уровню выделены необходимые буферы становится возможным дальше обмениваться данными. Давайте посмотрим, как это происходит с разных углов обзора:

Обзор в консоли клиента:

-10

Обзор на сети (Wireshark):

-11

Обзор из приложения:

-12

Теперь отправим, что-нибудь серверу в ответ:

-13
-14
-15

Далее наш сервер обработает полученный запрос и дает возможность ответить что-нибудь клиенту:

-16
-17
-18

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

https://gist.github.com/alex944591/26100262fb16cd60c0248d08ef4e338e

Если понравилось - ставьте лайки, делитесь с друзьями, коллегами в соцсетях. Все это придает смысл моему альтруизму :)

До скорого!