Приветик. В данной статье не будет никакой занудной теории по типу "А чТо ТаКоЕ СЕРВЕР???". Мы будем вкратце описывать работу клиент-сервер, а также приведём примеры. Данная статья будет интересна тем, кто не до конца понимает как работает клиент-сервер, кто забивает в череп гвозди, чтобы повысить содержание железа и стать умнее, кто думает что клиент-сервер это что-то заоблачное для обычного начинающего кодера.

Итак, приступим к описанию принципов работы:

  • Сервер, на IP адресе {пусть это будет alpha} по порту {пусть это будет beta} ожидает подключения на указанный IP адрес {пусть это alpha2} с портом {пусть это beta2}. IP машины и IP приёма должны быть одинаковы. Порты имеют значения в диапазоне 0-65535.

  • Далее, клиент хочет зайти на ваш сервер. Изначально, наш сервер это небольшая консоль для доступа .... например к дневнику Иванова Ивана и 5Б. Клиент вводит IP {alpha2} и порт {beta2}, после чего по TCP или UPD происходит обмен данными.

  • Сервер видит что кто-то хочет посмотреть нюдсы Иванова Ивана и просто отсылает их обратно клиенту.

- Вот так мы максимально кратко расписали клиент-сервер. Распишем немного побольше...
- ЭЙ СТОЙ! А чо ещё за TCP и UPD?
- Секундочку.

Что такое UPD и TCP?
Вкратце это технологии передачи данных между двумя устройствами. Они оба разные как лолихантер и милфхантер. Приведём парочку примеров:

- Эй, Санёк, я тут камни нашёл. Можно я в тебя его кину?
- Хорошо, Шанёк, кидай
- Разрешение кидать получено!
*Кинул камни настолько мягко и последовательно, что Санёк успел словить все*

Это был пример работы TCP. Он превосходит UPD в целостности данных, строго последовательным отправлением данных и большей надёжности, но в отличии от него меньшей скоростью.

- Эй, Санёк, лови!
*Кинул камни так сильно, что Санёк сразу дал дёру, успев сначала словить большую часть камней*
- *****, не поймал, в лицо попал

Это был пример работы UPD. В отличии от своего "прилежного" брата он более быстрый в закидывании камня. Но вместо строгой последовательности отправки данных, кидает всё что видит.

Теперь черпанём немного практики.

Для начала сделаем вечно получающий информацию сервер.
Для передачи информации через сокеты в Python используем socket

import socket

Теперь же нам надо сделать слушалку.

listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
IP = socket.gethostbyname(socket.gethostname())
PORT = 12333
listener.bind((IP, PORT))
listener.listen(0)

connection, address = listener.accept()

socket.socket() - создаём наш сокет. Тут мы просто создаём коробку определённого вида, цвета, размера (повторяю, это формально). Но без содержимого.

socket.setsockopt() - добавляем в нашу коробку пенопласт для информации. Настраиваем опции коробки: какой пенопласт, размеры и т.д.

IP = socket.gethostbyname(socket.gethostname()) - получаем наш IP. В функцию передаётся имя ПК, IP которого мы хотим получить, в нашем случае нас.

listener.bind((IP, PORT)) - устанавливаем данные для прослушивания данного порта. Берём почтовый ящик IP/PORT. Данные передаются кортежем, это не ошибка!

listener.listen() - разрешаем серверу принимать запросы.

listener.accept() - в случае найденного пользователя разрешаем ему подключиться

Теперь у нас есть почтовый ящик и заранее готовая коробка с посылкой. Теперь нам нужно проверять наш почтовый ящик. Наш почтовый ящик вмещает 1 КБ (1024 байт). Поэтому нам нужно каждый раз открывать ящик, забирать оттуда данные и продолжать до тех пор, пока ящик не опустеет.

connection.send("Привет, подкючайся!".encode('utf8'))

connection.send("Привет!".encode('utf8'))
while True:
	data_output = ''
	while True:
		data = connection.recv(1024).decode("utf8")
		data_output+=data
		if not data:
			break

	print(data_output)

connection.send() - отправить данные. Для вашей же живучести используйте .encode("utf8")

connection.recv(1024) - получить 1024 байт данных. Для вашей же живучести используйте .decode("utf8")

Вот так мы будем получать информацию о том, что нам отправили. Это как будто вы перевернули почтовый ящик вверх дном и высыпаете оттуда всё до конца.

- Но зачем в начале нам отправлять данные?
Таким образом мы показываем клиенту что готовы работать. Если бы мы этого не сделали, клиент бы стоял молчал.

Раз уж с сервером окончено перейдём к клиенту

import socket

connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
IP = "айпи сервера"
PORT = 12333
connection.connect((IP, PORT))
rd = connection.recv(1024)
print(rd.decode('utf8'))
connection.send("И тебе привет!".encode('utf8'))
connection.close()

connection.connect() - начать подключение. Данные в функцию передаются кортежем!

connection.close() - закрыть соединение

Ну вот, теперь при запуске клиента по IP, от сервера мы получим "Привет", а сервер получит наше "И тебе привет!", а также продолжит ждать от нас ответ.
Прошу акцентировать внимание, что эти каналы легко прослушать, а по сему для передачи личных данных желательно пользоваться алгоритмами шифрования (например идеально подойдёт RSA).

Немного дополнительных вопросов:

  • Можно ли при помощи сокетов сделать консоль? Как реализовать в нём команды?

  • Конечно. Команды можно реализовать просто отправкой сообщения, а после получения обработкой через условия.

  • Как сделать таймаут?

  • Используйте на вашем подключении .settimeout(Время в секундах).

  • Что если я укажу чужой адрес в прослушивании сервера?

  • Вам просто выбьет ошибку.

  • Можно ли по сокетам передавать фото, видео и т.д.?

  • Да, конечно. Фото и видео это просто данные. Их можно прочесть, а соответственно и отправить.

Небольшая справочка по основным командам для создания сокетов:

socket.socket(socket.AF_INET, socket.SOCK_STREAM) - создать сокет
socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - настроить сокет
socket.gethostbyname(socket.gethostname()) - получить наш готовый IP
listener.bind((IP, PORT)) - прослушивать данный IP по порту. Данные передаются кортежем!
listener.listen(0) - разрешаем серверу принимать запросы
listener.accept() - принимаем запрос на передачу данных
connection.send() - отправить данные
connection.recv(1024) - получить данные. Значение в скобочках, количество байт которые можно принять за один раз
connection.settimeout() - установить таймаут соединения
connection.connect() - установить подключение к серверу
connection.close() - закрыть соединение

Дополнительные источники, полезные ссылки:

И на этом мы закончим вступление клиент-сервер в Python для зародышей. Напомню что данная статья не создана для Truehard кодеров, она создана для тех кто не разбирается в сокетах и хочет понять как с ними работать.

Комментарии (2)


  1. andreymal
    29.09.2021 19:18
    +6

    Боюсь, что некорректные аналогии, необъяснённые функции и ошибки в используемых терминах лишь ещё сильнее всех запутают.


  1. gonol
    02.11.2021 20:24

    На мой взгляд, материал полезный, особенно для первого знакомства с сокетами. Но использование русского языка и правил пунктуации на низком уровне. Рекомендую вычитывать статью перед публикацией. Уважайте читателей!

    Спасибо за понимание.