python

Сокеты в Python, чат в 50 строк

  • среда, 22 декабря 2021 г. в 00:49:00
https://habr.com/ru/post/596985/
  • Python
  • Учебный процесс в IT


На очередной практике по 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()

Вот и всё! Создаем двух клиентов и переписываемся без мам пап, ватсапов и телеграмов, как говорится, мы и сами с усами.

P. S.

Буду рад любым комментариям, новичкам подскажу, опытных разработчиков выслушаю и приму к сведению.