Народ, это моя первая статья, так что задолбите меня критикой, дабы повысить качество следующих статей. :)

Вступление

Ну, начнем как и везде с определений, берите тетрадь и ручку сейчас начнется нудятина. Чтобы мы cмогли написать свой сервер, нужно для начала понимать как он вообще работает, ловите определение:

Сервер – это программное обеспечение, которое ожидает запросов клиентов и обслуживает или обрабатывает их соответственно.

Если объяснять это своими словами, представьте фургон с хот-догами(сервер), проголодавшись, вы(клиент) подходите и говорите повару, что вы хотите заказать(запрос), после чего повар обрабатывает, что вы ему сказали и начинает готовить, в конечном итоге вы получаете свой хот-дог(результат) и сытый радуетесь жизни. Для наглядности посмотри схему.


Околопрактика

Для написания сервера мы будем использовать Python и модуль Socket.

Socket позволяет нам общаться с сервером с помощью сокетов. Весь код я постараюсь пояснять, дабы ты мой дорогой читатель все понял. В конце статьи будет готовый код.

Создайте два файла в одной директории:

  1. socket_server.py

  2. socket_client.py


Практика

Пишем код для серверной части, так что открывайте файл socket_server.py.

Начнем с импорта модуля и создания TCP-сокета:

import socket

Далее весь код будет с комментариями:

s.bind(('localhost', 3030)) # Привязываем серверный сокет к localhost и 3030 порту.
s.listen(1) # Начинаем прослушивать входящие соединения
conn, addr = s.accept() # Метод который принимает входящее соединение.

Добавим вечный цикл, который будет считывать данные с клиентской части, и отправлять их обратно.

while True: # Создаем вечный цикл.
	data = conn.recv(1024) # Получаем данные из сокета.
	if not data:
		break
	conn.sendall(data) # Отправляем данные в сокет.
	print(data.decode('utf-8')) # Выводим информацию на печать.
conn.close()

Переходим к клиентской части, весь код теперь пишем в файле socket_client.py.

Начало у клиентской части такое-же как и у серверной.

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

Далее подключимся к нашему серверу и отправим сообщение "Hello. Habr!".

s.connect(('localhost', 3030)) # Подключаемся к нашему серверу.
s.sendall('Hello, Habr!'.encode('utf-8')) # Отправляем фразу.
data = s.recv(1024) #Получаем данные из сокета.
s.close()

Результат:

Слева сервер, справа клиент
Слева сервер, справа клиент

Заключение

Вот мы с вами и написали свой первый сервер, рад был стараться для вас, ниже будет готовый код.

socket_server.py:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.bind(('localhost', 3030)) # Привязываем серверный сокет к localhost и 3030 порту.
s.listen(1) # Начинаем прослушивать входящие соединения.
conn, addr = s.accept() # Метод который принимает входящее соединение.

while True:
	data = conn.recv(1024) # Получаем данные из сокета.
	if not data:
		break
	conn.sendall(data) # Отправляем данные в сокет.
	print(data.decode('utf-8'))
conn.close()

socket_client.py:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(('localhost', 3030)) # Подключаемся к нашему серверу.
s.sendall('Hello, Habr!'.encode('utf-8')) # Отправляем фразу.
data = s.recv(1024) #Получаем данные из сокета.
s.close()

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


  1. 16tomatotonns
    19.05.2022 01:25
    +17

    Очень просто. Я бы сказал что чересчур :)
    Во-первых, используемый вами протокол TCP безусловно хорош, но сервер который принимает только одно соединение, извлекает из него всё что в него отправили и тут же закрывается это немножко малоюзабельно. Что-то на уровне
    > Как в языке Python сделать Hello World? Всего лишь print("Hello World")! Ничего другого не надо!
    То есть это прямо скажем не сервер, а "Как прослушать порт, и принять из него что-нибудь", притом его легко полностью парализовать не завершая соединение :)

    Первое что имеет любой сервер:
    - Некоторый менеджер соединений. В него мы добавляем клиентов.
    - Некоторые абстракции над sock'ами, условные клиенты, которыми мы такие, желательно в неблокирующем виде, принимаем все присланные данные всеми sock'ами (сколько прислали столько неблокирующе и считываем, и если сообщение не закончено то переходим к следующему), отключаем клиентов по таймаутам, проверяем что они нам что-то прислали и выполняем на присланные сообщения какие-нибудь callback'и.
    - Некоторый абстрактный формат сообщений. Протокол TCP ориентирован не на сообщения, а на потоки, и ему нужен дополнительный формат для разделения сообщений, вы же не хотите использовать одно целое соединение чтобы прислать одно сообщение, когда их может быть пачка? Простейший формат сообщений -- message + "\r\n", и эти "\r\n" работают разделителями сообщений. Но у него проблема с самими данными, их приходится паковать в какой-нибудь base64 чтобы из-за тела сообщения содержащего эти самые "\r\n" мессага не распилилась, поэтому чуть более сложный вариант, например
    text = "abcdef"
    formatted = '{0:04d}{1:s}'.format(len(text), text)
    > '0006abcdef'
    Приведёт к уникальной возможности указывать длину сообщения перед самим сообщением, и корректно их сепарировать по длине, принимая и отправляя их сразу пачками (хотя появляется лимит на длину, которую можно указать произвольной, но она внесёт некоторый оверхед). Я сам использую похожие прикладные протоколы для своих небольших нужд, где HTTP слишком тяжеловесен, и что-то похожее используется в самом HTTP, только там чуть более комплексный заголовок, содержащий не только длину а сразу множество опций, и само тело там может быть а может не быть, но это уже частности.

    И вот чего-то подобного хотелось бы видеть в статье а не только "Мы открыли сокет! Мы отправили в него строчку и приняли! Ура!". Статья неплоха как вводная часть, перед дальнейшей кухней вроде той что я описал, но ПОСЛЕ теоретической части которой тоже не хватает: что такое сервер? что такое сокет? что такое порт? что такое tcp/udp/ip? Почему localhost и что он означает, во что трансформируется и почему? Ваше описание избыточно упрощено, и мало имеет отношения к действительности :)

    Ну и в третьих, хаб "Серверное администрирование" немножко кажется тоже лишним, здесь примерно ничего про администрирование, про роутинг, пробитие или использование портов, фаерволы и так далее :)


    1. Pinguin
      19.05.2022 02:08
      +2

      Присоединяюсь насчёт сокета. Всё делается через сокет, а что такое сокет -- непонятно.


      1. artemlight
        19.05.2022 09:42

        я там чуть ниже ответил на Ваш вопрос :)


    1. v2ray
      19.05.2022 07:42
      -3

      da nedavnyaya statya pro kolichestvo soedinenii na server byla dostatichno information chego tam proishodit + statya pro server-eto-script = chtoby obyasnit chto povar sdelal s sobakoi da bez vsyakih grishkovec


    1. XenixFSociety Автор
      19.05.2022 08:08
      +2

      Спасибо большое за критику, в дальнейшем буду развивать эту ветку статей.


    1. Kreastr
      19.05.2022 12:05

      Получить подключение, прочитать запрос и отправить ответ это и есть принцип работы, наверное, самого распространенного типа серверов - веб серверов.

      В текущем сценарии менеджер соединений не обязателен, потому что нет параллельной работы с соединениями. А на уровне получить запрос от клиента и подождать пока сервер примет соединение оно реализовано на уровне ОС.

      Вообще КМК архитектура веб-серверов плавно ползет именно к таким супер простым серверам запущенным в нескольких копиях с балансировкой нагрузки через reverse proxy.

      Но теории добавить стоит, конечно. И дампы траффика разобрать.


  1. unstopppable
    19.05.2022 07:42
    +1

    Предлагаю усложнять сервер в следующих статьях шаг за шагом. В чем косяк этого сервера - если подключиться еще одним клиентом, то он будет получать от сервера ответ только когда первый отключится... Соответственно, нужно переключать сервер между клиентами, например, используя опрос сокетов через select. Высший пилотаж - aiohttp:)


    1. XenixFSociety Автор
      19.05.2022 08:10

      В планах продолжать данный цикл статей. Спасибо за критику в следующих статьях думаю реализую)


  1. artemlight
    19.05.2022 09:18

    Как два байта переслать! (с)

    Ошибка статьи в том, что вы берете слишком низкоуровневый примитив и ожидаете, что поверх него пользователь будет строить какой-то велосипед для передачи данных. В реальности уже давно написаны практически всевозможные обертки над голыми сокетами, и крайне маловероятно что кому-то понадобится ещё одна.

    А ведь сервер - это не просто socket.recv(). Это дофига всего - ААА, сериализация, валидация, и много-чего-другого-ция. Да и даже socket.recv() уже мало кому интересен - слишком медленно и тяжко с асинхронностью.

    TL;DR: статья называется "как построить простой дом", но начинаем с добычи глины для лепки кирпичей.


  1. artemlight
    19.05.2022 09:41
    +2

    Ну и тут выше был вопрос про сокет - поэтому отвечу вкратце.

    Сокет в переводе с английского языка - разъем или розетка, проще говоря - дырка в которую можно вставить кабель. Как и в случае с обычной розеткой в стене - сама по себе она мало чем ценна, в неё нужно что-то включить. Отсюда возникает разделение на серверные (слушающие, Listening) сокеты и клиентские сокеты. И если серверный сокет - это условная розетка в стене, то клиентский сокет - это пальцы, которые мы в неё вставляем.

    Фактически серверный сокет - это такой механизм в ОС, который ждет входящего подключения. И как только к нему подключаются - сразу вызывает какой-то Ваш код, который обрабатывает это подключение. Ну то есть нормальное состояние серверного сокета - ничего не делать и ждать, он потому и называется слушающим.

    Клиентский же сокет - это механизм в ОС, который подключается к серверному сокету, и если подключение произошло удачно - то можно писать в сокет данные и читать из сокета данные.

    Технически эта вся история реализована при помощи семейства интернет-протоколов TCP\IP. IP - это про адресацию (те самые IP-адреса, да), бывает версии 4 и 6. TCP - это про надежность доставки (там есть два варианта - TCP медленнее и с гарантией и UDP быстро, но как повезет). На уровне TCP сделано ещё много чего полезного - например Congestion Control, которые определяют честность разделения лимитированного по ширине канала между несколькими одновременными соединениями.

    Чтобы не запихивать весь этот функционал в какую-то одну огромную библиотеку - умные люди придумали модель OSI. Это такая референсная и недостижимая (как социализм) штука, идея которой заключается в том, что сетевое взаимодействие между двумя системами делится на абстрактные "уровни", каждый из которых полностью реализует свою задачу. Условно говоря - протоколу TCP на уровне L3 по идее не важно, что у него за физический носитель на уровне L1 - медный кабель, оптоволокно или Wi-Fi. (на самом деле это совсем не так, но для 70-х годов прошлого века идея тотальной унификации всего на свете выглядела прогрессивной). Много полезной инфы об этом можно почерпнуть из старого курса Cisco ICND, и несмотря на его почтенный возраст он имхо обязателен к прочтению всем, кто пытается что-то соорудить из сокетов.


    1. 16tomatotonns
      19.05.2022 15:56
      +2

      Ответ хороший, но суть в том, что по хорошему он должен быть в теле статьи :)


  1. ivymike
    21.05.2022 00:07

    Почему все прицепились к какому-то сокету...

    Изначально проведена не самая корректная аналогия: фургон с булками - это БД, сервер - повар.


  1. souls_arch
    21.05.2022 04:58

    Не очень удачная абстракция с фургоном и поваром. Имхо, если любите покушать, то - ресторан.