*на самом деле мы напишем только прототип протокола.

Возможно, вы встречались с подобной ситуацией – сидите в любимом мессенджере, переписываетесь с друзьями, заходите в лифт/тоннель/вагон, и интернет вроде ещё ловит, но отправить ничего не получается? Или иногда ваш провайдер связи неправильно конфигурирует сеть и 50% пакетов пропадает, и тоже ничего не работает. Возможно, вы думали в этот момент — ну ведь можно же наверное как-то сделать, чтобы при плохой связи всё равно можно было отправить тот маленький кусочек текста, который вы хотите? Вы не одни.

Источник картинки

В этой статье я расскажу про свою идею для реализации протокола на основе UDP, который может помочь в этой ситуации.

Проблемы TCP/IP


Когда у нас плохое (мобильное) соединение, то начинает теряться большой процент пакетов (или ходить с очень большой задержкой), и протокол TCP/IP может воспринимать это как сигнал о том, что сеть перегружена, и всё начинает работать оооочень медленно, если работает вообще. Не добавляет радости тот факт, что установление соединения (особенно TLS) требует отправки и приема нескольких пакетов, и даже небольшие потери сказываются на его работе очень плохо. Также часто требуется обращение к DNS перед тем, как установить соединение — ещё пара лишних пакетов.

Итого, проблемы типичного REST API, основанного на TCP/IP при плохом соединении:

  • Плохая реакция на потери пакетов (резкое уменьшение скорости, большие таймауты)
  • Установление соединения требует обмена пакетами (+3 пакета)
  • Часто нужен «лишний» DNS-запрос, чтобы узнать IP сервера (+2 пакета)
  • Часто нужен TLS (+2 пакета минимум)

Суммарно это означает, что только для соединения с сервером нам нужно послать 3-7 пакетов, и при высоком проценте потерь соединение может занять существенное количество времени, а мы ещё даже ничего не отправили.

Идея реализации


Идея состоит в следующем: нам требуется всего-лишь отправить один UDP-пакет на заранее зашитый IP-адрес сервера с необходимыми данными авторизации и с текстом сообщения, и получить на него ответ. Все данные можно дополнительно зашифровать (этого в прототипе нет). Если ответ в течение секунды не пришел, то считаем, что запрос потерялся и пробуем отправить его заново. Сервер должен уметь убирать дубли сообщений, поэтому повторная отправка не должна создать проблем.

Возможные подводные камни для production-ready реализации


Ниже перечислены (далеко не все) вещи, которые нужно продумать перед тем, как использовать что-либо подобное в «боевых» условиях:

  1. UDP может «резаться» провайдером — нужно уметь работать и по TCP/IP
  2. UDP плохо дружит с NAT — обычно есть мало (~30 сек) времени, чтобы ответить клиенту на его запрос
  3. Сервер должен быть устойчив к атакам усиления — нужно гарантировать, что пакет с ответом будет не больше пакета с запросом
  4. Шифрование — это сложно, и если вы не эксперт по безопасности, у вас мало шансов реализовать его корректно
  5. Если выставить интервал перепосылок неправильно (например, вместо того, чтобы пробовать заново раз в секунду, пробовать заново без остановки), то можно сделать намного хуже, чем TCP/IP
  6. На ваш сервер может начать приходить больше трафика из-за отсутствия обратной связи в UDP и бесконечных повторных попыток отправки
  7. IP-адресов у сервера может быть несколько, и они могут меняться со временем, поэтому кеш нужно уметь обновлять (у Telegram хорошо получается :))

Реализация


Напишем сервер, который будет отдавать ответ по UDP и присылать в ответе номер запроса, который к нему пришел (запрос выглядит как «request-ts текст сообщения»), а также timestamp получения ответа:

// Это Go.
// Обработка ошибок убрана для краткости
buf := make([]byte, maxUDPPacketSize)

// Начинаем слушать UDP
addr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", serverPort))
conn, _ := net.ListenUDP("udp", addr)

for {
	// Читаем из UDP, нам обязательно нужен обратный адрес
	n, uaddr, _ := conn.ReadFromUDP(buf)
	req := string(buf[0:n])
	parts := strings.SplitN(req, " ", 2)

	// Высчитываем время на сервере по сравнению с временем клиента
	curTs := time.Now().UnixNano()
	clientTs, _ := strconv.Atoi(parts[0])

	// Тут можно сходить в базу или куда-нибудь ещё и непосредственно сохранить сообщение

	// Отправляем ответ
	conn.WriteToUDP([]byte(fmt.Sprintf("%d %d", curTs, clientTs)), uaddr)
}

Теперь сложная часть — клиент. Мы будем отправлять сообщения по одному и дожидаться ответа сервера перед тем, как послать следующее. Слать будем текущий timestamp и кусок текста — timestamp будет служить идентификатором запроса.

// Создаем сокеты
addr, _ := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverIP, serverPort))
conn, _ := net.DialUDP("udp", nil, addr)

// В UDP запись и чтение будут идти независимо, поэтому используем канал для удобства.
resCh := make(chan udpResult, 10)
go readResponse(conn, resCh)

for i := 0; i < numMessages; i++ {
  requestID := time.Now().UnixNano()
  send(conn, requestID, resCh)
}

Код функций:

func send(conn *net.UDPConn, requestID int64, resCh chan udpResult) {
  for {
    // Отправляем пакет до тех пор, пока не получим ответ на своё сообщение.
    conn.Write([]byte(fmt.Sprintf("%d %s", requestID, testMessageText)))
    if waitReply(requestID, time.After(time.Second), resCh) {
      return
    }
  }
}

// Ждем свой ответ, или таймаут.
// В сети пакеты могут как теряться, так и дублироваться, поэтому нужно
// проверять, что присланный ответ действительно относится к тому сообщению,
// которое мы посылали.
func waitReply(requestID int64, timeout <-chan time.Time, resCh chan udpResult) (ok bool) {
  for {
    select {
      case res := <-resCh:
        if res.requestTs == requestID {
          return true
        }
      case <-timeout:
        return false
    }
  }
}

// Распарсенный ответ сервера
type udpResult struct {
  serverTs  int64
  requestTs int64
}

// Функция для чтения ответа из соединения и засовывания ответа в канал.
func readResp(conn *net.UDPConn, resCh chan udpResult) {
  buf := make([]byte, maxUDPPacketSize)

  for {
    n, _, _ := conn.ReadFromUDP(buf)
    respStr := string(buf[0:n])
    parts := strings.SplitN(respStr, " ", 2)

    var res udpResult
    res.serverTs, _ = strconv.ParseInt(parts[0], 10, 64)
    res.requestTs, _ = strconv.ParseInt(parts[1], 10, 64)

    resCh <- res
  }
}

Также я реализовал то же самое на основе (более-менее) стандартного REST: с помощью HTTP POST посылаем те же requestTs и текст сообщения и дожидаемся ответа, после чего переходим к следующему. Обращение делалось по доменному имени, кеширование DNS в системе не запрещалось. HTTPS не использовался, чтобы сравнение было более честным (в прототипе шифрования нет). Таймаут был выставлен в 15 секунд: в TCP/IP уже есть перепосылки потерянных пакетов, а сильно больше 15 секунд пользователь, скорее всего, ждать не станет.

Тестирование, результаты


При тестировании прототипа измерялись следующие вещи (всё в миллисекундах):

  1. Время ответа на первый запрос (first)
  2. Среднее время ответа (avg)
  3. Максимальное время ответа (max)
  4. H/U — соотношение «время HTTP» / «время UDP» — во сколько раз меньше задержка при использовании UDP

Делалось 100 серий по 10 запросов — симулируем ситуацию, когда нужно послать буквально несколько сообщений и после этого уже становится доступен нормальный интернет (например Wi-Fi в метро, или 3G/LTE на улице).

Протестированные виды связи:


  1. Профиль «Very Bad Network» (10% потерь, 500 мс latency, 1 мбит/сек) в Network Link Conditioner — «Very Bad»
  2. EDGE, телефон в холодильнике («лифте») — fridge
  3. EDGE
  4. 3G
  5. LTE
  6. Wi-Fi

Результаты (время в миллисекундах):




(то же самое в формате CSV)

Выводы


Вот, какие выводы можно сделать из получившихся результатов:

  1. Если не считать аномалию с LTE, то разница при посылке первого сообщения тем больше, чем хуже связь (в среднем в 2-3 раза быстрее)
  2. Последующая отправка сообщений в HTTP не сильно медленней — в среднем в 1,3 раза медленней, а на стабильном Wi-Fi вообще разницы нет
  3. Время ответа на основе UDP намного стабильнее, что косвенно видно по максимальному времени ожидания — оно тоже меньше в 1,4-1,8 раз

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

Реализация прототипа


Прототип выложен на github. Не используйте его в продакшене!

Команда для запуска клиента на телефоне или компьютере:
instant-im -client -num 10
. Сервер пока что запущен :). Нужно смотреть в первую очередь на время первого ответа, а также на максимальную задержку. Все эти данные печатаются в конце.

Пример запуска в лифте


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


  1. VanquisherWinbringer
    03.01.2019 02:53
    -3

    Оу, спасибо. Я ненавижу Go, просто как раз хотел что нибудь про UDP почитать.


    1. gudvinr
      03.01.2019 03:19
      +2

      Часть про то, что вы ненавидите Go, была обязательной, чтобы свои впечатления выразить? То есть, никак не обойтись без того, чтобы хейтить и просто "что-нибудь про UDP" почитать? Раз от статьи не воротит настолько, что решили почитать, значит не так уж и ненавидите, наверное.


      К тому же вы у себя в профиле ставите этот язык как на котором разрабатываете (на втором месте, между прочим), и подписаны на хаб (наверное, чтобы поддерживать себя в тонусе).


      1. Billar
        03.01.2019 09:01

        мсье, вы бомбанули ярче сверх-новой, и что удивительно даже не по поводу своей статьи *удивление*

        Сам же автор поступил немного более мудро, и не посмел обронить даже единого слова (!) подмечу: в отличии от меня с Вами. По итогу решаю обобщить нашу с Вами причастность к ейфорическому вмешательству тролля свыше, ибо его -4 вполне вероятно похоронят и нас, ведь Ваши +2 им на противовес поставлены, И МЕСТНЫЙ(АЯ) МОДЕРАТОР БОГ(ИНЯ) ФЕМИДА НЕ СМОЖЕТ ОСТАНОВИТЬ ЭТО, ведь мы абсолютно резонно ответили друг другу.

        p.s. и что иронично изволить у меня и вовсе слишком мало кармы для голосования. знаете что бы я сделал? поставил обоим жирный минус за трату своего времени на объяснение этой несуразицы Вам
        au revoir, синьоры
        image


    1. ZaEzzz
      03.01.2019 09:25

      Я знаю отличную шутку про UDP, но не факт, что она до вас дойдет.


      1. maximw
        03.01.2019 13:59

        Повторю, что она до вас не дойдет.


        1. farcaller
          04.01.2019 16:54
          +2

          Повторю еще раз с увеличенной задержкой что-ли.


          1. TimsTims
            04.01.2019 18:06
            +3

            Как же я ненавижу UDP! Никогда не знаешь, дошел ли пакет, или нет, приходится повторять отправку.


          1. TimsTims
            04.01.2019 18:06
            +4

            Как же я ненавижу UDP! Никогда не знаешь, дошел ли пакет, или нет, приходится повторять отправку.


        1. ZaEzzz
          04.01.2019 20:49

          Я повторю вам шутку про TCP, если она до вас не дойдет, но не про UDP.


    1. JekaMas
      04.01.2019 16:35

      У вас с этим какие-то проблемы. Серьезно.


  1. BugM
    03.01.2019 04:14
    -2

    В реальном мире у нас есть авторизации, ключи, сессии.
    Вы сделали stateful мессенджер для телефона. А теперь придумайте пяток сценариев где это не будет работать вообще.
    И на закуску парочку сценариев где это приведет к серьезным проблемам. Не отправленное сообщение, которое отображается отправленным, это серьезная проблема.
    Совсем потом можно подумать на тему хранения сессий в WhatsApp. Миллиард активных пользователей. Как вы бы обработали на серверах stateful?

    И сразу все станет понятно.


    1. LeshiyUrban
      03.01.2019 08:10

      Как прототип/идея весьма неплохо. N сессий тут решается простым KV хранилищем с ключом по ID клиента.
      А для синхронизации последнего отправленного сообщения (проблема "отправленного" и не пришедшего сообщения) можно использовать алгоритм из биржевого FIX. Он прост как две копейки и, по моему, отлично ложится на предложенную архитектуру.
      Не претендую на истинность, но я бы в свободное время поразмыслил над этим проектом


      1. BugM
        03.01.2019 13:51

        Это даже на идею не тянет. Не говоря уже о прототипе. Отправка по UDP пакета и ожидание ответа сервера перед отправкой следующего максимум на лабораторку тянет.

        Решается, только масштабируется не очень. Нам нужен как минимум id клиента, id сессии, ключ шифрования и хеш с таймстемпом последнего сообщения. Пара килобайт точно. Если подумать точно еще что-то вылезет что надо хранить. На миллиарде клиентов это терабайты данных.

        Шардирование делать сложно. Пакеты шифрованные. В начале надо терминировать tls. Проблема явно решаемая, но не с налету.

        Вот чувствую зря я про совсем потом на писал. Там и без этого пункта проблем с stateful выше крыши.


        1. youROCK Автор
          03.01.2019 14:09

          Я не обещал в статье, что остальное реализовать легко. Напротив, я большую часть проблем озвучил :).

          По поводу того, что вы говорите:

          1) Нам нужен как минимум id клиента, id сессии, ключ шифрования и хеш с таймстемпом последнего сообщения. Пара килобайт точно

          Давайте посчитаем:
          id клиента — 8 байт точно хватит
          id сессии — ок, 20 байт должно хватить (sha1, например)
          ключ шифрования — его передавать не нужно, достаточно шифровать открытым ключом сервера
          хеш с таймстемпом последнего сообщения — неясно, о каком хеше речь, но ок
          timestamp — 8 байт (как в примере, с наносекундной точностью)
          длина сообщения — 2 байта (больше 64 Кб по UDP в одном пакете в любом случае не послать)
          само сообщение — обычно немного, 100-200 байт, плюс оверхед на шифрование, допустим те же 100 байт

          Итого:
          заголовки: 8+20+8+2 = 38 байт
          содержимое: 200 байт
          издержки на шифрование: 100 байт

          Всё в сумме дает не больше 400 байт на пакет, звучит как не очень много. Максимальный рекомендуемый размер UDP-пакета, чтобы он точно доставился — 576 байт (https://serverfault.com/questions/587625/why-dns-through-udp-has-a-512-bytes-limit), вроде влезаем, даже запас есть.

          2) На миллиарде клиентов это терабайты данных.
          Если у вас миллиард клиентов, то терабайт метаданных вы сможете хранить с легкостью. Вам ещё и тексты сообщений надо хранить :). И, вероятно, видео, фото и аудио тоже.

          3) Шардирование делать сложно.
          Может быть и сложно, но это точно задача решаемая, и решений очень много. Более того, даже в том же ВК поток сообщений не такой большой, как кажется — в пике до нескольких сотен тысяч в секунду. Такое количество можно пошардировать просто по остатку от деления на, скажем, 20 серверов, и этого вам хватит надолго.

          4) Пакеты шифрованные. В начале надо терминировать tls. Проблема явно решаемая, но не с налету.

          Не надо. Открытый ключ шифрования для сообщения можно зашить прямо в клиент. Чтобы слать шифрованный ответ пользователю, надо один раз при регистрации нового устройства запомнить открытый ключ устройства, который он вам при регистрации пришлет (приватный ключ будет храниться на самом устройстве). Регистрацию и прочее «тяжелое» общение можно делать и по обычному HTTPS / TLS.

          5) Вот чувствую зря я про совсем потом на писал. Там и без этого пункта проблем с stateful выше крыши.

          Основная проблема — «чувствую» :). Все эти проблемы вполне решаемы, но для прототипа не важны.


          1. BugM
            04.01.2019 14:31
            +1

            Вы вот так вот сразу отказали от End2End шифрования. В 2019 году. Вы это серьезно?
            Шифруем все в 2 слоя. Первый пакет до сервера, второй само сообщение.

            Сессионный ключ клиента это не паблик ключ сервера. Это именно сессионный ключ. Чтобы в случае компрометации серверного ключа трафик нельзя было расшифровать. Да и использовать один ключ неопределенно долгое время это не очень. Азы же.

            1. Вы считаете размер пакета, а надо считать размер инфы которою нужно хранить на серверах для поддержания сессии.

            2. Всю историю можно хранить на дисках и не париться о скорости. Подгрузка истории это процесс не требующий особой скорости. Клиент готов подождать пока у него загрузится история на новой устройстве. А вот информация о сессиях нужна в памяти. К ней нужен очень быстрый доступ.

            3. Проблема не в сообщениях, а в клиентах. С сообщениями все понятно. таймстемп последнего и хеш от него же.
            При https да. Именно так и делаем и все просто. Вы же хотите кастомный udp протокол. На сервер будет приходить шифрованный массив. Надо найти от него ключ, расшифровать, найти клиента, валидировать что все ок и уже потом передать на обработку.

            4. Сессионные ключи строго обязательны в 2019 году. Я выше написал.


            1. youROCK Автор
              04.01.2019 14:45

              > Вы вот так вот сразу отказали от End2End шифрования. В 2019 году. Вы это серьезно?
              > Шифруем все в 2 слоя. Первый пакет до сервера, второй само сообщение.

              Как именно шифровать и использовать ли End2End, я специально не озвучивал, поскольку я не являюсь экспертом ни в построении мессенджеров, ни в области security :).

              > 1. Вы считаете размер пакета, а надо считать размер инфы которою нужно хранить на серверах для поддержания сессии.

              В любом случае это килобайты, и это очень мало :). Даже если надо хранить, скажем, 10 Кб на пользователя, это всего-лишь 10 Гб памяти на 1 млн пользователей. Сервер с 16 Гб ОЗУ стоит на амазоне 100 долларов в месяц (https://aws.amazon.com/ru/ec2/pricing/on-demand/ — r5.large), и то это пожалуй слишком много :).

              > 3. Проблема не в сообщениях, а в клиентах. С сообщениями все понятно. таймстемп последнего и хеш от него же.
              > При https да. Именно так и делаем и все просто. Вы же хотите кастомный udp протокол. На сервер будет приходить шифрованный массив. Надо найти от него ключ, расшифровать, найти клиента, валидировать что все ок и уже потом передать на обработку.

              Ок, да, нужно сделать несколько запросов в разные сервисы. В чём здесь проблема :)?

              > 4. Сессионные ключи строго обязательны в 2019 году. Я выше написал.

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


              1. BugM
                04.01.2019 17:53
                +1

                Как именно шифровать это не важно в данном случае. Но необходимо понимать общий принцип шифрования. Пакет отдельно, пользовательские данные в нем отдельно. Ключ от пакета сессионный, ключ от пользовательских данных только у получателя.

                Миллион пользователей это мало. Мессенджеры нынче живут на миллиарде пользователей. И любая разработка должна быть готова к работе с таким объемом. Миллиард это 10Тб шаренной и доступной со всех узлов входа ОЗУ только на сессии. Это уже много. Очень много. Да и просто держать столько открытых tls сессий это задача ну очень нетривиальная.

                Проблема что это плохо масштабируется горизонтально.
                Проблема что это плохо будет работать за фаерваллами.
                Это будет плохо работать за двойными натами.
                Это плохо совместимо с проксями.
                Это проблема консистентности статуса клиента и сервера.
                Это в конце концов кастомный низкоуровневый протокол. То есть все типовые балансировщики с ним будут работать плохо или не будут вообще.
                Реверс прокси работать не будет точно.

                И ради чего все это? Чтобы пользователь имел повышенный шанс отправить сообщение при ухудшении сигнала? Оно того не стоит.


                1. youROCK Автор
                  04.01.2019 21:39

                  > Миллиард это 10Тб шаренной и доступной со всех узлов входа ОЗУ только на сессии. Это уже много. Очень много.

                  Учитывая, что 512 Гб на сервер — это вполне стандартная (чуть выше среднего) современная конфигурация, вам нужно всего 20 серверов, чтобы хранить это. Если учесть резервирование, то 40 серверов. Для хранения самых пользовательских данных вам потребуется в сотни раз больше, так что это не проблема.

                  > Реверс прокси работать не будет точно.
                  В целом, в этом нет особой необходимости, весь трафик может принять буквально несколько серверов. К тому же, балансировку на уровне сети никто не отменял.

                  > Это будет плохо работать за двойными натами.
                  Поясните, пожалуйста, почему именно за двойным натом это должно плохо работать.

                  > Проблема что это плохо масштабируется горизонтально.
                  Уж что-что, а UDP прекрасно масштабируется на любое число машин, поскольку нет состояния между запросами.

                  > Проблема что это плохо будет работать за фаерваллами.
                  Возможно.

                  > Это плохо совместимо с проксями.
                  Если речь про корпоративный прозрачный HTTP-прокси, то да. Речь шла про мобильный интернет в первую очередь, там нет никаких проксей обычно.

                  > Это в конце концов кастомный низкоуровневый протокол. То есть все типовые балансировщики с ним будут работать плохо или не будут вообще.
                  Вы говорите про L7 балансировку, но есть и другие виды балансировки, например L3/L4.
                  И я бы не сказал, что этот протокол низкоуровневый :). С каких пор UDP стал протоколом низкого уровня?

                  > И ради чего все это? Чтобы пользователь имел повышенный шанс отправить сообщение при ухудшении сигнала? Оно того не стоит.
                  А по-моему, стоит, если вы разрабатываете мессенджер и это ваш основной бизнес. Люди целые протоколы изобретают или начинают использовать просто ради улучшения качества связи, как например Apple с Multipath TCP для звонков FaceTime или iMessage — благодаря этой технологии они могут работать даже при более плохих условиях связи, и не прерывать соединение при смене сети — это очень большое конкурентное преимущество по сравнению с остальными.


                  1. gudvinr
                    03.01.2019 23:13

                    Учитывая, что 512 Гб на сервер — это вполне стандартная (чуть выше среднего) современная конфигурация, вам нужно всего 20 серверов, чтобы хранить это. Если учесть резервирование, то 40 серверов. Для хранения самых пользовательских данных вам потребуется в сотни раз больше, так что это не проблема.

                    А если учесть, что плотность пользователей сильно зависит от локации и онлайн в разных локациях разнится в разное время суток, то в реальности даже и это — оценка сверху. Потому что в некоторых регионах и более низкие требования будут.


    1. SirEdvin
      03.01.2019 11:14
      +1

      Совсем потом можно подумать на тему хранения сессий в WhatsApp. Миллиард активных пользователей. Как вы бы обработали на серверах stateful?

      Боги послали вам редис с шардированием, например.


  1. epishman
    03.01.2019 06:14

    У Вас клиентская часть тоже на голэнге? А как доставлять такое приложение на мобильник, я просто не в курсе технологии.


    1. youROCK Автор
      03.01.2019 11:06
      +2

      Это лишь пример, поэтому да, тоже на го :). На самом деле go вполне встраивается в мобилки, есть даже утилита для построения интерфейса целиком на go под названием gomobile. Если интересно, как я тестировал этот «клиент» на телефоне, то всё довольно просто: для Android есть приложение Termux, (небольшой дистрибутив Linux) котором можно в качестве одного из пакетов поставить golang, после чего я склонировал репозиторий, собрал его прямо на телефоне, написал скрипт для серийного запуска и положил в холодильник гонять тесты, для симуляции ситуации «в лифте».


      1. epishman
        03.01.2019 12:14

        Спасибо, познавательно. Хотя я повременю это изучать, надеюсь все-таки на снятие проклятия с PWA :)


    1. JekaMas
      04.01.2019 16:38
      +1

      Отлично работает гошка прямо на мобильных. См, например, Status.im


  1. SellerOfSmiles
    03.01.2019 09:13

    Когда я последний раз изобретал свой протокол для Интернет, то столкнулся с тем что примерно 10% моих пользователей работают в корпоративных сетях за всякими фаерволами, и с ними можно взаимодействовать только по http(s). Что привело меня тогда к необходимости передавать реалтаймовые данные по tcp.
    Думаю в процессе разработки вашего «отказоустойчивого» протокола этот опыт тоже будет полезен. :)


  1. eteh
    03.01.2019 10:37

    Ну если сравнивать по latency то вотсапп например неплохо живет и на спутниковом инете — пинг 600-2500 скорость 8-40 мбит в зависимости от погоды...


  1. ivan386
    03.01.2019 13:20

    Опять в очередном меседжере используется прокладка в виде сервера. Почему бы не отправлять сообщения напрямую получателю ведь интернет на это расчитан. Теперь уже и IPv6 есть которому NAT не нужен.


    Кстати в GO есть пакет шифрования так что изобретать ничего не нужно.


    1. Goodkat
      03.01.2019 14:06

      Потому что ваш телефон извне недоступен, наверное.


      1. ivan386
        03.01.2019 14:09

        С чего это вдруг? Тем более по UDP.


        1. youROCK Автор
          03.01.2019 14:12

          NAT может этому помешать. На практике до 50% мобильных абонентов не «видят» друг друга напрямую, даже если использовать техники вроде STUN. Подробнее можно почитать об этом, например, тут: en.wikipedia.org/wiki/STUN#Limitations:

          > In the cases of restricted cone or port restricted cone NATs, the client must send out a packet to the endpoint before the NAT will allow packets from the endpoint through to the client. STUN does not work with symmetric NAT (also known as bi-directional NAT) which is often found in the networks of large companies.


        1. Goodkat
          04.01.2019 14:52

          Может NAT, может файрволл у опсоса или в самом телефоне, не знаю.


          Несколько раз пытался попасть на свой телефон извне — через вайфай получалось, через мобильную связь — нет.


          1. ivan386
            04.01.2019 15:06
            +1

            IPv6 операторы потихоньку внедряют. Для IPv4 нужно пробивать UDP порт при помощи UDP hole punch.


            1. youROCK Автор
              04.01.2019 15:25

              Спасибо за ссылку :)! У меня, как оказалось, уже давно подключена услуга IPv6, но «просто так» IPv6 при этом не работал, нужно ещё настройки APN было подкрутить. Тем не менее, test IPv6 теперь у меня выдает 9/10, что уже неплохо.


              1. Goodkat
                04.01.2019 20:12

                Телефон доступен извне?


                1. youROCK Автор
                  04.01.2019 21:47

                  Удивительно, но да, выдается белый (но, вероятно, динамический) IP.


            1. epishman
              04.01.2019 20:42
              +1

              Это ж кошмар для прослушивающих дядей будет, если все начнут друг-другу что-то слать, как это мониторить, как этим управлять?


    1. youROCK Автор
      04.01.2019 14:16

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

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


      1. ivan386
        04.01.2019 14:32

        Помоему отправить UDP пакет напрямую гораздо быстрее чем через сервер. Ну и использование IPv6 избавит от необходимости преодолевать NAT.


        Сервер нужен для первичного входа в DHT сеть (сразу после установки приложения либо после очень долгого отсутствия в сети). Если сеть будет достаточно крупной то вполне сможет обеспечить временное хранение шифрованных не доставленных сообщений пока оба абонента офлайн.


        Авторизованные абоненты могут сигнализировать друг другу напрямую о изменении адреса и порта. Если вдруг случится что они сменят адрес одновременно то могут найти друг друга в DHT.


        1. youROCK Автор
          04.01.2019 14:35

          В целом, всё звучит разумно, но учитывайте, что целью моего эксперимента было достижение минимально возможной задержки, и «поиск друг друга в DHT» явно будет препятствовать быстрой первоначальной отправке, а в этом был основной смысл.

          > Помоему отправить UDP пакет напрямую гораздо быстрее чем через сервер. Ну и использование IPv6 избавит от необходимости преодолевать NAT.

          Да, если у всех есть белый IP-адрес, то отправка напрямую обычно будет быстрее. Но пока что реальность такова, что белого IP почти ни у кого нет, тем более на мобильном интернете. Ну и IPv6 пока что тоже встречается очень редко, к сожалению.


          1. ivan386
            04.01.2019 14:45

            6to4 поможет получить IPv6 даже за натом. Если его конечно специально не будут резать.


            1. youROCK Автор
              04.01.2019 14:50

              В общем, в текущих условиях, особенно если речь про мобилки, отправка на сервер с белым IPv4 — это самый предсказуемый по latency вариант, который точно будет работать. Будет ли работать именно UDP — вопрос открытый, но если, опять же, речь про мобильную связь, то обычно UDP работает без проблем, по крайней мере у большой тройки.

              Ваши варианты интересные, но они не ориентированы на минимальную задержку отправки, поскольку для работы IPv6 без NAT сейчас нужны всякие «костыли» вроде 6to4, которые явно стабильности и предсказуемости в работе не прибавляют. В светлом будущем, когда у всех белый IPv6 и нет NAT, я верю, что ваш вариант действительно будет самым лучшим, как в плане latency, так и в плане отсутствия централизованного хранения чьих-либо данных.


            1. ValdikSS
              04.01.2019 15:31

              6to4 не работает за NAT.


              1. ivan386
                04.01.2019 15:35

                У меня работает на всех провайдерах. Только я беру 64 битную зону. Тунельные провайдеры бы тоже тогда не работали.


                1. ValdikSS
                  04.01.2019 16:27

                  Для 6to4 нужен маршрутизируемый IP-адрес, он не работает за NAT. Туннельные провайдеры, использующие 6in4, тоже не работают за NAT.


                  1. ivan386
                    04.01.2019 16:46

                    Я сейчас нахожусь за NAT.


                    Перепроверил. test-ipv6.com даёт 7 из 10. Даже пинганул себя сервисом Online Ping IPv6 все пинги пришли.


                    1. ValdikSS
                      04.01.2019 16:48

                      Значит, вам провайдер выдает IPv6, или вы находитесь не за NAT провайдера, а за подконтрольным вам NAT на вашем маршрутизаторе.


                      1. ivan386
                        04.01.2019 17:21

                        Я сам 6to4 только что настроил(опять настройки слетели) на своём роутере. Провайдеры не хотят давать IPv6. NAT ни одного из провайдеров я не контролирую. От провайдеров на роутер приходит локальные IP(10.x.x.x).


                        1. Глобальный IP я узнаю в сервисах типа 2ip.ru а далее генерирую IPv6 зону по нему.
                        2. В сервсисе IPv4 to IPv6 Conversion получаю IPv6 адрес.
                        3. Заменяю заменяю один ноль рандомным числом и задаю зону /64
                        4. Задаю IPv6 роутера
                        5. Задаю адрес сервера 192.88.99.1

                        Далее на всю технику IPv6 прилетает от роутера.


                        Единственно что если исходящего ipv6 трафика не будет то через некоторое время NAT теряет маршрут к роутеру.


                        1. ValdikSS
                          04.01.2019 18:08

                          Вы уверены, что ваш провайдер не делает one-to-one NAT, который не транслирует порты и протоколы, а только перенаправляет весь трафик с одного адреса на другой?


                          1. ivan386
                            04.01.2019 12:24

                            Уверен. Я столько торрентов не качаю сколько вижу на iknowwhatyoudownload.com.


                            1. ValdikSS
                              04.01.2019 14:53

                              Это очень странно, либо вы что-то не договариваете. Даже если у вашего провайдера есть conntrack helper для протокола 6in4, то всё равно нельзя определить, кому за NAT предназначается входящее соединение. Полноценно это работать не будет.


                              1. ivan386
                                04.01.2019 16:13

                                Если NAT понимает инкапсулированный IPv6 то ему проще. Запомнил с какого локального IPv4 адреса прилетел IPv6 и соответственно все пакеты в обратную сторону с этим IPv6 адресом в получателе отправлять на тот локальный IPv4.


                                Можно даже ограничится запоминанием 64 бит потсети.


        1. lixmix
          04.01.2019 18:18
          +1

          Авторизованные абоненты могут сигнализировать друг другу напрямую о изменении адреса и порта.

          На Андроиде и IOS приложение часто впадают в спячку. Операционная система рвет соедения, после выхода приложения или отключения экрана. IP адреса на мобильных устройствах могут менятся каждые пять минут


    1. lixmix
      04.01.2019 17:52
      +1

      Опять в очередном меседжере используется прокладка в виде сервера. Почему бы не отправлять сообщения напрямую получателю ведь интернет на это расчитан. Теперь уже и IPv6 есть которому NAT не нужен.

      Сервера нужны по-целому ряду причин:
      • Без сервера сервера оба собеседника должны быть постоянно в сети и активно обмениватся пакетами присутвия. Это нереально, в особенности на мобильных устройствах, которые уходят в спячку
      • Без сервера можно удобно провести целевые атаки на устройства пользователя. Я попробую пример привести: Вы заблокировали собеседника. Он решили Вам отомстить. Установив Ваш айпи, он устраивает атаку на ваш роутер и Вы лишаетесь интернета на день. Вы меняете айпи, но это не помогает, поскольку злойдей снова находит Вас по p2p. На следующий день Вам звонит Ваш провайдер и предлогает перейти на услуги для комерческих клиентов, потому что Вам нужна защита от Ддос-атак на 10К рублей. Вы покупаете это пакет, но злодей снова находит Вас, сканируют ваши порты и находит уязвимость в Виндовс и скачивает Ваши хоме фото, скан паспорта и т.д

      Это лишь первые недостатки, которые пришли в голову. Можно еще много найти минусов. Я бы не назвал сервера ненужно прокладкой. Если нет сервера, то устройство выполняет роль сервера, что не всегда целесобразно.


    1. SunTechnik
      04.01.2019 22:40

      Потому что ОПСОСы предоставляют, почему-то именно IPv4 адрес. ( Посмотрите на своем телефоне). Состояние IPv6 достаточно странное: он давно есть, ни кроме гиков его почти никто не использует…


      1. farcaller
        04.01.2019 00:16

        Состояние IPv6 достаточно странное: он давно есть, ни кроме гиков его почти никто не использует

        Verizon Wireless similarly reports that about 90% of its traffic uses IPv6


        Однако же?


  1. ValdikSS
    04.01.2019 14:26

    Уже существует DTLS — TLS через UDP.


    1. youROCK Автор
      04.01.2019 14:31

      Да, но у DTLS тоже есть немаленький handshake, что в моём случае сводит весь профит от отсутствия хэндшейка в UDP на нет.


      1. ValdikSS
        04.01.2019 15:14

        Не думаю, что это принципиально, ведь соединение перед заходом в лифт будет уже установлено.


        1. youROCK Автор
          04.01.2019 15:29

          Не факт. Или предполагается «соединение» вообще не закрывать и сохранять сессионные ключи на диск, если приложение выгружено из памяти? Если так, то звучит интересно, конечно.


          1. ValdikSS
            04.01.2019 15:30

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


            1. youROCK Автор
              04.01.2019 15:33

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


            1. ZaEzzz
              04.01.2019 22:10
              +1

              Вы застряли в лифте и решили об этом написать.


              1. ZaEzzz
                04.01.2019 22:17

                P.S. не нашёл как в мобильной версии изменять сообщение. В общем вспомнил ваш пост как застрял в лифте.
                P.P.S. и часто наблюдал как связь теряется у владельцев айфона.


    1. youROCK Автор
      04.01.2019 14:32

      Но важным преимуществом DTLS является его устойчивость к DDoS атакам с амплификацией, поэтому для каких-то вещей его точно можно использовать :).


  1. miolini
    04.01.2019 15:16

    Предлагаю в тестирование добавить еще кол-во syscall на 1 мбит/с потока данных, cpu load, pps.


    1. youROCK Автор
      04.01.2019 15:36

      Я ведь отправляю текстовые сообщения, поэтому там не будет большого PPS. Одно клиентское приложение во время активной отправки будет генерировать от силы с десяток PPS.

      Если речь про сервер, то на одном ядре процессора реалистично обрабатывать до 50k pps, что будет примерно 25k pps на прием и 25k pps на отправку. Мне кажется, даже одного ядра без ухищрений вроде netmap хватит достаточно надолго :).


      1. miolini
        04.01.2019 15:43

        Конкретно в Go UDP против TCP гораздо более скромные результаты в PPS. Основная причина в кратном увеличении кол-ве системных вызовов.


        1. youROCK Автор
          04.01.2019 16:26

          Кажется, при желании можно сильно улучшить производительность: go-review.googlesource.com/c/net/+/38275.

          Пример использования, по идее, есть тут: godoc.org/golang.org/x/net/ipv4#PacketConn.ReadBatch


  1. lixmix
    04.01.2019 17:32
    -1

    XMPP сервера тоже могут работать через UPD. По умолчанию идут с TCP, но можно перекллючить протокол на UPD


    1. gudvinr
      03.01.2019 23:25

      Вы же понимаете, что в XMPP оверхед на одно сообщение гигантский, что объем трафика значительно увеличивает? Плюс, он архитектурно достаточно плохо переживает постоянные обрывы соединения и переключения на другой канал. А учитывая посредственную поддержку XMPP в клиентских библиотеках в настоящее время, придётся фактически заново изобретать протокол. Несмотря на то, что XEP, которые решают эту проблему, вообще говоря, есть.


  1. netricks
    04.01.2019 02:54

    Господа. Вы решаете не ту задача.

    Дело не в мессенджере, а в том, что tcp становится плохо при хреновой связи. Это проблема сетевого и транспортного уровней, а не уровня приложения.
    Получается, нужно выкинуть из головы всю хрень про шифрование, сервера и прочую лабуду и разработать аналог tcp, который будет нормально работать при 95% потерянных пакетов…


  1. Drebin893
    04.01.2019 16:36

    Я никогда не понимал, почему разработчики своих apps никогда не тестируют свои творения с плохой связью и не оптимизируют их для работы при плохой связи. Причем речь идет о топовых apps. Я говорю не только про мессенджеры.


  1. FYR
    04.01.2019 17:03

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


    1. ZaEzzz
      04.01.2019 21:17

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


      1. yetanotherman
        04.01.2019 22:42

        Практика обычно показывает, что любой протокол нужно уметь готовить. REST без дополнительных ухищрений прверх TLS всегда будет плохо работать в нестабильной среде, но это не значит, что TCP плох. Готов поспорить, что разумное применение TCP будет в общем случае работать лучше, чем поделка на коленках на основе UDP. Есть хорошая книжка на эту тему — «Эффективное программирование TCP/IP» (Йон Снейдер). Она достаточно старая, но подробно рассматривает озвученную в статье проблему.


        1. ZaEzzz
          05.01.2019 10:41

          Готов поспорить, что разумное применение TCP будет в общем случае работать лучше, чем поделка на коленках на основе UDP.

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

          Кроме того TCP имеет ряд недостатков, которые приводят к необходимости проверки целостности данных. То есть не смотря на то, что он как бы гарантирует доставку и целостность данных, но это не точно.
          Особо интересно, когда TCP вреден. Если откинуть медиастриминг, туннелирование и реалтайм данные (к примеру, онлайн игры, биржи), то сходу можно найти проблему приоретизации данных в рамках одной сессии — TCP будет пытаться дослать вам данные, которые для приложения имеют меньший приоритет и вы никак на это не сможете повлиять. Конечно же можно для данных разного рода использовать отдельные соединения, но это не лучший выход из ситуации.

          Есть хорошая книжка на эту тему — «Эффективное программирование TCP/IP» (Йон Снейдер). Она достаточно старая, но подробно рассматривает озвученную в статье проблему.

          Книжка хорошо освещает вопрос в общих чертах и даже немного затрагивает достаточно частные случаи, но некоторые выводы и советы там вызывают вопросы. К примеру, в ней дается совет, что не надо изобретать TCP, но тут же сразу упоминается о том, что действительно есть отличные реализации на UDP.
          Когда она вышла, были вопросы как они умудрились получить потери данных в рамках локалхоста, но в конечном итоге они написали пример «как написалось» — отсюда и потери.
          Это мы приходим к вашему верному утверждению, что
          любой протокол нужно уметь готовить.

          Да, любой протокол нужно уметь готовить. Если в общих случаях TCP подойдет и лень заморачиваться с оберткой вокруг UDP, то используйте TCP и учитесь его готовить, но не ожидайте от него серебряную пулю и готовьтесь к внезапным неожиданным сбоям в продакшене. Для решения некоторых проблем можно попробовать SCTP, но это так же не гарантированный выход.

          Кстати да, UDP так же не является гарантированно предпочтительным протоколом — не смотря на то, что он дает бОльшую свободу над контролем передаваемых данных, он так же обязывает больше за ними следить.

          Для каждой задачи свои решения.


      1. FYR
        04.01.2019 23:37

        Конечно, если изобрести что-то очень очень узкозаточенное, причем и по функционалу и по условиям использования — есть шанс сделать лучше. Но это будет очень узко специализированное.


        А например в настройках сетевого стека tcp у линукс 100500 всяких параметров. Там наверняка есть то что нужно, надо только найти нужное )))


        1. ZaEzzz
          05.01.2019 10:45

          Конечно, если изобрести что-то очень очень узкозаточенное, причем и по функционалу и по условиям использования — есть шанс сделать лучше. Но это будет очень узко специализированное.

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

          А например в настройках сетевого стека tcp у линукс 100500 всяких параметров. Там наверняка есть то что нужно, надо только найти нужное )))

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