Возможно, вы встречались с подобной ситуацией – сидите в любимом мессенджере, переписываетесь с друзьями, заходите в лифт/тоннель/вагон, и интернет вроде ещё ловит, но отправить ничего не получается? Или иногда ваш провайдер связи неправильно конфигурирует сеть и 50% пакетов пропадает, и тоже ничего не работает. Возможно, вы думали в этот момент — ну ведь можно же наверное как-то сделать, чтобы при плохой связи всё равно можно было отправить тот маленький кусочек текста, который вы хотите? Вы не одни.
Источник картинки
В этой статье я расскажу про свою идею для реализации протокола на основе UDP, который может помочь в этой ситуации.
Проблемы TCP/IP
Когда у нас плохое (мобильное) соединение, то начинает теряться большой процент пакетов (или ходить с очень большой задержкой), и протокол TCP/IP может воспринимать это как сигнал о том, что сеть перегружена, и всё начинает работать оооочень медленно, если работает вообще. Не добавляет радости тот факт, что установление соединения (особенно TLS) требует отправки и приема нескольких пакетов, и даже небольшие потери сказываются на его работе очень плохо. Также часто требуется обращение к DNS перед тем, как установить соединение — ещё пара лишних пакетов.
Итого, проблемы типичного REST API, основанного на TCP/IP при плохом соединении:
- Плохая реакция на потери пакетов (резкое уменьшение скорости, большие таймауты)
- Установление соединения требует обмена пакетами (+3 пакета)
- Часто нужен «лишний» DNS-запрос, чтобы узнать IP сервера (+2 пакета)
- Часто нужен TLS (+2 пакета минимум)
Суммарно это означает, что только для соединения с сервером нам нужно послать 3-7 пакетов, и при высоком проценте потерь соединение может занять существенное количество времени, а мы ещё даже ничего не отправили.
Идея реализации
Идея состоит в следующем: нам требуется всего-лишь отправить один UDP-пакет на заранее зашитый IP-адрес сервера с необходимыми данными авторизации и с текстом сообщения, и получить на него ответ. Все данные можно дополнительно зашифровать (этого в прототипе нет). Если ответ в течение секунды не пришел, то считаем, что запрос потерялся и пробуем отправить его заново. Сервер должен уметь убирать дубли сообщений, поэтому повторная отправка не должна создать проблем.
Возможные подводные камни для production-ready реализации
Ниже перечислены (далеко не все) вещи, которые нужно продумать перед тем, как использовать что-либо подобное в «боевых» условиях:
- UDP может «резаться» провайдером — нужно уметь работать и по TCP/IP
- UDP плохо дружит с NAT — обычно есть мало (~30 сек) времени, чтобы ответить клиенту на его запрос
- Сервер должен быть устойчив к атакам усиления — нужно гарантировать, что пакет с ответом будет не больше пакета с запросом
- Шифрование — это сложно, и если вы не эксперт по безопасности, у вас мало шансов реализовать его корректно
- Если выставить интервал перепосылок неправильно (например, вместо того, чтобы пробовать заново раз в секунду, пробовать заново без остановки), то можно сделать намного хуже, чем TCP/IP
- На ваш сервер может начать приходить больше трафика из-за отсутствия обратной связи в UDP и бесконечных повторных попыток отправки
- 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 секунд пользователь, скорее всего, ждать не станет.
Тестирование, результаты
При тестировании прототипа измерялись следующие вещи (всё в миллисекундах):
- Время ответа на первый запрос (first)
- Среднее время ответа (avg)
- Максимальное время ответа (max)
- H/U — соотношение «время HTTP» / «время UDP» — во сколько раз меньше задержка при использовании UDP
Делалось 100 серий по 10 запросов — симулируем ситуацию, когда нужно послать буквально несколько сообщений и после этого уже становится доступен нормальный интернет (например Wi-Fi в метро, или 3G/LTE на улице).
Протестированные виды связи:
- Профиль «Very Bad Network» (10% потерь, 500 мс latency, 1 мбит/сек) в Network Link Conditioner — «Very Bad»
- EDGE, телефон в холодильнике («лифте») — fridge
- EDGE
- 3G
- LTE
- Wi-Fi
Результаты (время в миллисекундах):
(то же самое в формате CSV)
Выводы
Вот, какие выводы можно сделать из получившихся результатов:
- Если не считать аномалию с LTE, то разница при посылке первого сообщения тем больше, чем хуже связь (в среднем в 2-3 раза быстрее)
- Последующая отправка сообщений в HTTP не сильно медленней — в среднем в 1,3 раза медленней, а на стабильном Wi-Fi вообще разницы нет
- Время ответа на основе UDP намного стабильнее, что косвенно видно по максимальному времени ожидания — оно тоже меньше в 1,4-1,8 раз
Другими словами, в соответствующих («плохих») условиях наш протокол будет работать намного лучше, особенно при посылке первого сообщения (часто это всё, что необходимо отправить).
Реализация прототипа
Прототип выложен на github. Не используйте его в продакшене!
Команда для запуска клиента на телефоне или компьютере:
instant-im -client -num 10
. Сервер пока что запущен :). Нужно смотреть в первую очередь на время первого ответа, а также на максимальную задержку. Все эти данные печатаются в конце.Комментарии (78)
BugM
03.01.2019 04:14-2В реальном мире у нас есть авторизации, ключи, сессии.
Вы сделали stateful мессенджер для телефона. А теперь придумайте пяток сценариев где это не будет работать вообще.
И на закуску парочку сценариев где это приведет к серьезным проблемам. Не отправленное сообщение, которое отображается отправленным, это серьезная проблема.
Совсем потом можно подумать на тему хранения сессий в WhatsApp. Миллиард активных пользователей. Как вы бы обработали на серверах stateful?
И сразу все станет понятно.LeshiyUrban
03.01.2019 08:10Как прототип/идея весьма неплохо. N сессий тут решается простым KV хранилищем с ключом по ID клиента.
А для синхронизации последнего отправленного сообщения (проблема "отправленного" и не пришедшего сообщения) можно использовать алгоритм из биржевого FIX. Он прост как две копейки и, по моему, отлично ложится на предложенную архитектуру.
Не претендую на истинность, но я бы в свободное время поразмыслил над этим проектомBugM
03.01.2019 13:51Это даже на идею не тянет. Не говоря уже о прототипе. Отправка по UDP пакета и ожидание ответа сервера перед отправкой следующего максимум на лабораторку тянет.
Решается, только масштабируется не очень. Нам нужен как минимум id клиента, id сессии, ключ шифрования и хеш с таймстемпом последнего сообщения. Пара килобайт точно. Если подумать точно еще что-то вылезет что надо хранить. На миллиарде клиентов это терабайты данных.
Шардирование делать сложно. Пакеты шифрованные. В начале надо терминировать tls. Проблема явно решаемая, но не с налету.
Вот чувствую зря я про совсем потом на писал. Там и без этого пункта проблем с stateful выше крыши.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 выше крыши.
Основная проблема — «чувствую» :). Все эти проблемы вполне решаемы, но для прототипа не важны.BugM
04.01.2019 14:31+1Вы вот так вот сразу отказали от End2End шифрования. В 2019 году. Вы это серьезно?
Шифруем все в 2 слоя. Первый пакет до сервера, второй само сообщение.
Сессионный ключ клиента это не паблик ключ сервера. Это именно сессионный ключ. Чтобы в случае компрометации серверного ключа трафик нельзя было расшифровать. Да и использовать один ключ неопределенно долгое время это не очень. Азы же.
1. Вы считаете размер пакета, а надо считать размер инфы которою нужно хранить на серверах для поддержания сессии.
2. Всю историю можно хранить на дисках и не париться о скорости. Подгрузка истории это процесс не требующий особой скорости. Клиент готов подождать пока у него загрузится история на новой устройстве. А вот информация о сессиях нужна в памяти. К ней нужен очень быстрый доступ.
3. Проблема не в сообщениях, а в клиентах. С сообщениями все понятно. таймстемп последнего и хеш от него же.
При https да. Именно так и делаем и все просто. Вы же хотите кастомный udp протокол. На сервер будет приходить шифрованный массив. Надо найти от него ключ, расшифровать, найти клиента, валидировать что все ок и уже потом передать на обработку.
4. Сессионные ключи строго обязательны в 2019 году. Я выше написал.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 году. Я выше написал.
Ок, можно шифровать сессионным ключом, противоречий с моей идеей пока что не вижу :).BugM
04.01.2019 17:53+1Как именно шифровать это не важно в данном случае. Но необходимо понимать общий принцип шифрования. Пакет отдельно, пользовательские данные в нем отдельно. Ключ от пакета сессионный, ключ от пользовательских данных только у получателя.
Миллион пользователей это мало. Мессенджеры нынче живут на миллиарде пользователей. И любая разработка должна быть готова к работе с таким объемом. Миллиард это 10Тб шаренной и доступной со всех узлов входа ОЗУ только на сессии. Это уже много. Очень много. Да и просто держать столько открытых tls сессий это задача ну очень нетривиальная.
Проблема что это плохо масштабируется горизонтально.
Проблема что это плохо будет работать за фаерваллами.
Это будет плохо работать за двойными натами.
Это плохо совместимо с проксями.
Это проблема консистентности статуса клиента и сервера.
Это в конце концов кастомный низкоуровневый протокол. То есть все типовые балансировщики с ним будут работать плохо или не будут вообще.
Реверс прокси работать не будет точно.
И ради чего все это? Чтобы пользователь имел повышенный шанс отправить сообщение при ухудшении сигнала? Оно того не стоит.youROCK Автор
04.01.2019 21:39> Миллиард это 10Тб шаренной и доступной со всех узлов входа ОЗУ только на сессии. Это уже много. Очень много.
Учитывая, что 512 Гб на сервер — это вполне стандартная (чуть выше среднего) современная конфигурация, вам нужно всего 20 серверов, чтобы хранить это. Если учесть резервирование, то 40 серверов. Для хранения самых пользовательских данных вам потребуется в сотни раз больше, так что это не проблема.
> Реверс прокси работать не будет точно.
В целом, в этом нет особой необходимости, весь трафик может принять буквально несколько серверов. К тому же, балансировку на уровне сети никто не отменял.
> Это будет плохо работать за двойными натами.
Поясните, пожалуйста, почему именно за двойным натом это должно плохо работать.
> Проблема что это плохо масштабируется горизонтально.
Уж что-что, а UDP прекрасно масштабируется на любое число машин, поскольку нет состояния между запросами.
> Проблема что это плохо будет работать за фаерваллами.
Возможно.
> Это плохо совместимо с проксями.
Если речь про корпоративный прозрачный HTTP-прокси, то да. Речь шла про мобильный интернет в первую очередь, там нет никаких проксей обычно.
> Это в конце концов кастомный низкоуровневый протокол. То есть все типовые балансировщики с ним будут работать плохо или не будут вообще.
Вы говорите про L7 балансировку, но есть и другие виды балансировки, например L3/L4.
И я бы не сказал, что этот протокол низкоуровневый :). С каких пор UDP стал протоколом низкого уровня?
> И ради чего все это? Чтобы пользователь имел повышенный шанс отправить сообщение при ухудшении сигнала? Оно того не стоит.
А по-моему, стоит, если вы разрабатываете мессенджер и это ваш основной бизнес. Люди целые протоколы изобретают или начинают использовать просто ради улучшения качества связи, как например Apple с Multipath TCP для звонков FaceTime или iMessage — благодаря этой технологии они могут работать даже при более плохих условиях связи, и не прерывать соединение при смене сети — это очень большое конкурентное преимущество по сравнению с остальными.gudvinr
03.01.2019 23:13Учитывая, что 512 Гб на сервер — это вполне стандартная (чуть выше среднего) современная конфигурация, вам нужно всего 20 серверов, чтобы хранить это. Если учесть резервирование, то 40 серверов. Для хранения самых пользовательских данных вам потребуется в сотни раз больше, так что это не проблема.
А если учесть, что плотность пользователей сильно зависит от локации и онлайн в разных локациях разнится в разное время суток, то в реальности даже и это — оценка сверху. Потому что в некоторых регионах и более низкие требования будут.
SirEdvin
03.01.2019 11:14+1Совсем потом можно подумать на тему хранения сессий в WhatsApp. Миллиард активных пользователей. Как вы бы обработали на серверах stateful?
Боги послали вам редис с шардированием, например.
epishman
03.01.2019 06:14У Вас клиентская часть тоже на голэнге? А как доставлять такое приложение на мобильник, я просто не в курсе технологии.
youROCK Автор
03.01.2019 11:06+2Это лишь пример, поэтому да, тоже на го :). На самом деле go вполне встраивается в мобилки, есть даже утилита для построения интерфейса целиком на go под названием gomobile. Если интересно, как я тестировал этот «клиент» на телефоне, то всё довольно просто: для Android есть приложение Termux, (небольшой дистрибутив Linux) котором можно в качестве одного из пакетов поставить golang, после чего я склонировал репозиторий, собрал его прямо на телефоне, написал скрипт для серийного запуска и положил в холодильник гонять тесты, для симуляции ситуации «в лифте».
epishman
03.01.2019 12:14Спасибо, познавательно. Хотя я повременю это изучать, надеюсь все-таки на снятие проклятия с PWA :)
SellerOfSmiles
03.01.2019 09:13Когда я последний раз изобретал свой протокол для Интернет, то столкнулся с тем что примерно 10% моих пользователей работают в корпоративных сетях за всякими фаерволами, и с ними можно взаимодействовать только по http(s). Что привело меня тогда к необходимости передавать реалтаймовые данные по tcp.
Думаю в процессе разработки вашего «отказоустойчивого» протокола этот опыт тоже будет полезен. :)
eteh
03.01.2019 10:37Ну если сравнивать по latency то вотсапп например неплохо живет и на спутниковом инете — пинг 600-2500 скорость 8-40 мбит в зависимости от погоды...
ivan386
03.01.2019 13:20Опять в очередном меседжере используется прокладка в виде сервера. Почему бы не отправлять сообщения напрямую получателю ведь интернет на это расчитан. Теперь уже и IPv6 есть которому NAT не нужен.
Кстати в GO есть пакет шифрования так что изобретать ничего не нужно.
Goodkat
03.01.2019 14:06Потому что ваш телефон извне недоступен, наверное.
ivan386
03.01.2019 14:09С чего это вдруг? Тем более по UDP.
youROCK Автор
03.01.2019 14:12NAT может этому помешать. На практике до 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.
Goodkat
04.01.2019 14:52Может NAT, может файрволл у опсоса или в самом телефоне, не знаю.
Несколько раз пытался попасть на свой телефон извне — через вайфай получалось, через мобильную связь — нет.
ivan386
04.01.2019 15:06+1IPv6 операторы потихоньку внедряют. Для IPv4 нужно пробивать UDP порт при помощи UDP hole punch.
youROCK Автор
04.01.2019 15:25Спасибо за ссылку :)! У меня, как оказалось, уже давно подключена услуга IPv6, но «просто так» IPv6 при этом не работал, нужно ещё настройки APN было подкрутить. Тем не менее, test IPv6 теперь у меня выдает 9/10, что уже неплохо.
epishman
04.01.2019 20:42+1Это ж кошмар для прослушивающих дядей будет, если все начнут друг-другу что-то слать, как это мониторить, как этим управлять?
youROCK Автор
04.01.2019 14:16Всё равно нужно как-то координировать действия, и, зачастую, как уже сказали выше, установление прямого соединения пока что невозможно.
Ну и, конечно же, P2P соединение будет устанавливаться ещё дольше. Но вы можете использовать сервер для кратковременного хранения сообщений, которые зашифрованы ключом вашего собеседника, тогда это будет более безопасно, хотя я не security эксперт, к сожалению :).ivan386
04.01.2019 14:32Помоему отправить UDP пакет напрямую гораздо быстрее чем через сервер. Ну и использование IPv6 избавит от необходимости преодолевать NAT.
Сервер нужен для первичного входа в DHT сеть (сразу после установки приложения либо после очень долгого отсутствия в сети). Если сеть будет достаточно крупной то вполне сможет обеспечить временное хранение шифрованных не доставленных сообщений пока оба абонента офлайн.
Авторизованные абоненты могут сигнализировать друг другу напрямую о изменении адреса и порта. Если вдруг случится что они сменят адрес одновременно то могут найти друг друга в DHT.
youROCK Автор
04.01.2019 14:35В целом, всё звучит разумно, но учитывайте, что целью моего эксперимента было достижение минимально возможной задержки, и «поиск друг друга в DHT» явно будет препятствовать быстрой первоначальной отправке, а в этом был основной смысл.
> Помоему отправить UDP пакет напрямую гораздо быстрее чем через сервер. Ну и использование IPv6 избавит от необходимости преодолевать NAT.
Да, если у всех есть белый IP-адрес, то отправка напрямую обычно будет быстрее. Но пока что реальность такова, что белого IP почти ни у кого нет, тем более на мобильном интернете. Ну и IPv6 пока что тоже встречается очень редко, к сожалению.ivan386
04.01.2019 14:456to4 поможет получить IPv6 даже за натом. Если его конечно специально не будут резать.
youROCK Автор
04.01.2019 14:50В общем, в текущих условиях, особенно если речь про мобилки, отправка на сервер с белым IPv4 — это самый предсказуемый по latency вариант, который точно будет работать. Будет ли работать именно UDP — вопрос открытый, но если, опять же, речь про мобильную связь, то обычно UDP работает без проблем, по крайней мере у большой тройки.
Ваши варианты интересные, но они не ориентированы на минимальную задержку отправки, поскольку для работы IPv6 без NAT сейчас нужны всякие «костыли» вроде 6to4, которые явно стабильности и предсказуемости в работе не прибавляют. В светлом будущем, когда у всех белый IPv6 и нет NAT, я верю, что ваш вариант действительно будет самым лучшим, как в плане latency, так и в плане отсутствия централизованного хранения чьих-либо данных.
ValdikSS
04.01.2019 15:316to4 не работает за NAT.
ivan386
04.01.2019 15:35У меня работает на всех провайдерах. Только я беру 64 битную зону. Тунельные провайдеры бы тоже тогда не работали.
ValdikSS
04.01.2019 16:27Для 6to4 нужен маршрутизируемый IP-адрес, он не работает за NAT. Туннельные провайдеры, использующие 6in4, тоже не работают за NAT.
ivan386
04.01.2019 16:46Я сейчас нахожусь за NAT.
Перепроверил. test-ipv6.com даёт 7 из 10. Даже пинганул себя сервисом Online Ping IPv6 все пинги пришли.
ValdikSS
04.01.2019 16:48Значит, вам провайдер выдает IPv6, или вы находитесь не за NAT провайдера, а за подконтрольным вам NAT на вашем маршрутизаторе.
ivan386
04.01.2019 17:21Я сам 6to4 только что настроил(опять настройки слетели) на своём роутере. Провайдеры не хотят давать IPv6. NAT ни одного из провайдеров я не контролирую. От провайдеров на роутер приходит локальные IP(10.x.x.x).
- Глобальный IP я узнаю в сервисах типа 2ip.ru а далее генерирую IPv6 зону по нему.
- В сервсисе IPv4 to IPv6 Conversion получаю IPv6 адрес.
- Заменяю заменяю один ноль рандомным числом и задаю зону /64
- Задаю IPv6 роутера
- Задаю адрес сервера 192.88.99.1
Далее на всю технику IPv6 прилетает от роутера.
Единственно что если исходящего ipv6 трафика не будет то через некоторое время NAT теряет маршрут к роутеру.
ValdikSS
04.01.2019 18:08Вы уверены, что ваш провайдер не делает one-to-one NAT, который не транслирует порты и протоколы, а только перенаправляет весь трафик с одного адреса на другой?
ivan386
04.01.2019 12:24Уверен. Я столько торрентов не качаю сколько вижу на iknowwhatyoudownload.com.
ValdikSS
04.01.2019 14:53Это очень странно, либо вы что-то не договариваете. Даже если у вашего провайдера есть conntrack helper для протокола 6in4, то всё равно нельзя определить, кому за NAT предназначается входящее соединение. Полноценно это работать не будет.
ivan386
04.01.2019 16:13Если NAT понимает инкапсулированный IPv6 то ему проще. Запомнил с какого локального IPv4 адреса прилетел IPv6 и соответственно все пакеты в обратную сторону с этим IPv6 адресом в получателе отправлять на тот локальный IPv4.
Можно даже ограничится запоминанием 64 бит потсети.
lixmix
04.01.2019 18:18+1Авторизованные абоненты могут сигнализировать друг другу напрямую о изменении адреса и порта.
На Андроиде и IOS приложение часто впадают в спячку. Операционная система рвет соедения, после выхода приложения или отключения экрана. IP адреса на мобильных устройствах могут менятся каждые пять минут
lixmix
04.01.2019 17:52+1Опять в очередном меседжере используется прокладка в виде сервера. Почему бы не отправлять сообщения напрямую получателю ведь интернет на это расчитан. Теперь уже и IPv6 есть которому NAT не нужен.
Сервера нужны по-целому ряду причин:
- Без сервера сервера оба собеседника должны быть постоянно в сети и активно обмениватся пакетами присутвия. Это нереально, в особенности на мобильных устройствах, которые уходят в спячку
- Без сервера можно удобно провести целевые атаки на устройства пользователя. Я попробую пример привести: Вы заблокировали собеседника. Он решили Вам отомстить. Установив Ваш айпи, он устраивает атаку на ваш роутер и Вы лишаетесь интернета на день. Вы меняете айпи, но это не помогает, поскольку злойдей снова находит Вас по p2p. На следующий день Вам звонит Ваш провайдер и предлогает перейти на услуги для комерческих клиентов, потому что Вам нужна защита от Ддос-атак на 10К рублей. Вы покупаете это пакет, но злодей снова находит Вас, сканируют ваши порты и находит уязвимость в Виндовс и скачивает Ваши хоме фото, скан паспорта и т.д
Это лишь первые недостатки, которые пришли в голову. Можно еще много найти минусов. Я бы не назвал сервера ненужно прокладкой. Если нет сервера, то устройство выполняет роль сервера, что не всегда целесобразно.
SunTechnik
04.01.2019 22:40Потому что ОПСОСы предоставляют, почему-то именно IPv4 адрес. ( Посмотрите на своем телефоне). Состояние IPv6 достаточно странное: он давно есть, ни кроме гиков его почти никто не использует…
farcaller
04.01.2019 00:16Состояние IPv6 достаточно странное: он давно есть, ни кроме гиков его почти никто не использует
Verizon Wireless similarly reports that about 90% of its traffic uses IPv6
Однако же?
ValdikSS
04.01.2019 14:26Уже существует DTLS — TLS через UDP.
youROCK Автор
04.01.2019 14:31Да, но у DTLS тоже есть немаленький handshake, что в моём случае сводит весь профит от отсутствия хэндшейка в UDP на нет.
ValdikSS
04.01.2019 15:14Не думаю, что это принципиально, ведь соединение перед заходом в лифт будет уже установлено.
youROCK Автор
04.01.2019 15:29Не факт. Или предполагается «соединение» вообще не закрывать и сохранять сессионные ключи на диск, если приложение выгружено из памяти? Если так, то звучит интересно, конечно.
ValdikSS
04.01.2019 15:30Предполагается, что если у вас возникает желание написать кому-то, будучи в лифте, вы, с большой вероятностью, писали что-то кому-то до захода в лифт, а значит, сессия уже открыта.
youROCK Автор
04.01.2019 15:33Не обязательно. Мне мог придти пуш (вот там соединение и правда уже было открыто, поэтому он и пришел) и я хочу на него ответить. Хочется по-максимуму не завязываться на то, что соединение уже открыто, потому что когда оно открыто, то в целом уже всё работает достаточно хорошо.
youROCK Автор
04.01.2019 14:32Но важным преимуществом DTLS является его устойчивость к DDoS атакам с амплификацией, поэтому для каких-то вещей его точно можно использовать :).
miolini
04.01.2019 15:16Предлагаю в тестирование добавить еще кол-во syscall на 1 мбит/с потока данных, cpu load, pps.
youROCK Автор
04.01.2019 15:36Я ведь отправляю текстовые сообщения, поэтому там не будет большого PPS. Одно клиентское приложение во время активной отправки будет генерировать от силы с десяток PPS.
Если речь про сервер, то на одном ядре процессора реалистично обрабатывать до 50k pps, что будет примерно 25k pps на прием и 25k pps на отправку. Мне кажется, даже одного ядра без ухищрений вроде netmap хватит достаточно надолго :).miolini
04.01.2019 15:43Конкретно в Go UDP против TCP гораздо более скромные результаты в PPS. Основная причина в кратном увеличении кол-ве системных вызовов.
youROCK Автор
04.01.2019 16:26Кажется, при желании можно сильно улучшить производительность: go-review.googlesource.com/c/net/+/38275.
Пример использования, по идее, есть тут: godoc.org/golang.org/x/net/ipv4#PacketConn.ReadBatch
lixmix
04.01.2019 17:32-1XMPP сервера тоже могут работать через UPD. По умолчанию идут с TCP, но можно перекллючить протокол на UPD
gudvinr
03.01.2019 23:25Вы же понимаете, что в XMPP оверхед на одно сообщение гигантский, что объем трафика значительно увеличивает? Плюс, он архитектурно достаточно плохо переживает постоянные обрывы соединения и переключения на другой канал. А учитывая посредственную поддержку XMPP в клиентских библиотеках в настоящее время, придётся фактически заново изобретать протокол. Несмотря на то, что XEP, которые решают эту проблему, вообще говоря, есть.
netricks
04.01.2019 02:54Господа. Вы решаете не ту задача.
Дело не в мессенджере, а в том, что tcp становится плохо при хреновой связи. Это проблема сетевого и транспортного уровней, а не уровня приложения.
Получается, нужно выкинуть из головы всю хрень про шифрование, сервера и прочую лабуду и разработать аналог tcp, который будет нормально работать при 95% потерянных пакетов…
Drebin893
04.01.2019 16:36Я никогда не понимал, почему разработчики своих apps никогда не тестируют свои творения с плохой связью и не оптимизируют их для работы при плохой связи. Причем речь идет о топовых apps. Я говорю не только про мессенджеры.
FYR
04.01.2019 17:03Если для лабы то сойдет. Если для жизни и учесть все нюансы то вы сейчас изобретете недоTCP, который кстати и изобретался для надежной доставки в ненадежных сетях с высоким латенси и потерями, за счет пенальти по скорости и оверхедам по пакетам.
ZaEzzz
04.01.2019 21:17Как показывает практика, зачастую недоTCP лучше, чем TCP. Протокол не знает, какие вам данные нужны и будет гарантировать доставку и очередность всех пакетов. Таким образом вы одной картинкой сможете повесить прием сообщений всех пользователей с плохой связью в вашем чатике.
yetanotherman
04.01.2019 22:42Практика обычно показывает, что любой протокол нужно уметь готовить. REST без дополнительных ухищрений прверх TLS всегда будет плохо работать в нестабильной среде, но это не значит, что TCP плох. Готов поспорить, что разумное применение TCP будет в общем случае работать лучше, чем поделка на коленках на основе UDP. Есть хорошая книжка на эту тему — «Эффективное программирование TCP/IP» (Йон Снейдер). Она достаточно старая, но подробно рассматривает озвученную в статье проблему.
ZaEzzz
05.01.2019 10:41Готов поспорить, что разумное применение TCP будет в общем случае работать лучше, чем поделка на коленках на основе UDP.
В общем случае да, но статья рассматривает частный случай. В частных случаях больший контроль над трафиком будет иметь свои позитивные результаты.
Кроме того TCP имеет ряд недостатков, которые приводят к необходимости проверки целостности данных. То есть не смотря на то, что он как бы гарантирует доставку и целостность данных, но это не точно.
Особо интересно, когда TCP вреден. Если откинуть медиастриминг, туннелирование и реалтайм данные (к примеру, онлайн игры, биржи), то сходу можно найти проблему приоретизации данных в рамках одной сессии — TCP будет пытаться дослать вам данные, которые для приложения имеют меньший приоритет и вы никак на это не сможете повлиять. Конечно же можно для данных разного рода использовать отдельные соединения, но это не лучший выход из ситуации.
Есть хорошая книжка на эту тему — «Эффективное программирование TCP/IP» (Йон Снейдер). Она достаточно старая, но подробно рассматривает озвученную в статье проблему.
Книжка хорошо освещает вопрос в общих чертах и даже немного затрагивает достаточно частные случаи, но некоторые выводы и советы там вызывают вопросы. К примеру, в ней дается совет, что не надо изобретать TCP, но тут же сразу упоминается о том, что действительно есть отличные реализации на UDP.
Когда она вышла, были вопросы как они умудрились получить потери данных в рамках локалхоста, но в конечном итоге они написали пример «как написалось» — отсюда и потери.
Это мы приходим к вашему верному утверждению, что
любой протокол нужно уметь готовить.
Да, любой протокол нужно уметь готовить. Если в общих случаях TCP подойдет и лень заморачиваться с оберткой вокруг UDP, то используйте TCP и учитесь его готовить, но не ожидайте от него серебряную пулю и готовьтесь к внезапным неожиданным сбоям в продакшене. Для решения некоторых проблем можно попробовать SCTP, но это так же не гарантированный выход.
Кстати да, UDP так же не является гарантированно предпочтительным протоколом — не смотря на то, что он дает бОльшую свободу над контролем передаваемых данных, он так же обязывает больше за ними следить.
Для каждой задачи свои решения.
FYR
04.01.2019 23:37Конечно, если изобрести что-то очень очень узкозаточенное, причем и по функционалу и по условиям использования — есть шанс сделать лучше. Но это будет очень узко специализированное.
А например в настройках сетевого стека tcp у линукс 100500 всяких параметров. Там наверняка есть то что нужно, надо только найти нужное )))
ZaEzzz
05.01.2019 10:45Конечно, если изобрести что-то очень очень узкозаточенное, причем и по функционалу и по условиям использования — есть шанс сделать лучше. Но это будет очень узко специализированное.
Про общие случаи не пишут в статьях. Они обычно рассмотрены в различной учебной литературе.
Грубо говоря, если допускается результат работы при использовании общих подходов, то лучше их и использовать. В иных случаях нужно искать другие решения.
А например в настройках сетевого стека tcp у линукс 100500 всяких параметров. Там наверняка есть то что нужно, надо только найти нужное )))
Не уверен, что будет найден нужный ключик. К примеру, приоретизация данных в рамках одной сессии. Откуда операционной системе или протоколу знать что важнее в данных конкретного приложения?
И еще как-то странно будет, если мессенджер при установке будет менять параметры всего стэка.
VanquisherWinbringer
Оу, спасибо. Я ненавижу Go, просто как раз хотел что нибудь про UDP почитать.
gudvinr
Часть про то, что вы ненавидите Go, была обязательной, чтобы свои впечатления выразить? То есть, никак не обойтись без того, чтобы хейтить и просто "что-нибудь про UDP" почитать? Раз от статьи не воротит настолько, что решили почитать, значит не так уж и ненавидите, наверное.
К тому же вы у себя в профиле ставите этот язык как на котором разрабатываете (на втором месте, между прочим), и подписаны на хаб (наверное, чтобы поддерживать себя в тонусе).
Billar
мсье, вы бомбанули ярче сверх-новой, и что удивительно даже не по поводу своей статьи *удивление*
Сам же автор поступил немного более мудро, и не посмел обронить даже единого слова (!) подмечу: в отличии от меня с Вами. По итогу решаю обобщить нашу с Вами причастность к ейфорическому вмешательству тролля свыше, ибо его -4 вполне вероятно похоронят и нас, ведь Ваши +2 им на противовес поставлены, И МЕСТНЫЙ(АЯ) МОДЕРАТОР БОГ(ИНЯ) ФЕМИДА НЕ СМОЖЕТ ОСТАНОВИТЬ ЭТО, ведь мы абсолютно резонно ответили друг другу.
p.s. и что иронично изволить у меня и вовсе слишком мало кармы для голосования. знаете что бы я сделал? поставил обоим жирный минус за трату своего времени на объяснение этой несуразицы Вам
au revoir, синьоры
ZaEzzz
Я знаю отличную шутку про UDP, но не факт, что она до вас дойдет.
maximw
Повторю, что она до вас не дойдет.
farcaller
Повторю еще раз с увеличенной задержкой что-ли.
TimsTims
Как же я ненавижу UDP! Никогда не знаешь, дошел ли пакет, или нет, приходится повторять отправку.
TimsTims
Как же я ненавижу UDP! Никогда не знаешь, дошел ли пакет, или нет, приходится повторять отправку.
ZaEzzz
Я повторю вам шутку про TCP, если она до вас не дойдет, но не про UDP.
JekaMas
У вас с этим какие-то проблемы. Серьезно.