Танчики в консоли, статья четвёртая: «Новый сервер — новый протокол»

    Добрый день, дорогие читатели. Мы были на WorldSkills (чему я посвящу отдельную статью) в связи с чем эта публикация долго не писалась, так же как и не обновлялся сервер.

    Сегодня я расскажу об неожиданной идеи и небольшом анализе сетевых протоколов. Подробности под катом.

    Так вот, начнём с протокола TCP/IP. Чем хорош этот протокол: защищённое соединение, обеспечивающее целостность в доставке пакетов, надёжность и безопасность. Я тоже долгое время так думала, пока не произошло страшное… на определённом этапе работы у меня отвалилась виртуалка (т.е. клиент) и сервер зациклился сам на себе. Он просто отправлял себе пакеты на один порт, принимал что-то другим, складывал пакет+пакет (т.е. буквально, добавлял в конец первого данные начала второго, а иногда и все данные второго) и отсылал себе на порт приёма, потом принимал, потом отсылал себе снова и это очень ело ресурсы (от слова 100%).

    Я решив найти ответы в интернете, ответы в конечном счёте не нашла и разочаровавшись в этом протоколе стала просить друга помощи в создании нового протокола, на базе UDP.

    Часть 1: «Изменения на сервере»


    Перед тем как перейдём к UDP протоколу, я хочу рассказать об изменениях на сервере:

    1. Было реализовано нормальное разпоточивание клиентов (что не удавалось ранее).
    2. Добавлена более адекватная (на мой взгляд) модель взаимодействия с клиентами.

    Сразу перекинусь на второй пункт, так как реализовав его я смогла добиться реализации первого. Раньше я по глупости своей создавала поток, который создаёт поток, который создаёт потоки и отправляет всем сообщения… это было не правильно в корне. На данный момент всё работает по этой схеме:

    image

    Пожалуй это самая адекватная модель взаимодействия, которая была вытащена из моей головы.
    И при приёме, и при отправке соединение не рвётся, а копируется и отправляется.

    Взаимодействие происходит по принципу: услышал-отправил. Не происходит не какой конвертации или проверки данных, но периодически происходит проверка на жизнеспособность клиента (чтобы нам не уходить в вечный цикл). Это происходит по задаваемому параметру — количество выполненных чтений (сколько раз мы слышали от клиента сообщения).

    Если он достигает нужной отметки, то на клиента отправляется сообщение спрашивающее жив ли он

        match item.stream.write(b"you") { 
        Ok(ok) => {}, Err(e) => {/* Выходим из приёма */ break;}
        , };
    

    И если мы принимаем от него ненулевое сообщение, то заканчиваем проверку и слушаем как и раньше

        let mut buf_q:[u8; 256] = [0; 256]; 
        let mut buf_q_else: [u8; 128] = [0; 128];
    	
        let mut recv_val = false;
            let mut b_false_true = false;
    	loop {
    		    item.stream.read(&mut buf_q);
    		    if buf_q.starts_with(&buf_q_else) { 
    				recv_val = recv_.recv().unwrap(); 
    				if recv_val == true { 
    					      b_false_true = true; break;  } }						
    					}
    			if b_false_true == true { break;} /*  это всё в ещё одном loop цикле, который и заставляет поток не завершаться и слушать клиента дальше */
    

    Слушаем клиентов

        item.stream.read(&mut buf); println!("Принимаем сообщения [{:?}]", item.stream);
        if buf.starts_with(&q) == false { sender_clone.send(buf).unwrap(); }
    /* где q - пустой байтовский массив (проверка нужна для того чтобы не передавать пустые сообщения всем клиентам) */
    

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

    Часть 2: «Краткая теория сетевых протоколов»


    Я не буду разбирать дебри системы OSI, а скажу следующее — есть два вида транспортных сетевых протоколов (на которых строятся все остальные):

    1й. TCP/IP
    2й. UDP/IP

    Что первый, что и второй строятся на протоколе IP (поэтому и появляется '/IP').
    Обсудим плюсы и минусы обоих

    TCP/IP


    + поочерёдная отправка пакетов
    + базовое шифрование (методом случайного числа)
    + повторный запрос в случае искажения пакета
    +- малые порции отправки

    — медленная скорость
    — тройное рукопожатие при коннекте и двойное подтверждение доставки
    — много других не видных на первый взгляд ошибок (с одной из них я и столкнулась)

    UDP/IP


    + скорость
    +- независимость (плевать дошёл ли пакет)
    +- пакет не делится на маленькие порции
    + отсутствует рукопожатие в принципе
    +- нету ошибок (нету их определения, пакет просто извергается извне и летит… куда глаза его байтные глядят)

    — нет очерёдности
    — возможны искажения (т.к. нету проверки)
    — нет шифрования

    Есть так же и множество базирующихся на этих протоколах других протоколов, но мне была важна скорость работы, а не удобство восприятия/дополнительные функции.

    Глава 3: «Строим модель и подводим итоги»


    Начнём с начала: что нам надо?

    1. Стабильное соединение с клиентом (TCP)
    2. Высокая скорость передачи данных (UDP)
    3. Высокая скорость первоначального соединения (UDP)
    4. Понимание как это работает
    5. Не должно быть искажений и должна быть очерёдность (TCP)

    Два TCP и два UDP свойства. Разве стоила одна ошибка в TCP (на данный момент исправленная в проекте) создание подобия TCP но со своими заморочками? Мой ответ — да!

    Почему да? Полазав на форумах я поняла что TCP это случай из UDP, и в TCP есть ещё масса ошибок, которые в UDP отсутствуют (по причине того что UDP отправляет и забывает).

    Структура данных пакета

    image

    Схема работы сервера на будущем протоколе на этом изображении:

    image

    Тем самым мы получаем простую схему работы, клиент подключается — мы передаём его в общий таймаут и при каждой отправки сообщения ждём подтверждения приёма у клиента, при этом работая с остальными клиентами.

    Минусами этого решения является отсутствие шифрования (что мы доделаем в клиенте) и непредсказуемое поведение выражающееся в отсутствие выдачи ошибок на экран.

    Теоретически получился тот же велосипед, что и на TCP, только без тройного рукопожатия и без деления пакетов на маленькие порции, что я считаю победой.

    Увидеть это вы сможете совсем скоро в моём гитхабе.

    Спасибо за прочтение и всего вам наилучшего!
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 6
    • +1
      Интересно, как это клиент вместо сервера стал принимать свои же пакеты? Слетела исходящая маршрутизация, в результате чего маршрут по умолчанию оказался через lo?
      • +1
        К сожалению я потеряла те скриншоты и не смогу показать их… постараюсь восстановить старую версию сервера из гитхаба и выложить сюда ссылку. Как я поняла такая ошибка не единична (как и на форумах об этом говорили, так и наши учителя/сис админ), и так как я не понимаю с чем она связанна и почему при падении клиента не рвалось соединение, а получилось зацикливание — пришлось делать новый протокол
        • 0
          Возможно вы простое не умеете готовить TCP/IP. Не являюсь специалистом, но некоторые утверждения касательно TCP вызывают сильные сомнения, например «шифрование» и «двойное подтверждение».
          Что бы при падении клиента рвалось соединение, необходимо вручную отслеживать таймауты и сетевую активность, в ином случае сервер и клиент никогда не узнают, что соединение развалилось.
      • 0
        В гугле для решения этих проблем придумали QUIC

        А в остальном да, много ошибок: никакого шифрования в TCP нет; в UDP тоже есть контрольные суммы для проверки целостности, просто нет автоматического ретрансмита битых пакетов. Двойное подтверждение — что имелось в виду непонятно. На TCP-пакет в ответ приходит один ACK.
        • –2
          И сервер отправляет сообщение что этот аск пришёл
          • +1
            Не отправляет. Three-Way-Handshake идет только при установлении соединения. Изучайте протокол TCP.

            Более того, ACK приходит не на каждый пакет, а на окно (некое количество неподтвержденных пакетов), которое меняется динамически в зависимости от количества потерь в канале. За это отвечают congestion-алгоритмы.

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

        Самое читаемое