7.1 Що таке сокети?
Давайте копнемо ще глибше. Спочатку ми навчилися працювати з request, потім з http.client, потім з проксі. Що ж далі? А далі ми зазирнемо всім цим бібліотекам під капот…
Сокет (дослівно — розетка) — це точка в мережі, через яку дані відправляються і приймаються. Сокет можна уявити як кінцеву точку двостороннього каналу зв'язку між двома програмами, які працюють на одній або різних машинах.
Сокети підтримують різні мережеві протоколи, але найчастіше використовуються два:
-
TCP
(Transmission Control Protocol): Надійний протокол, який забезпечує встановлення з'єднання, перевірку цілісності даних і їх правильну послідовність. -
UDP
(User Datagram Protocol): Протокол, не орієнтований на з'єднання, який не гарантує доставку даних, але є більш швидким та ефективним для певних типів застосунків.
Для ідентифікації сокета використовуються IP-адреса (ідентифікуючий пристрій у мережі) і порт (ідентифікуючий конкретний додаток або сервіс на пристрої).
Важливо!
IP-адреса і порт однозначно
ідентифікують програму в мережі. Це як адреса дому та номер
квартири. Адреса дому (IP-адреса) — це адреса вашого комп'ютера в
мережі, а порт — це номер квартири, який програма використовує для
отримання і відправлення повідомлень.
Більш детально про те, що таке IP-адреса і порт, ви можете дізнатися в лекціях, присвячених пристрою мережі.
Основні етапи роботи програми з сокетами:
- Створення сокета: Програма створює сокет, вказуючи тип протоколу (TCP або UDP).
- Прив'язка до адреси: Сокет прив'язується до IP-адреси і номера порта, щоб бути доступним для з'єднань або відправки/отримання даних.
-
Прослуховування та встановлення з'єднання (для TCP):
- Прослуховування: Сокет на стороні сервера переводиться в режим прослуховування, очікуючи вхідних з'єднань.
- Встановлення з'єднання: Клієнт ініціює з'єднання з сервером. Сервер приймає з'єднання, створюючи новий сокет для взаємодії з клієнтом.
- Обмін даними: Дані передаються між клієнтом і сервером. У випадку TCP дані передаються в надійному порядку.
- Закриття з'єднання: Після завершення обміну даними з'єднання закривається.
Переваги використання сокетів:
- Гнучкість: Сокети дозволяють додаткам обмінюватися даними незалежно від їх місцезнаходження та платформи.
- Продуктивність: Сокети забезпечують швидкий та ефективний спосіб передачі даних, особливо у випадку використання UDP.
- Надійність (у випадку TCP): Протокол TCP забезпечує надійну доставку даних з перевіркою цілісності та відновленням втрачених пакетів.
7.2 Створення сокет-сервера
Робота з сокетами в Python здійснюється за допомогою вбудованого модуля socket
, який
надає інтерфейс
для низькорівневого мережевого програмування.
За допомогою сокетів можна створити socket-сервер
— додаток/об'єкт, який отримуватиме
запити від клієнтів і
відповідатиме їм. А також socket-клієнт
— додаток/об'єкт, який буде надсилати запити
socket-серверу і
отримуватиме від нього відповіді.
Щоб створити socket-сервер
, потрібно виконати три дії:
- Створити об'єкт socket-сервера.
- Прив'язати (
bind
) його до якоїсь IP і порта. - Увімкнути режим прослуховування (
listen
) вхідних повідомлень.
Виглядати цей код буде приблизно так:
import socket
# Створення сокета
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Прив'язка сокета до адреси і порта
server_socket.bind(('localhost', 12345))
# Прослуховування вхідних з'єднань
server_socket.listen(5)
print("Сервер очікує з'єднання...")
Тут socket.AF_INET
вказує, що ми використовуємо IPv4 для мережевого протоколу,
а socket.SOCK_STREAM
означає, що ми використовуємо TCP. Ці параметри найчастіше
використовуються для створення мережевих додатків.
Після того, як надійшло вхідне повідомлення, потрібно зробити ще чотири дії:
- Встановити
(accept)
з'єднання з клієнтом. - Отримати
(receive)
запит (дані) від клієнта. - Надіслати
(send)
клієнту відповідь — теж якісь дані. - Закрити
(close)
з'єднання.
Виглядає цей код приблизно так:
# Прийняття нового з'єднання
client_socket, client_address = server_socket.accept()
print(f"З'єднання встановлено з {client_address}")
# Отримання даних від клієнта
data = client_socket.recv(1024)
print(f"Отримано: {data.decode('utf-8')}")
# Відправка даних клієнту
client_socket.sendall(b'Hello, client!')
# Закриття з'єднання з клієнтом
client_socket.close()
Метод sendall()
використовується замість send()
, тому що він гарантує,
що всі дані будуть відправлені. Метод send()
може відправити тільки частину даних,
якщо буфер заповниться.
Число 1024 у recv(1024)
вказує максимальну кількість байтів, яку можна
отримати за один раз. Це допомагає контролювати обсяг даних, оброблюваних за одну операцію.
Код останнього прикладу зазвичай виконується в безкінечному циклі — сервер обробляє запит, потім чекає нового, потім його обробляє, і так постійно.
Якщо ви хочете запустити його у себе або просто побачити повний приклад, то наведу його тут:
import socket
# Створення сокета
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Прив'язка сокета до адреси і порта
server_socket.bind(('localhost', 12345))
# Прослуховування вхідних з'єднань
server_socket.listen(5)
print("Сервер очікує з'єднання...")
while True:
# Прийняття нового з'єднання
client_socket, client_address = server_socket.accept()
print(f"З'єднання встановлено з {client_address}")
# Отримання даних від клієнта
data = client_socket.recv(1024)
print(f"Отримано: {data.decode('utf-8')}")
# Відправка даних клієнту
client_socket.sendall(b'Hello, client!')
# Закриття з'єднання з клієнтом
client_socket.close()
7.3 Створення сокет-клієнта
Сокет-сервер ми створили, тепер давайте напишемо свій сокет-клієнт, який буде звертатися до сервера і отримувати від нього дані у відповідь.
Для цього потрібно виконати п'ять дій:
- Створити об'єкт
socket-клієнта
. - Встановити з'єднання
(connect)
з IP-адресою і портом нашого socket-сервера. - Відправити
(send)
повідомлення на сервер. - Отримати
(receive)
дані від сервера. - Закрити
(close)
з'єднання.
Насправді це простіше, ніж здається. Ось як буде виглядати цей код:
import socket
# Створення сокета
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Встановлення з'єднання з сервером
client_socket.connect(('localhost', 12345))
# Відправка даних на сервер
client_socket.sendall(b'Hello, server!')
# Отримання даних від сервера
data = client_socket.recv(1024)
print(f"Отримано від сервера: {data.decode('utf-8')}")
# Закриття сокета
client_socket.close()
Якщо на тій стороні немає запущеного socket-сервера
або з'єднання обірвалося, то виникне виняток типу
socket.error
. Так що не забувайте обробляти винятки.
На цьому ми сьогодні закінчимо роботу з сокетами.
При будь-якій роботі з мережею у вас знову і знову будуть спливати хости, порти, IP-адреси, встановлення з'єднань, прослуховування запитів і все в тому ж дусі. Тож розуміння того, як це працює глибоко всередині, дуже сильно полегшить вам життя і допоможе об'єднати розрізнені знання в єдину картину.