Привет, Хабр... Да, я опять забыл как называются местные пользователи (хабаровцы? хабровичане? хабровички?.. :), хотя и читал об этом совсем недавно. Бывает, в любом случае, на этот раз я предлагаю простую и достаточно элегантную идею из сетевого мира. И, прежде чем начнём, я оговорюсь, что язык программирования из прошлой статьи ушёл на покой, уступив место молодым и более перспективным наследникам.
Суть
Открывать сетевой канал между субъектами на сразу определённое количество времени либо пакетов с возможностью продления на указываемое число?
Цитата выше — это моя записка на телефоне, которая описывает основополагающую мысль. Представьте, стандартную ситуацию в данном контексте: клиент отправляет серверу запрос на создание соединения и тот соглашается:
Client: OpenConnection<10 second>
Server: OpenConnection<okey>
Client: OpenConnection<5 packet>
Server: OpenConnection<okey>
Client: OpenConnection<2 hour, 1 packet>
Server: OpenConnection<okey>
Предлагаемые время жизни и количество исходящих пакетов. Всё просто, согласитесь? Также, нужно сразу представить продление подключения:
Client: ExtendConnection<128 minute>
Server: ExtendConnection<okey>
Client: ExtendConnection<18 packet>
Server: ExtendConnection<okey>
Client: ExtendConnection<8 minute, 21 packet>
Server: ExtendConnection<okey>
Поясню, что после завершения обозначенного времени либо при отсутствии его обозначения, вступает в дело счётчик оставшихся пакетов — когда он приравнивается нулю, подключение сразу считается безвозвратно закрытым. Кстати, время отсчитывается от момента отправления пакета-подтверждения у отправителя и от момента получения этого пакета у получателя, а счётчик индивидуален, обозначая входящий лимит-требование на количество. То есть:
Client: OpenConnection<1 packet>
Server: OpenConnection<okey>
Client: DataPacket
// Всё, дальше подключение молча умерло.
Client: OpenConnection<15 minute>
Server: OpenConnection<okey>
// Спустя пятнадцать минут, подключение также молча умрёт.
Client: OpenConnection<15 minute, 1 packet>
Server: OpenConnection<okey>
// Спустя пятнадцать минут, подключение будет жить, ведь есть ещё счётчик.
// Теперь сервер ждёт один пакет от клиента.
Server: ExtendConnection<1 packet>
Client: ExtendConnection<okey>
// А теперь, ещё и клиент ждёт от сервера один пакет.
Client: DataPacket
Server: DataPacket
// Подключение ликвидировано...
Думаю, вы уже заметили, что OpenConnection и ExtendConnection требуют соответствующего пакета-ответа, а DataPacket — нет. Конечно же, может быть не только согласие, но и отказ:
Client: OpenConnection<1000000 day>
Server: OpenConnection<bad, connection lifetime is too long>
// Конец, увы ;)
Сразу тогда сообщу, что есть ещё EndPacket, который нужен лишь формально — чтобы обозначить, что больше пакетов твоя сторона не будет отправлять. Иначе говоря, его использование не обязательно, и он нужен лишь для подобного трюка:
Client: OpenConnection<30 minute>
Server: OpenConnection<okey>, ExtendConnection<1 packet>
Client: ExtendConnection<okey>
// Спустя тридцать минут, когда сервер это обнаружил.
Server: EndPacket
// Подключение считается завершённым с обеих сторон.
Обратите внимание, что отправка пакета после завершения, по той или иной причине (например, из-за разницы во времени жизни подключения у клиента и сервера, так как была задержка между отправкой и получением OpenConnection<okey>), подключения у противоположной стороны является полностью корректным поведением, но тогда этот пакет будет обработан получателем как пакет без подключения. Да, можно просто отправлять пакеты:
Client: DataPacket
Формально, первый пакет подключения (OpenConnection) также отправляется одиночным образом. Предупрежу, что нельзя в подключении открыть подключение.
Ах да, теперь самое важное: как следствие, в протоколе нет разделения на клиент-сервер, только субъективное отправитель-получатель (Sender и Receiver).
Пояснения
Для тех кто назовёт это слишком сложным или потребует код, я предлагаю простой алгоритм:
Receiver: Ожидаем входящих пакетов...
Sender: Отправляет пакет EndPacket получателю.
Receiver: Получет пакет от отправителя.
Receiver: Проверяет есть ли у него запись со связкой IP адрес-порт отправителя.
Receiver: Если нет, то просто обрабатываем одиночный пакет (например, игнорируем все кроме OpenConnection от определённого диапазона адресов), иначе продолжаем:
Receiver: Проверяем timestamp (метку времени; например, 09.10.2025 12:23:56) окончания подключения.
Receiver: Если она равна нулю (условное обозначение отсутствия) или уже была пройдена (например, сейчас 09.10.2025 12:24:06), то удаляем запись и обрабатываем как одиночный пакет, иначе продолжаем:
Receiver: Проверяем входящий счётчик-лимитёр пакетов.
Receiver: Если он равен нулю, то удаляем запись и обрабатываем как одиночный пакет, иначе обрабатываем как пакет из соответствующего подключения.
И, конечно же, небольшой кусочек кода на Си:
// Напиши потом, а то сейчас лень писать эту мелочь :)
// Да и спать пора, имей совесть, Ваня...
P.s. Почему Хабр даёт выбрать лишь C++ как язык кода?..
Безусловно, нужно добавить ещё подобную проверку, но для всех записей подключений, например, в потоке для приёма/отправки пакетов, основном потоке ОС или ещё где-нибудь.
В остальном... Я представляю это как простой идентификатор в виде двухбитного big-endian числа:
0x00 — EndPacket
0x01 — DataPacket
0x10 — OpenConnection
0x11 — ExtendConnection
Такого же типа пакета, но только у последних двух:
0x00 — Отказ
0x01 — Согласие
0x10 — Зарезервировано
0x11 — Запрос
И наконец, 32-битным неотрицательным, целым числом обозначающим длину тела-содержимого-нагрузки (body/content/payload) и самим упомянутым. Нет, у EndPacket он также присутствует, но я предлагаю использовать какое-нибудь магическую строку или адрес-порт отправителя/получателя. Эквивалентно у Open- и Extend- Connection пакетов в Согласии, в Отказе — сообщение об ошибке (возможно, с её кодом в начале?) и одним/двумя аргументами в Запросе.
Что же насчёт оставшейся структуры, то во-первых, нужно этот протокол обернуть в IP, а во-вторых, я сам понятия не имею... Вот так как-то. Безответственно, но как есть ;-)
Заключение
Лично я считаю, что этот протокола, даже под другим названием, может стать официальным мировым интернет-стандартом из-за своих локаничности и детерминированности... который сможет в итоговом варианте послужить серьёзной альтернативой существующим TCP, UDP и подобным... Увидим, когда подам соответствующий документ в IETF и пройду через тернии к звёздам (ха-ха :). В любом случае, я ожидаю здесь, к статье на Хабре, комментариев с критикой и предложениями. Спасибо за чтение? Не?
Комментарии (21)
kipar
09.10.2025 10:12Он поверх TCP работает? Если нет то как будут разруливаться потери\дублирование\изменение порядка пакетов?
HardlinePeak936 Автор
09.10.2025 10:12Нет, сразу поверх IP. Читайте внимательней :)
kipar
09.10.2025 10:12Я так и предположил, но что тогда по второму вопросу?
Если открыли соединение на 2 пакета, потом клиент отправил 1 пакет а он пришел на сервер дважды - соединение закроется? А если пакет потерялся - соединение наоборот будет висеть до перезагрузки сервера? Что если потеряется EndPacket\OpenConnection\ExtendConnection - во всех случаях будут либо висящие соединения либо прием пакетов без соединения? Тогда я не понимаю чем это лучше UDP.
garwall
09.10.2025 10:12Дело осталось за малым - представить, что будет с дропом, джиттер, реордером, изменением источника в сессии, нужна ли абстракция, подобная порту, или хватит только абонента, ну и сущая мелочь, убедить хуавеи и циски, что им это надо/
JBFW
09.10.2025 10:12Ну теоретически, с учётом тенденции к запретам и регулировании по всему миру - софтовый роутер, строящий такую сеть, мог бы быть востребован, а при успехе и железячники подтянутся.
Если оно того будет стоить - и вот это совсем другой вопрос
Storm54
09.10.2025 10:12Неплохо было бы почитать про особенности работы компьютерных сетей, чтобы такую наивную дичь не постить.
В текущем виде (особенно с подсчетом пакетов) совершенно непонятно, как протокол должен работать с учетом переотправки потерянных пакетов.
Также, при таком дизайне можно заддосить сервер, открыв множество соединений на длительное время.
HardlinePeak936 Автор
09.10.2025 10:12Не понимаю, что вам непонятно в переотправке потерянных пакетов: по-умолчанию нет никаких механизмов на этот случай, но если вам необходимо, то можно элементарно присылать DataPacket в ответ, разве нет?
Что же насчёт ддоса, то он возможен и в TCP, и в UDP, так как решается не на этом уровне и достаточно просто: ты можешь отбраковывать неугодные тебе одиночные пакеты (в том числе, и на открытие соединения). Даже по причине подозрительной активности у отправителя...
Кстати, а что наивного-то? :)
Storm54
09.10.2025 10:12Поэтому и стоит сначала ознакомиться со спецификацией TCP и понять, какие проблемы этот протокол решает, прежде чем выдумывать свой протокол, который просто не будет работать в реальных условиях.
В этом и заключается Ваша наивность - эффект Даннинга - Крюгера.
Vindicar
09.10.2025 10:12Не нашел в статье ответа на вопросы:
в каком сценарии использования наличие заранее заданных ограничений по времени/числу пакетов будет полезно?
как планируется выбирать эти ограничения с учётом нестабильной работы сети?
vadimr
09.10.2025 10:12Я тоже не понял главным образом, зачем это нужно. Поддержка таблицы соединений стоит денег, зачем это, если цель – просто передавать пакеты?
HardlinePeak936 Автор
09.10.2025 10:12Насчёт таблицы соединений — осмелюсь заметить, что даже для UDP она поддерживается, просто самими провайдерами и в, так сказать, корыстных целях :)
Соглашусь, что этот протокол залез ещё и на уровне выше (уровень сессий)... но в данном случае — это достаточно обосновано и просто, идя как лёгкий довесок. Нет/да?
HardlinePeak936 Автор
09.10.2025 10:12Всё достаточно элементарно по вашим вопросам:
В любом. Это как сервисные пакеты для проверки соединения в том же TCP, но ты сразу и чётко знаешь сколько их будет или как долго будет соединение. Можете воспринимать это как контракты, например.
Методом научного тыка?.. Не уверен, честно говоря, всё-таки представлял это как: открыл соединение на N минут (предположим, N=5), через N-1 расширил соединение ещё на N, если необходимо, и так на протяжении всей работы. Можно даже производить простенькие вычисления, чтобы не накапливался остаток.
Я ответил?
Vindicar
09.10.2025 10:12Не вижу сходства. Сервисные пакеты отправляются строго по мере надобности.
Ключевой момент именно в этом: на транспортном уровне мы не знаем, что, сколько и как долго собирается передавать приложение. В лучшем случае эта информация будет доступна на уровне протокола самого приложения, подобно Keep-Alive в HTTP. И даже там она передаётся "по факту", без попыток угадать заранее.
А угадать будет непросто. Сеть подтормаживает, и наша прикидка о длительности сессии оказалась некорректной. Соединение разорвётся, несмотря на работоспособность сети? Пользователю это не понравится.
Далее, а что подразумевается под пакетом? IP пакет? Тогда сразу встаёт вопрос: как быть с фрагментацией пакетов? Фрагмент считается за пакет? Как быть с повторной отправкой?
И это все при условии что приложение вообще знает, сколько точно данных и как долго оно будет отправлять. Я вообще навскидку затрудняюсь назвать такой сценарий, кроме разве что передачи файла заранее известного размера. Так это всего один сценарий, из-за чего огород-то городить?
Конечный итог будет простой: приложение будет брать параметры с бааальшим запасом. И тогда ваш протокол мало чем будет отличаться от того же TCP.
llllXl
09.10.2025 10:12Как будто бы надёжности в этом протоколе нет, да и подделать пакеты третьей стороной в сессии не представляется сложным. Будущее конечно за оптимизацией, но к сожалению не в мире сетевых технологий)
HardlinePeak936 Автор
09.10.2025 10:12Действительно, насчёт механизмов против подделки не было упомянуто, так как они отсутствуют, но их достаточно просто реализовать (как пример: подписью и её проверкой поверх протокола). Возможно, можно сразу учесть и обозначить этот функционал в протоколе... Но я не совсем уверен, стоит ли.
А вот оптимизации нужны и будут даже в сетевых технологиях, вопрос в том какие, как и когда. Разве нет? ;)
ArPi
09.10.2025 10:12Забыли упомянуть какой из фатальных недостатков имеющихся протоколов ваш решает.
Так сказать, сравнительный анализ провести.
HardlinePeak936 Автор
09.10.2025 10:12Фатальных? Скорее всего, никакой. Увы, но это просто нечто вроде оптимизации (приводя аналогию, замена неумелого бумажного самолётика на сделанный более аккуратно и ровно ;)
Nemoumbra
09.10.2025 10:12Статья представляет из себя большой мысленный эксперимент. Надо бы всё-таки чё-нибудь написать. Причём на Си я бы не советовал. Возьмите библиотеку scapy на Питоне, опишите её средствами структуру пакетов, с её помощью сбилдите несколько примеров сессий, сохраните в PCAP. Потом можно будет добавить поддержку в Wireshark через Lua-скрипт, чтобы можно было посмотреть на это в удобной форме.
К моменту, как всё это сделаете, возможно, у вас появится понимание проблем протокола и, возможно, идеи, как это чинить.
Kin77
09.10.2025 10:12Очень нравится читать подобные неопытные наброски, т.к. я также неопытен и интересно как разбирают в комментариях. Благодарю!
HardlinePeak936 Автор
Вне хабра проскочил вопрос «передачи параметров времени и лимита пакетов. Где в OpenConnection указываются те самые 10 second?» Я думал, что это и так понятно, но, на всякий случай, поясню тут и пойду спать (:
<packet id><type id><first arg — time><second arg — count> — всё...
А как кодировать? Можете предложить варианты!