Сокеты в Python, чат в 50 строк
- среда, 22 декабря 2021 г. в 00:49:00
На очередной практике по Java, не предвещающей ничего необычного, преподаватель ворвался в аудиторию и с порога заявил: "Сегодня мы с вами познакомимся с сокетами и напишем прототип собственного чата".
"А вечер-то перестаёт быть томным" - подумал я и не ошибся. Чёрт возьми, это какая-то магия, вертелось в моей голове по пути домой. Тут надо отметить, что я не только бедный студент, но ещё и преподаватель в кружке программирования, поэтому после столкновения с такой интересной темой во мне затаилось жгучее желание поделиться ею со своими ребятами.
Так и родилась эта статья, которая является крайне упрощенным пособием по использованию веб-сокетов для начинающих изучать программирование.
Для начала определимся, что это за зверь такой - Сокет?
Представим себе работу ресторана быстрого питания, пусть будет Burger Queen, так вот работник этого заведения и будет сокетом, то есть программой, которая отвечает за обмен данными(бургерами) между клиентом и заведением. Как сокет узнает кому отдать заветное хрючево(данные)? У него есть чек с номером заказа! Вот и у сокета есть порт к которому он привязан, то есть и в Burger Queen и в сетевых технологиях есть приложение, тот самый сокет, который отвечает за работу с определенным портом, так же как и работник, который отвечает за обработку конкретного номера заказа, ведь и к конкретному компьютеру и к конкретной забегаловке одновременно конектятся разные клиенты, и всем им нужны разные данные.
Как и от работников сети быстрого питания от сокетов большого набора скиллов не требуется, поэтому мы создадим отдельно серверный сокет, он будет принимать данные от одного клиента и передавать другому и клиентский сокет, он будет отправлять данные на сервер и получать ответ.
Фуууух, самая потная часть статьи позади, расчехляем питонов! Первым делом напишем программу серверного сокета, работника Burger Queen, который принимает заказы.
import socket # Подключаем необходимую библиотеку, она встроена
new_socket = socket.socket() #Создаём объект сокета
new_socket.bind(('127.0.0.1', 5000)) # Привязываем наш объект к ip и порту
new_socket.listen(2) # Указываем нашему сокету, что он будет слушать 2 других
print("Server is up now!")
Каюсь, в объяснении сокетов я ни слова не сказал про IP, но тут тоже всё просто, наш работник(сокет) работает в конкретном ресторане по конкретному адресу, то есть, подытоживая можно сказать, в конкретном ресторане по определенному IP адресу работает много работников, сокетов, каждый из которых обслуживает свой порт, номер заказа.
Почему я выбрал 5000-ный порт? Методом научного тыка, доступные порты на вашем устройтве могут быть определены с помощью специальных утилит, а IP 127.0.0.1 - стандартный локальный адрес любого компьютера(совсем любого).
Ползём дальше, получаем коннекты.
conn1, add1 = new_socket.accept()
# сохраняем объект сокета нашего клиента и его адрес
print("First client is connected!")
conn2, add2 = new_socket.accept()
#аналогично со вторым клиентом
print("Second client is connected!")
Далее создадим две функции, которые принимают данные от одного клиента и отправляют их другому.
def acceptor1():
# Запустим бесконечный цикл, мы хотим общаться постоянно!
while True:
#Получим 1024 байта от первого клиента
a = conn1.recv(1024)
#Перешлём их второму
conn2.send(a)
def acceptor2():
# А здесь мы получим 1024 байта от второго клиента и перешлём первому.
while True:
b = conn2.recv(1024)
conn1.send(b)
Вуаля, осталось запустить функции, но вот проблема, они будут выполнятся последовательно(вспомним физику 8-го класса), то есть друг за другом, а нам хотелось бы, что они выполянлись параллельно, для этого в программировании существует концепция так называемых потоков, тут я снова встал на скользкую дорожку, потому что про потоки можно говорить часами и всё равно будут оставаться вопросы(лично у меня так бывает), поэтому здесь, памятуя, что статья для новичков, скажу лишь одно, поток - волшебный класс, который позволяет выполянять указанные функции параллельно.
from threading import Thread # Подключили класс потока
#Создаём потоки, в качестве именнованного аргумента передаем нашу ф-ю
tread1 = Thread(target=acceptor1)
tread2 = Thread(target=acceptor2)
#Запускаем потоки
tread1.start()
tread2.start()
Здесь вся магия заключается в том, что мы указываем каждому потоку, какую функцию он будет выполнять, а затем стартуем их и отныне наши функции выполянются параллельно.
Вот собственно и все, можно запустить наш сервер и попробовать подключиться к нему из командной строки, с помощью следующей команды:
curl 127.0.0.1:5000
Здесь мы просто говорим системной утилите curl законектиться к указанному адресу и порту, при успешном подключении сервер сообшит нам о нём в консоли.
Итак, приведём полный код сервера.
import socket
from threading import Thread
new_socket = socket.socket()
new_socket.bind(('127.0.0.1', 5000))
new_socket.listen(2)
print("Server is up now!")
conn1, add1 = new_socket.accept()
print("First client is connected!")
conn2, add2 = new_socket.accept()
print("Second client is connected!")
def acceptor1():
while True:
a = conn1.recv(1024)
conn2.send(a)
def acceptor2():
while True:
b = conn2.recv(1024)
conn1.send(b)
tread1 = Thread(target=acceptor1)
tread2 = Thread(target=acceptor2)
tread1.start()
tread2.start()
С клиентом, а точнее с клиентами всё куда проще, идеалогически мы всего лишь создаём новый объект сокета и заставляем его отправлять и принимать данные, делает он это конечно же параллельно, смотрим сразу весь код и вникаем.
#Подключаем зависимости
import socket
from threading import Thread
#Создаём новый сокет
client_socket = socket.socket()
#Заставляем его подключиться к серверному сокету
client_socket.connect(("127.0.0.1", 5000))
#Создаём ф-и отправки и получения сообщений
def sender():
while True:
#Читаем строку с клавиатуры
a = input()
#Отправляем её, предварительно закодировав
client_socket.send(a.encode("utf-8"))
def reciver():
while True:
#Получаем строку от сервера
b = client_socket.recv(1024)
#Печатаем, предварительно раскодировав
print(b.decode("utf-8"))
#Создаём по отдельному потоку для каждой функции
tread1 = Thread(target=sender)
tread2 = Thread(target=reciver)
#Потоки запушены, клиент готов получать и отправлять сообщения
tread1.start()
tread2.start()
Вот и всё! Создаем двух клиентов и переписываемся без мам пап, ватсапов и телеграмов, как говорится, мы и сами с усами.
Буду рад любым комментариям, новичкам подскажу, опытных разработчиков выслушаю и приму к сведению.