Обновить
19

Пользователь

9
Подписчики
Отправить сообщение

Тему с UB я разложил ниже, но вы упомянули переполнения чисел то это иная проблема в C/C++ которая ортогональна UB. На самом деле ошибкой в дизайне языков C и C++ является ограниченность и неконсистетность числовых типов (для знаковых чисел переполнение это UB а для беззнаковых это wrap-around). Дело в том что числовые типы помимо размера и знаковости на самом деле бывают как минимум 4 типов

1) когда переполнение вызывает эффект wrap-around 

2) когда переполнение вызывает эффект saturation

3) когда переполнение вызывает эффект паники (с помощью дополнительных рантайм-проверок) либо ошибки компиляции (если на этапе компиляции компилятору удалось статически проанализировать и доказать переполнение действительно произойдет используя data-flow/range-analysis/symbolic-execution/etc) 

4) когда переполнение вызывает эффект UB. Точнее разработчик хочет чтобы был эффект паники в рантайме но из-за дороговизны рантайм-чеков разработчику приходится заниматься микрооптимизациями и либо специальным типом либо другими средствами сообщать компилятору не вставлять рантайм-проверку на переполнение так у него есть дополнительная информация о природе данных (которая недоступна статическому анализатору компилятора) или об особенностях компиляции и оптимизаций конкретно этого компилятора

И если глубоко обдумать эти четыре типа чисел и возможные дефолты для какого-то нового языка то можно прийти к выводу что числа с типом 4 (которые в C/C++ по дефолту для знаковых типов) на самом деле не должны быть дефолтом в языке поскольку этот тип является вынужденным - разработчик на самом деле хочет рантайм-чеков и паники но из-за тормозов вынужден заниматься микрооптимизациями. Соотвественно если мы хотим сделать язык безопасным то правильнее было бы выбрать другие дефолты потому что далеко не всегда, а точнее в >95% случаев при написании приложений и серверов, судя по популярности других интерпретируемых языков, нам не нужны эти микрооптимизации и логично сделать язык с безопасностью по умолчанию и возможность оптимизации по запросу но только после профайлинга и индетификации проблемы (когда рантайм-чек переполнения становится боттлнеком)

Также надо отметить что брать по дефолту тип с эффектом wrap-around (который по дефолту для беззнаковых типов в C/C++) тоже преступление потому что этот тип нужен исключительно тогда когда разработчику нужна модульная арифметика. А нужна она в исключительно редких случаях - в криптографии, в качестве индекса для кольцевого буфера и еще может быть в парочке случаев. А попытка использовать этот тип для случаев когда модульная арифметика не нужна будет только скрывать логические ошибки переполнения (которые могли бы быть пойманы компилятором)

По итогу в контексте вышесказанного у C/C++ дважды неправильные дефолты для числовых типов и единственное что радует так это то что C/C++ здесь не одинок и в других языках тоже неправильные дефолты (когда по дефолту wrap-around)

Похоже ни автор статьи ни все комментирующие не понимают сути проблемы с UB в C/C++. Что ж, попробую объяснить весь расклад. Смотрите, проблема не в оптимизациях комплиятора (я наборот считаю что они недостаточно агреcсивны) - проблема в том что

1) сначала стандарт С++ (в си аналогично) разрешил имплементациям (они же компиляторы) - что в случае наступления любой опасной ситуации (которая попадает в список UB) компилятору разрешается не аварийно завершить работу программы (как единственное логичное действие) а тупо продолжить выполнение программы в неконсистетном режиме с нарушением всех возможных логических инвариантов, проверок и т.д. Вы только вдумайтесь насколько дикая ситуация если подумать. Вот определение UB из стандарта C++

3.65 [defns.undefined] undefined behavior behavior for which this document imposes no requirements [Note 1 to entry: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression (7.7) never exhibits behavior explicitly specified as undefined in Clause 4 through Clause 15. — end note]

Перечитайте еще раз эту фразу

Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message)

и вдумайтесь - стандарт не обязывает от реализаций останавливать программу в случае возникновения UB, - он такой говорит - "ну вы типа можете выдать диагностику и остановить программу на этапе компиляции или в рантайме (и на том “спасибо”) но вообще-то чтобы получить лычку “standard-conforming” в случае возникновения UB вам как компилятору (который обеспечивает рантайм и отвественнен за выполнение) разрешается тихо проглотить это и продолжить выполнение в неконсистетном и непуправляемом состоянии с любыми возможными нарушениями". Разве это не дико? Я бы понял бы если бы стандарт разрешал бы подобную ситуацию в других режимах а но для получения лычки “standard-conforming” в обязательном порядке требовал бы от компилятора иметь опцию или режим в котором компилятор пусть ценой любого возможного замедления (путем вставки рантайм-проверок или вообще интерпретации) но все же гарантировал бы что в случае возникновения UB программа просто упадет но ни в коем случае не продолжит свое исполнение в неконсистеном режиме с разносом нарушающие все возможные инварианты

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

2) а потом компиляторы такие - “ага, ну раз разрешено в момент наступления UB делать что угодно, ну так давайте оптимизировать так как будто никаких UB нет” и это привело к ситуации что если UB происходит то программа действительно начинает выполняться в неконсистетном режиме из-за вырезанных if-ов и других инвариантов из кода и дальше я мог бы поискать список из несколько десятков статей на хабре с описанием этих ужасов которые происходят результате такой политики компиляторов, но мне лень :)

При чем тут надо провести четкое разделение - я не против самих оптимизаций, я наборот считаю что компиляторы еще недостаточно агрессивно оптимизируют код. Я просто считаю что компиляторы вместо того чтобы здраво смотреть на вещи они приняли за мантру "если стандарт разрешает вместо аварийной остановки тихо выполнять программу в неконсистетном режиме, так давайте это делать даже не предоставляя иной опции” вместо того чтобы сказать, мол “знаете, мы не согласны с такой позицией и вот вам режим в котором компилятор будет гарантировать что в момент UB программа упадет но точно не продолжит свое выполнение в неконсистетном режиме”
И дальше что касается оптимизаций, то смотрите какая интересная штука получается - как только включается этот режим то дальше компилятор встроив рантайм проверку (например проверка переполнения, проверка выхода за пределы массива и т.д с аварийным завершением работы и выдачей диагностики) может проводить самые агрессивные оптимизации которые только возможны и если он смог статически доказать (после whole-program анализа используя самые передовые data-flow, range-analysis, symbolic-execution и т.д техники) что заход в такой-то if невозможен то тем самым он сможет вполне безопасно вырезать эту рантайм проверку и тем самым ускорить программу.

Ну а если юзер использует раздельную компиляцию и не хочет предоставлять все исходники всех зависимых библиотек или если они закрыты (но слава богу в 2026 году это редкость и можно вполне строить проекты используя библиотеки или зависимости которые имеют на 100% открытые исходники) или если юзер не хочет ждать несколько часов (но по идее инкрементальный режим с анализом на каждое нажатие клавиши и ввода кода должен значительно ускорить анализ) - ну тогда естественно возможностей доказать что-то статически у компилятора значительно поуменьшатся и юзер соотвественно получит замедление кода.

Но главное это выбор, разве не так? Главное это возможность при необходимости таки обдумать трейд-оффы, понять чего мы лишаемся, понять чего нам стоит внедрить то или другое, может не так уж и сложно настроить whole-world сборку проекта полностью из исходников всех зависимостей (особенно если код этих зависимостей и так открыт) чтобы компилятор в момент статического анализа мог заглянуть в тело абсолютно любой вызываемой функции и т.д. Самое главное это выбор. А текущая политика компиляторов этого выбора юзеру не дает и не предоставляет режима при котором пусть с замедлением но все же будет гарантироваться остановка программы в момент UB вместо тихого продолжения выполнения в неконсистетном режиме

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

Ну если большинство с этим согласно то тогда всё ок, а если нет то тогда соотвественно такое решение неприемлемо (отвечая на комментарий выше). То есть - "Любое решение приемлемо, если с ним согласно большинство, кроме закрытых границ". И если какое-то государство решило закрыть границы отбирая последнюю возможность "проголосовать ногами" у того меньшинства которое не согласно с тем что творит государство (представляя волю большинства) то тогда я считаю что такое государство не должно иметь право на существование в 21 веке (в принципе и раньше не должно но в 21 веке особенно)

Любое решение приемлемо, если с ним согласно большинство

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

Например, эффективная долговременная память -- это грааль современного ИИ: все пытаются реализовать достаточно обширную постоянную память, да так, чтобы модельку не переучивать заново, не сталкиваться с катастрофическим забыванием и контекст не терять

Так это давно уже решили. Просто это решение не удовлетворяет по ресурсам поэтому никакие облачные LLM это не поддерживают. Что это за решение? Подумайте о том что после обучения на википедии и на терабайтных объемах спираченных как научных так и художественных книг LLM помнит и не забывает огромное количество фактов. Разве это не удивительно? Вот вам и решение - на каждый диалог юзера с LLM взять и скопировать все веса и начать в процессе диалога проводить дообучение. Таким образом получим практически бесконечный контекст (десятки и сотни миллионы токенов) и долговременную память которая сравнима с той изначальной памятью LLM об общеизвестных фактах после обучения. Единственный минус - это необходимость для каждого юзера на каждый (!) диалог с LLM скопировать (и потом дообучать в процессе диалога) 1 терабайт весов (примерно столько весят последние модели Gemini/ChatGPT/Grok/Claude)

5. клиент запрашивает id последнего, получает 6 или 7, для дальнейшего некритично

Этого шага нет и остальное тоже теряет смысл. Я под фразой "он должен в первую очередь загрузить этот айдишник" подразумевал что это сервер должен загрузить из бд айдишник (он же номер последнего обработанного запроса) а не клиент. То есть когда клиент создает новое соединение к серверу и дальше отправляет по нему свои запросы то он должен нумеровать эти запросы сквозным образом (то есть счетчик запроса не должен сбрасываться при разыве связи и установки нового соединения) а сервер при обработке каждого запроса должен сохранять в бд соответствие "клиент -> номер последнего обработанного запроса" а перед обработкой проверить чтобы номер присланного запроса был строго больше чем номер последнего обработанного запроса

Итого. Даже если в эту схему вводить нюансы типа "это номер обработанного запроса или еще только полученног", всё равно дубликаты будут.

Не будут

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

Сложности в виде защиты от reply атак возникают не из-за нулевого рандтрипа а из-за отсуствия долговременного хранения этого счетчика (которого вы передаете с каждым сообщением) на сервере и на клиенте. Вот у вашего протокола уже есть такая защита но внутри установленного соединения. А что происходит если у пользователя на некоторое время отключается интернет? Если соединение разрывавется (например из-за таймаута) то повторная попытка установить новое соединение с повторной отправкой сообщения как раз и приведет к replay багам. И решением этой проблемы как раз и будет долговременное хранение как на сервере так и на клиенте либо этого счетчика либо вообще контекст всего соединения

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

А почему бы просто не установить на сервере таймаут соединения в бесконечность (если там есть этот таймаут) а дальше на клиенте просто сохранять на диск контекст соединения и таким образом никогда не разрывать соединение? То есть даже если юзер выходит из приложения то при следующем запуске приложение просто загрузит контекст соединения из диска и просто продолжит отправлять и получать данные по этому уже установленному соединению.

который должен значительно сократить время повторного подключения

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

А запекание ephemeral ключа приведет к проблемам с PFS, а в данном случае для меня безопасность приоритетнее скорости

Если мы запекаем в бинарнике приложения ephemeral ключ то на сервере нужно будет хранить приватную часть этого ключа не все время а только до выхода новой версии приложения (а если приложение активно развивается то выход минорной версии происходит обычно не реже чем раз в месяц). Это конечно не идеальный perfect-forward-security но уже лучше чем в протоколе TLS 1.2 когда часто использовалась схема с использованием RSA и публичного ключа из сертификата для дальнейшей передачи симметричного ключа шифрования - там действительно утечка приватного ключа сертификата позволит расшифровать все перехваченные ранее сообщения за все время действия сертификата (год и больше) и поэтому в TLS 1.3 полностью отказались от этой варианта и оставили только схемы c согласованием ключа через DHE или ECDHE (diffie-hellman и аналог на эллиптических кривых).

Вообще "идеального" PFS не сущестсвует так как это всегда trade-off между удобством для пользователя (отсутствие необходимости делать полноценный хендшейк на каждый чих) и безопасностью. Вот скажем ради безопасности вы решили что зашивать ephemeral ключ в бинарнике приложения (с необходимостью хранить приватную часть на сервере до выхода новой версии приложения) это небезопасно с точки зрения PFS поэтому вы решаете генерировать приватную часть и согласовывать новый ключ с каждым соединением. Но проблема в том что если вы решаете организовать общение клиента с сервером не через установку нового соединения на каждый запрос к серверу а через установку одного долгоживущего соединения (с обменом сообщений внутри этого соединения) то это уже в какой-то степени жертвование безопасностью и уход от PFS. А если вы решаете добавить механизм возобновления сессии (ну или же мое предложение в виде сохранения контекста соединения на диск) то это еще больший уход и пожертвование perfect-forward-security ради увеличения эффективности и удобства.

В общем для себя я решил что проблемы PFS несколько преувеличены и по сути являются сомнительной (на мой взгляд) попыткой сгладить потенциальный взлом сервера (а иначе как этот долговременный приватный ключ может утечь?) и я такой себе дальше думаю - если уж произошел такой взлом с утечкой приватного ключа то мне кажется что возможность расшифровать перехваченный ранее обмен сообщениями это будет не самая большая проблема в сравнении с потенциальным доступом (в случае взлома) ко всем данным пользователей (то есть ко всему что этот сервер может хранить или обрабатывать)

// 3. Подписываем свой эфемерный публичный ключ своим долгосрочным приватным ключом.

// Это доказательство для клиента, что мы — именно тот сервер, за кого себя выдаем.

Мне кажется или здесь зарыта дыра в вашем алгоритме? Что мешает "человеку посередине" перехватить это сообщение а потом выдать себя за сервер просто отправив эту копию? Приватного ключа у него нет но он ему и не нужен так как он просто отправит тот же эфемерный публичный ключ который был сформирован сервером и прикрепит ту же самую подпись и таким образом успешно выдаст себя за сервера. А чтобы предотвратить такую возможность нужно чтобы сервер подписал/зашифровал своим приватным ключом не свой эфемерный ключ а некое рандомное число которое клиент сгенерирует и пришлет серверу в своем первом сообщении (и дальше клиент получив ответ расшифрует число публичным ключом сервера и проверит совпадает ли оно с тем числом которое клиент отправил)

В Eppie каждое клиентское приложение это нода в p2p-сети. Каждый пользователь автоматически участвует в хранении и транспортировке данных.

Очень интересно узнать как вы собираетесь обеспечивать этот инвариант. Например что помешает пользователю форкнуть исходники этой ноды и изменить сорцы так чтобы можно было отправить письмо без участия в хранении и транспортировке? В децентрализированных приложениях основанных на криптовалютах такое же почтовое приложение будет работать таким образом что отправка письма потребует оплаты этой криптовалютой которую можно заработать как раз и обеспечивая работу этой сети. Получается устойчивая замкнутая экосистема. А как будет обеспечиваться устойчивость работы в вашей сети непонятно. Можно предположить что для отправки письма пользователю нужно будет пропустить через себя н мегабайт трафика и продержать данные н дней но только непонятно как вы собираетесь обеспечить это на уровне алгоритма консенсуса то есть чтобы даже измененные исходники ноды со стороны юзера не позволили ему обмануть эти правила (то есть чтобы другие ноды не принимали отправленное письмо)

Есть несколько мыслей по оптимизации вашей схемы. У вас получается что клиент сможет отправить первый байты полезных данных (например имя api endpoint-a) только после полного раундтрипа? А что насчет сервера? Если сервер вычислил ключ шифрования уже на первом upd-пакете от клиента то значит он имеет теоретическую возможность отправить зашифрованные данные тут же не дожидаясь запроса от клиента, правильно? То есть сервер генерирует "ephemeral server public key" записывает его в udp-пакет который предназначен клиенту и тут же внутри этого же udp пакета следом записывает уже зашифрованные данные и тогда клиент получив кей-шару от сервера вычисляет симметрический ключ шифрования и дальше сможет расшифровать уже полученные данные от сервера.

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

И дальше появляются мысли как можно еще сильнее зарыться в оптимизацию сетевых раундтрипов - если имя api-ручки сервера не является таким уж и секретным то клиент может включить это имя (в незашифрованном виде) в тот свой первый udp-пакет и тогда сервер сможет выполнить полученный запрос и отправить уже зашифрованные данные клиенту уже в том первом udp-пакете который клиент получит от сервера.
И кстати тут же напрашивается идея что можно сэкономить драгоценное место в upd-пакете (1472 байт) и закодировать имя api-ручки в номере udp-порта - то есть сервер будет слушать не один порт а например тысячу сетевых портов по одному на каждую api-ручку, правда для этого нужно чтобы это зашифрованное соединение не было привязано не только к ip-аддресу и порту клиента (что в принципе и так является хорошей практикой чтобы например избежать установки нового соединения при смене клиентом wi-fi сети на мобильную сеть и наоборот) но и к порту сервера.

Ну и наконец появляется такая идея что этот "ephemeral server public key" можно вообще включить в бинарник приложения и тогда клиент сможет вычислить ключ для симметричного шифрования вообще без общения с сервером и тогда он сможет в своем первом upd-пакете (включив сначала свой "ephemeral public key" ) отправить серверу в зашифрованном виде как имя api-endpoint-а так и соответствующие данные.

Ещё лет 5 назад на собеседованиях с backend-разработчиками был популярен вопрос: как переименовать колонку в высоконагруженной таблице (возможны вариации: как сделать любой другой alter table)? Ответ мог быть примерно следующий:

Зачем такие сложности с создаем новой колонки и постепенной миграцией? Почему бы просто не задать всем колонкам уникальное имя которое никогда не захочется поменять (случайный айдишник) а уже в коде хранить соответствие имя -> id при работе с этой бд. Тогда переименование колонки сведется к простейшей операции замены одного имени на другое в коде вообще без каких-либо затрат со стороны бд

вы только представляете а кто-то уже так делает и зарабатывает миллионы — вот один комментарий на HN который недавно набрал популярность в твиттере https://news.ycombinator.com/item?id=27454589


I currently have 10 fully remote engineering jobs. The bar is so low, oversight is non-existent, and everyone is so forgiving for under performance I can coast about 4-8 weeks before a given job fires me. Currently on a $1.5M run-rate for comp this year. And the interviewing process is so much faster today, companies are desperate, it takes me 2-3hrs of total effort to land a new job with thousands to chose from.

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


<div>
  ...
  <button onClick={()=> {
    project.tasks.push(new Task({text: project.newTaskText});
    project.newTaskText = "";
    ....
  }}>
      add task
  </button>
  ...
</div>

Тут можно сказать мол "да ты захламляешь шаблон как такое вообще читать и поддерживать когда мол верстка в перемешку с логикой?" на что я отвечу "а code folding (сворачивание) в редакторе кода для кого придумали?" Если хотите видеть/редактировать только верстку то можно легко свернуть весь код обработчиков и увидеть чистый шаблон. И наоборот — если хотите посмотреть что делает какая-то кнопка то перемещаем мышку влево на пару сантиметров и кликаем-раскрываем обработчик кнопки.


Это намного удобнее чем переходить в отдельный файл или скроллить экран вверх-вниз (когда выносим обработчик функции в другое место). Это также удобно в поддержке — если допустим у нас поменялся дизайн/ux/логика и поменялась верстка в результате которой нужно удалить какой-то блок вместе с кнопкой то я удаляю нужный кусок верстки и вместе с этим удалится и обработчик этой кнопки поскольку он записан инлайном — мне не нужно беспокоится а не удалил/почистил ли я различные обработчики в другом файле или других местах по коду


И то же самое с добавлением какой-то фичи — я пишу кнопку и тут же пишу обработчик этой кнопки рядом и мне не нужно думать над тем куда вынести этот обработчик а главное — какое имя придумать этому обработчику. И также это удобно при ревью кода — когда добавляется какая-то фича то в git diff видишь изменения только в одном месте а не размазаны по кусочкам на кучу файлов/мест (верстка в одном месте, обработчик в другом месте и т.д)

Как вебсокеты могут быть более тяжёлой штукой чем http-запросы? Вы понимаете как работает http? HTTP-запрос это точно такое же тсп-соединение как и вебсокеты и ничем не легче по "тяжести". И если интернет-соединение нестабильное то http-запрос будет точно также обрываться с ошибкой как и вебсокет-соединение (и соотвественно нужно делать повторный запрос/установку соединения), в этом плане я вообще не вижу никакой разницы.


А вот во многих моментах http намного хуже и имеет больший оверхед.
Во-первых если не использовать keep-alive то браузер будет создавать тсп-соединение для каждого http-запроса и здесь http выходит намного тяжелее.
Во-вторых передача статуса и хедеров в текстовый формате для каждого запроса в итоге имеет больший размер и оверхед и это особенно видно при передаче небольших порций данных (с вебсокетами это может быть десяток байт когда же с http запрос будет занимать от сотни байт)
В-третих с http у браузеров есть ограничение на количество одновременных запросов (чего нет с вебсокетами).
В-четвертых проблема идемпотентности с http решается намного сложнее чем с вебсокетами (с http порядок запросов не гарантируются и они на сервер могут прийти в другом порядке чем были отправлены из клиента, когда же с вебсокетами у нас есть строгий порядок сообщений)


Ну и наконец вебсокеты это очень простая спецификация (по сути хедер с размером сообщения поверх тсп-протокола) и написать парсер вебсокетов можно за пару часов (формат фрейма объясняют даже для новичков) в то время как написать парсер http (не говоря уже про http2) это приключение на месяц учитываю кучу разнообразных нюансов самого текстового http формата, так и специфику корректно обработки различных хедеров (в частности keep-alive)


А все эти REST/RPC PUT/DELETE/OPTIONS/GET/POST и прочее это просто уровни абстракции придуманные самими разработчиками для удобства. Если писать большое приложение на WebSocket-ах вы придумаете свои такие же.

Нет, такого беспорядка и дилеммы выбора как с http не будет, зачем? Зачем нужны эти имена методов и статусы кодов ошибок если можно просто передать скажем объект {action: "createProject", params: [...]} и вернуть объект {error: "..." | null, data: "..." | null} и вебсокет-обработчик на сервере просто вызовет нужную функцию и уже в самой функции будет происходит вся необходимая обработка — будь то получение (GET) или обновление данных (POST, PUT) или удаление (DELETE) или когда все эти действия происходят одновременно вместе с if-ами и циклами для батч-обработки каких-то данных (что просто не имеет соответствия "семантическим" методам в случае http). То есть исходная задача — вызывать какую-то процедуру на сервере и передать ей нужные параметры чтобы она обработала "запрос" и получила/обновила нужные данные в бд и вернула ответ назад с вебсокетами решается на порядок проще чем с http (где вызов нужной процедуры почему-то додумались делать через ручное придумывание и конфигурацию имен для роутов с маппингом на path/query http-запроса)

А объясните пожалуйста зачем какому-то условному новичку (который примерно понимает как работает веб, как ходят запросы и что такое tcp) использовать все эти express/koa/fastify/nest/etc nodejs-фреймворки? Вот стоит перед новичком задача — со стороны клиента получить какие-то объекты с данными из бд или отправить объект с метаданными (для того чтобы изменить данные в бд). Новичок и так будет знать что вся информация в вебе ходит поверх тсп-протокола (который уже обеспечивает целостность данных при передаче), но чистый тсп в браузерах недоступен и браузеры предлагают два варианта — http или websockets
И какой самый простой способ решить эту задачу? Зачем этому новичку следует выбирать всякие express/fastify/nest-фреймворки со всякими непонятными middleware/сontrollers поверх http/rest подходов со всякими тонкостями/нюансами/дилеммами выбора методов (post vs put), способа передачи информации (path/query/headers/body) и выбора кодов для ошибок, зачем все это если можно просто установить вебсокет-соединение и просто пересылать нужные объекты/json от клиента к серверу и обратно. То есть исходная задача (установка транспорта между клиентом и сервером чтобы пересылать данные) с вебсокетами решается на порядок проще без всяких фреймворков чем c этими http/rest-фреймворками вроде express/fastify/nest и со стороны новичка становится совершенно непонятно зачем выбирать тот же fastify вместо вебсокетов

Вот он — триумф вертикального масштабирования! In-memory база данных поверх такого процессора способна будет обрабатывать десятки, сотни миллионов или возможно даже миллиард serializable транзакций в секунду и способна заменить сотни/тысячи серверов (и тот огромный оверхед при реализации распределенных транзакций в случае горизонтального масштабирования) определенно точно найдет своего покупателя.
Интересно как в этом процессоре работает механизм cache-coherence который обеспечивает CAS ("compare-and-swap") и другие атомарные обновления памяти? Cache-coherence это другими словами реализация атомарного бродкаста и поскольку это и так самая сложная часть при проектировании современных процессоров то здесь при 850 тысяч ядрер и возможном отказе этих ядер по отдельности проектировщики должны были выйти на уровень распределенных систем (интересно они реализовали paxos в железе?)

а чем плох вариант просто вызывать в цикле функцию вроде parseNextHttpRequest(buf) с передачей буфера и получением словаря http-хедеров и значений (а также статуса и тела). Вполне себе органично вписывается в тот пример кода выше где вместо вызова do_use_fd(..) будет вызов parseNextHttpRequest а потом уже обработка самого http-запроса вызовом еще одной функции

А чем плох вариант просто пробросить системный вызов epoll в линуксе? Linux уже предоставляет возможности асинхронного io виде синхронного вызова epoll_wait в бесконечном цикле. Вот я нашел такую библиотеку (https://github.com/chopins/php-epoll), какие есть недостатки у такого подхода ?


for (;;) {
    $nfds = $epoll->wait($events, MAX_EVENTS, -1);
    if ($nfds == -1) {
        perror("epoll_wait");
        exit(EXIT_FAILURE);
    }

    for ($n = 0; $n < $nfds; ++$n) {
        if ($events[$n]->data->fd == $listen_sock) {
            $conn_sock = stream_socket_accept($stream);
            if (!$conn_sock) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            stream_set_blocking($conn_sock, false);
            $ev->setEvent(Epoll::EPOLLIN | Epoll::EPOLLET);
            $connFdno = $epoll->getFdno($conn_sock, Epoll::RES_TYPE_NET);
            $ev->setData(['fd' => $connFdno]);
            if ($epoll->ctl(Epoll::EPOLL_CTL_ADD, $connFdno,
                        $ev) == -1) {
                perror("epoll_ctl: conn_sock");
                exit(EXIT_FAILURE);
            }
        } else {
            do_use_fd($events[$n]->data->fd);
        }
    }
}

Понятно что это только работа с голым tcp но ведь можно подключить какую-нибудь библиотеку которая будет парсить из tcp-потока http-запросы и таким образом получим на php асинхронный http-сервер который будет не хуже чем в NodeJS (и также это избавляет от необходимости использовать Apache или Nginx перед php)

1
23 ...

Информация

В рейтинге
4 778-й
Зарегистрирован
Активность