Подсистема потоковой передачи¶
Подсистема потоковой передачи обрабатывает трансляцию в прямом эфире, VOD и со сдвигом по времени. Это быстрый путь (~10-100 тыс. запросов в минуту, <50 мс p99), и он использует отдельный облегченный bootstrap, чтобы избежать загрузки всего стека администратора.
Поток запросов¶
client request
|
nginx rewrite (/auth/{token} -> /stream/live.php?token={token})
|
StreamingRequestBootstrap::init()
|
StreamingBootstrap::bootstrap()
|
LegacyInitializer::initStreaming()
|
endpoint logic (live.php / vod.php / timeshift.php)
|
ShutdownHandler::handle()
nginx переписывает все потоковые URL-адреса в точки входа PHP под Public/stream/:
| Шаблон URL-адреса | Точка входа | Цель |
|---|---|---|
/auth/{token} |
live.php |
Прямая трансляция |
/vauth/{token} |
vod.php |
Доставка видео по запросу |
/tsauth/{token} |
timeshift.php |
Архивирование/воспроизведение с временным сдвигом |
/hls/{token} |
segment.php |
Предоставление услуг в сегменте ЗОЖ |
/key/{token} |
key.php |
Ключ шифрования AES-128 |
/subauth/{token} |
subtitle.php |
Передача субтитров |
Расположение каталога¶
src/Streaming/
├── StreamingBootstrap.php
├── AsyncFileOperations.php
├── Auth/
│ ├── StreamAuth.php
│ └── StreamAuthMiddleware.php
├── Balancer/
│ └── ProxySelector.php
├── Codec/
│ ├── FFmpegCommand.php
│ ├── FfmpegPaths.php
│ └── FFprobeRunner.php
├── Delivery/
│ ├── HLSGenerator.php
│ ├── OffAirHandler.php
│ ├── SegmentReader.php # fanout off: TS chase-read segment picking
│ ├── SignalSender.php # fanout off: send-message overlay
│ └── StreamRedirector.php
├── Fanout/
│ ├── FanoutClient.php
│ ├── FanoutMode.php # the fanout_enabled master switch
│ └── IngestFeeder.php
├── Health/
│ └── ProcessChecker.php
├── Lifecycle/
│ └── ShutdownHandler.php
└── Protection/
└── ConnectionLimiter.php
src/Public/stream/
├── index.php # Entry router for the stream endpoints
├── auth.php # Token validation gateway
├── live.php # Live streaming delivery
├── vod.php # VOD delivery
├── timeshift.php # Archive/timeshift playback
├── segment.php # HLS segment delivery
├── key.php # Encryption key delivery
├── subtitle.php # Subtitle delivery
├── thumb.php # Thumbnail delivery
├── probe.php # Stream probe / off-air status
└── rtmp.php # RTMP publishing endpoint
Конвейер начальной загрузки¶
1. StreamingRequestBootstrap::init()¶
Файл: src/Infrastructure/Bootstrap/StreamingRequestBootstrap.php
Действия в порядке:
- Загружайте коды ошибок, обработчик, пути, конфигурацию, двоичные файлы.
- Защита от наводнений (только HTTP): проверьте наличие
FLOOD_TMP_PATH . 'block_' . $rIP. - Загрузите настройки из файлового кэша (
CACHE_TMP_PATH . 'settings'). - Проверка хоста (только HTTP): проверка на соответствие
allowed_domains. - Инициализируйте регистратор.
- Аварийно закрытый шлюз: возвращает 404, если настройки отсутствуют (кроме
/status). - Вызовите
StreamingBootstrap::bootstrap().
2. Потоковый загрузчик::bootstrap()¶
Файл: src/Streaming/StreamingBootstrap.php
Классифицирует конечную точку:
- Конечные точки зондирования:
probe,player_api( небольшая нагрузка) - Конечные точки по умолчанию:
live,thumb,subtitle,timeshift,vod,status - Привилегированные конечные точки:
rtmp,portal
Загружает AsyncFileOperations.php и DatabaseHandler.php, сохраняет настройки в $GLOBALS['rSettings'] и получает доступ к данным в $GLOBALS['rAccess'], затем вызывает LegacyInitializer::initStreaming().
Возвращает экземпляр базы данных $db (используемый устаревшими точками входа).
3. LegacyInitializer::Инициализация потока()¶
Файл: src/Core/Init/LegacyInitializer.php
Заполняет глобальные переменные из кэша:
$GLOBALS['rSettings'],$GLOBALS['rServers'],$GLOBALS['rBouquets']$GLOBALS['rBlockedUA'],$GLOBALS['rBlockedISP'],$GLOBALS['rBlockedIPs']$GLOBALS['rAllowedIPs'],$GLOBALS['rProxies'],$GLOBALS['rSegmentSettings']$GLOBALS['rFFMPEG_CPU'],$GLOBALS['rFFMPEG_GPU'],$GLOBALS['rFFPROBE']
Подключается к базе данных/Redis на основе $rSettings['redis_handler'].
Важный: Путь к потоковой передаче считывается исключительно из файлового кэша. При обычной работе программа не запрашивает настройки в базе данных или запросы пользователей.
Аутентификация по токену¶
Файл: src/Streaming/Auth/StreamAuthMiddleware.php
Содержимое токена:
| Поле | Описание |
|---|---|
username |
Имя пользователя строки |
password |
Пароль к строке |
stream_id |
Идентификатор целевого потока |
expires |
Временная метка истечения срока действия токена |
channel_info |
Потоковые метаданные (on_demand, прокси, pid) |
user_info |
Разрешения пользователя (max_connections, is_restreamer) |
country_code |
Код страны GeoIP |
video_codec |
Запрашиваемый видеокодек |
Утверждение:
- Считайте токен с
Encryption::readToken()в полеlive_streaming_pass. - Проверьте истечение срока действия:
$rTokenData['expires'] < time() - $rServers[SERVER_ID]['time_offset']. - Возвращает проанализированные данные токена или вызывает ошибку.
Формат токена¶
Токены потоковой связи создаются с помощью Encryption::mintToken() и считываются с помощью Encryption::readToken(); ничто другое не вызывает устаревший encrypt()/decrypt() для токена (StreamTokenCallSitesTest применяет его принудительно).
secure_stream_tokens |
Отчеканенные токены | Устаревшие токены читаются |
|---|---|---|
1 (по умолчанию при новой установке) |
Запечатано: AES-256-GCM, случайный номер, base64url(nonce ‖ ciphertext ‖ tag) |
Только в том случае, если токен содержит учетные данные, которые снова проверяются по базе данных |
0 |
Устаревший AES-CBC | Везде |
Устаревший формат - AES-CBC с фиксированным значением IV и без MAC: измененный токен расшифровывается в измененные байты, а ошибка заполнения отличается от неверных учетных данных, которых достаточно для считывания токена или его подделки. Запечатанные токены не могут быть прочитаны или изменены без ключа, и они сохраняют тот же безопасный алфавит URL-адресов, поэтому маршрут или шаблон не меняются.
Где устаревшие токены все еще считываются при включенной настройке и почему:
auth.php/play/ссылки,rtmp.phpтокены иprobe.php/play/ссылки содержат имя пользователя и пароль, которые просматриваются повторно, поэтому подделка ничего не дает. Сохраненные плейлисты и ссылки на портал имеют старый формат. Каждый токен, который не удается прочитатьauth.php, засчитывается по адресу, указанному вBruteforceGuard, что останавливает чтение старого токена из-за сообщений об ошибках.- Все, чьему содержимому доверяют в том виде, в каком оно есть, — JSON-файл live/vod/ timeshift (
user_info,channel_info), Сегмент HLS и ключевые токены, токены миниатюр и субтитров,uitoken—адрес администратора плеера, URL-адрес прокси-сервера веб-плеера и токен подтверждения портала MAG - отклоняются устаревший формат.
Серверы более старой версии не могут считывать запечатанные токены. Поэтому при переносе 021_add_secure_stream_tokens.sql этот параметр отключается на панели, на которой есть другие серверы; включите его в Настройки → Токены потока, защищенные от несанкционированного доступа, как только каждый сервер запустит эту версию.
Заголовки ответов задаются через StreamAuthMiddleware::sendStreamHeaders():
Access-Control-Allow-Origin: *
X-XSS-Protection: 0
X-Content-Type-Options: nosniff
Alt-Svc: h3-29, h3-T051, h3-Q050 (HTTP/3 hints)
Потоковая доставка¶
Жить (live.php)¶
Основная конечная точка доставки (~650 строк):
- Расшифруйте токен с помощью
StreamAuthMiddleware::decryptToken(). - Разрешить использование сервера/прокси-сервера:
StreamAuth::checkAccess()+ProxySelector::availableProxy(). - Установите ограничения на подключение:
StreamAuth::validateConnections(). - Создайте запись о подключении:
ConnectionTracker::createConnection(). - Ручная доставка в
xc_fanoutдемон (смотрите ниже; с разветвлением выключен, см. Отключение разветвления): PHP выдает сообщениеX-Accel-Redirectи завершает работу с байтовым путем — nginx передает байты в потоковом режиме. - тс:
X-Accel-Redirect: /xc_fanout/<id>?c=<uuid>&prebuffer=N(nginx перезаписывается в файл демона/live/<id>). - ЗОЖ: список воспроизведения указывает на выделенные сегменты;
segment.phpпоказы в прямом эфире сегментирует только через демон (/xc_fanout_hls/<id>_<seq>), иначе404. - При выходе:
ShutdownHandler::handle()→ закрыть запись о подключении.
ВИДЕО (vod.php)¶
Тот же процесс аутентификации, что и в live. Считывается из VOD_PATH вместо STREAMS_PATH. Диапазоны байтов (поиск) разрешаются с помощью Streaming\Delivery\HttpRange (одиночные диапазоны RFC 7233, включая диапазоны суффиксов). Видеофильм с прямым подключением передается с помощью cURL, запрашивая у источника точно запрошенный диапазон.
Временной сдвиг (timeshift.php)¶
Обслуживает архивированные сегменты (со сдвигом по времени) из пути к архиву. Запрос TS передает в потоковом режиме несколько файлов подряд; им сопоставляется диапазон байт (поиск) — файлы перед началом пропускаются, первый вводится с правильным смещением, и доставка прекращается в конце диапазона.
Доставка демона — xc_fanout¶
Оперативная доставка клиента (TS и HLS) равна только для демонов: PHP авторизует средство просмотра, а затем полностью покидает байтовый путь, так что средство просмотра больше не закрепляет PHP-FPM-рабочий для всего потока.
- Расходимся веером.
xc_fanout(встроенный демон Go) извлекает каждый источник однажды и он доступен каждому пользователю через сокет unix с помощью встроенного в оперативную память сегментатора HLS. В PHP отсутствует байтовый путь для каждого просмотрщика: рабочий процесс чтения для каждого просмотрщика цикл обслуживания и путь к клиенту на дискеgenerateHLS()исчезли.AsyncFileOperations::awaitFileExists()по-прежнему используется для запуска потока ожидания и путь к байтам VOD/timeshift (см. таблицу производительности). - Кто кормит демона. Поскольку демон является единственным путем к клиенту, каждый живой
продюсер должен включить его, иначе канал нельзя будет смотреть:
поток ffmpeg переходит в свой принимающий сокет (
buildLive(); обратная петля включая дочерних), создатели супервизора демонов делают то же самое, PHP производители — сегментатор LLOD (LlodCommand) и ретранслятор с обратной связью (LoopbackCommand) — протолкнуть черезStreaming\Fanout\IngestFeeder, и поток отложенный подается черезDelayCommand, который перемещает каждый задержанный сегмент по мере того, как он публикует его, в зависимости от продолжительности сегмента (его выходные данные с кодировщика являются отложенный, поэтому тройник для него не используется).IngestFeederбуферизует то, что не удалось отправить неблокирующую запись (короткая запись больше не разрывает пакеты), выполняет повторную регистрацию и набор номера после перезапуска демона и сохраняет ключ HLS. - Две розетки. Клиентский сокет (ориентированный на nginx) обслуживает
/live/<id>и/hls/...; управляющий сокет, предназначенный только для PHP, регистрирует источники (PUT /streams/<id>//ingest/<id>), отвечает на вопросы о статусе выхода в эфир (GET /streams/<id>,GET /probe/<id>) и предоставляет доступ к телеметрии. - Телеметрия / согласование данных.
fanout_syncопросыGET /rates(для каждого uuid КБИТ/с →lines_divergence) и сверяетGET /connectionsс даннымиlines_liveстрок в обоих направлениях: строка, просмотрщик которой покинул демон, называется закрыто (PHP не может увидеть отключение приX-Accel), и демон-просмотрщик, чей пропущенная строка — собранная, с истекшим сроком действия или запрещенная строка — удаляется по истечении 20 секунд (DELETE /connections/<uuid>). - Удары и ограничения по подключению. Строка TS viewer, обслуживаемая демоном, содержит
pid = 0: здесь нет рабочего, которого можно было бы убить.ConnectionLimiter/ConnectionTracker::closeConnection()завершите его с помощьюConnectionTracker::dropDaemonViewer()—FanoutClient::dropConnection()на этом узле, или сигналdrop_conо том, что узел зрителя превращается в тот же вызов. Ограничитель никогда не отключает запрашивающее соединение сам по себе (это идентифицируется по uuid, поскольку каждая строка демона имеет общий pid 0). - Вне эфира. Если демон сообщает об отсутствии данных (
has_data=false/ stale), PHP показывает страницу "не в эфире" вместо того, чтобы позволить зрителю зависнуть. - Сохраненный на диске HLS только для временного сдвига / миниатюр /
.analyse/ дочерние элементы loopback / проверки запуска по требованию — не для доставки клиенту.
Управление потоковой передачей и встроенный ремультиплексор¶
При включенном Контроль разветвленного энкодера (fanout_supervise, миграция 018, по умолчанию включено),
в прямой трансляции не используется PHP watchdog. StreamProcess::startMonitor() создает свои команды и раздает
их супервизору демона (FanoutClient::supervise → PUT /monitor/<id>), который запускает,
отслеживает и перезапускает их — отработка отказа, приоритетное резервное копирование, принудительный источник, сбой вывода, потеря звука,
включая снижение частоты кадров и перезапуск по расписанию. PHP продолжает создавать каждую команду и выполнять все
запись в базу данных; демон выполняет то, что ему передают.
- Передача —
StreamProcess::superviseStream()сначала запрашивает у демона (GET /monitors/state: доступно,accepting), создает спецификацию (StreamProcess::buildSupervisorSpec(): одна команда для каждого источника, политики и работоспособности, сопоставленных с соблюдены настройкиMonitorCommand), записывает pid демона какmonitor_pid, затем передает его over. Without a restart a running encoder is adopted, not replaced;cron:streamsmoves PHP-отслеживает потоки таким образом при следующем проходе. - Команды — прямая трансляция, доступная только для копирования, запускает собственный ремуксор демона,
xc_fanout remux, построенный с помощьюStreamProcess::buildNativeLive()рядом сbuildLive(): он считывает исходный код изначально (MPEG-TS по протоколу http(s), HLS с сегментами TS, udp/rtp) и записывает тот же HLS на диск и демон передавайте как строку ffmpeg-f tee, без ffmpeg. Какие потоки соответствуют требованиям, этоStreamProcess::nativeRefusal()/isNativeSource();fanout_source_backendрешает:auto= ремультиплексор с командой ffmpeg в видеfallback_cmd(используется при завершении работы ремультиплексора 3, "не удается использовать этот источник"),native= только для ремиксера,ffmpeg= только для ffmpeg. Только панель записывает команду ремультиплексора, когда демон узла объявляет об этом (featuresвGET /monitors/state,FanoutClient::supportsRemux()) — более старый двоичный файл неправильно разобрал бы его. - Какой продюсер баллотировался и почему — переданная команда записывается рядом с записью потока.
файлы, подобные пути к самопроизвольному запуску
<id>_.ffmpeg:<id>_.fanoutдля ремультиплексора,<id>_.ffmpegдля ffmpeg (вauto- для обоих). Когда включен собственный сервер и выполняется поток ffmpeg в любом случае, причинаStreamProcess::nativeRefusal()добавляется к<id>.errors([panel] ffmpeg runs this stream: transcoding is enabled), тот же файл, что и у производителя. stderr переходит в. Соответствующий типstreams_types.type_key=live;gen_timestampsиread_nativeнамеренно не являются отказами (оба значения по умолчанию равны 1, поэтому они ничего не говорят о канал — смотрите программу запуска демона). - Reconcile — the daemon cannot write the database, so
StreamProcess::reconcileSupervised()копирует его состояние вstreams_servers(статус, pid, текущий источник, кодеки, разрешение, измеренный битрейт): каждый проходcron:streamsи каждые 5 секунд от демонаsignals. A контролируемый поток, строка которого пропущена или помечена как остановленная, освобождается. Кодеки и размер изображения записываются вstream_infoJSON, а также в плоские столбцы — этот JSON - это то, что отображает список потоков, из которого адаптивный мастер-плейлист беретBANDWIDTH/RESOLUTIONи гдеstream/auth.phpсчитывает видеокодек зрителя, и контролируемый поток никогда не запускается попробуйте заполнить его. - Остановка —
StreamProcess::stopStream()освобождает первый (DELETE /monitor/<id>, который убивает продюсер); сначала супервизор перезапускает процесс, убивая продюсера. - Резервный вариант для PHP — демон, который отключен или не принимает, и поток указывает, что он не принимает
берем (задержка, созданные каналы,
yt-dlpисходники платформы), запускаемMonitorCommand, как и раньше;MonitorCommandотключается для потока, который контролирует демон. - - За этим следят? - спросил я. — для контролируемого потока
monitor_pid- это pid демона, поэтому вызывающие абоненты используютStreamProcess::isWatched()(PHP-монитор активен или контролируется), а неProcessManager::isMonitorAlive()один.
Runbook на стороне демона - включение, проверка, откат кодов завершения работы ремультиплексора — это
docs/en/09-encoder-supervision.md в репозитории XC_VM_Fanout.
Выключение разветвления¶
settings.fanout_enabled (переключатель Доставка по разветвлению, расположенный рядом с другими настройками разветвления, по умолчанию
включен; миграция 027) - это главный переключатель, считываемый с помощью
Streaming\Fanout\FanoutMode. Выключен:
- Демон останавливается на каждом узле.
FanoutMode::applyToNode()записывает флаг файлbin/xc_fanout/disabled, затем сначала убиваетrun.sh(поэтому он не может возродиться демон) иxc_fanoutвторой. Он запускается в режиме MAIN при заданных настройках. сохраняется и на каждом узле, начиная сRootSignalsCronJob, в течение минуты. То Рутсигналы продолжают работать, а почасовое самоисцелениеfanout_binaryпропускает работу демона пока флаг есть. То же самое можно сделать сservice bootиrun.sh, которые не могут считывать установите настройки и вместо этого проверьте файл с флагом. При повторном включении флаг исчезнет и программа keepalive снова запустится сrun.sh. - Every daemon call behaves as "daemon down".
LicenseGate::fanoutUsable()и при каждом вызове управляющей розеткиFanoutClientпроверяйте переключатель.IngestFeederстановится недоступным, поэтому разработчики PHP (LLOD, loopback, delay) сохраняют только свои HLS на диске. Контроль возвращается кMonitorCommand.fanout_syncостанавливается записывает конфигурацию демона и закрывает обслуживаемое демоном (pid = 0) соединение строки, оставленные остановленным демоном. - Зрители проходят по маршрутам, предшествующим разветвлению (
FanoutMode::legacyDelivery(), который это также верно, когда лицензия запрещает разветвление): - ЗОЖ:
live.phpобслуживаетHLSGenerator::generateHLS()на диске.<id>_.m3u8.segment.phpобслуживает<id>_<n>.tsотSTREAMS_PATHдоX-Accel-Redirect: /xc_hls/…, AES-128-шифрование при первом чтении, когдаencrypt_hlsвключен. - тс:
live.phpchase - считывает сегменты на диске (SegmentReader) в Работник FPM, на весь срок службы соединения. - Потоки прокси-серверов:
StreamProcess::startProxy()выполняетсяProxyCommand(XC_VMProxy[<id>]). Он извлекает источник один раз и отправляет дейтаграммы каждому из них. unix-сокет viewer подCONS_TMP_PATH/<id>/, которыйlive.phpретранслирует в клиент. Он закрывается через несколько секунд после последнего просмотра. - Отправить сообщение: файл сигнала, указанный в разделе
SIGNALS_PATH, записывается на следующий сегмент просмотра наSignalSender.
При разветвлении на демон, который просто не работает, все равно не выходит в эфир до тех пор, пока keepalive перезапускает его (примерно через 2 секунды). Доставка никогда не прерывается по каждому запросу.
Наложение отправленного сообщения¶
Действие администратора "Отправить сообщение" приводит к появлению текстового баннера на видео, которое просматривает один зритель.
PHP отправляет его в сокет управления демоном
(FanoutClient::sendSignal → POST /signal/<uuid>), и демон применяет
наложение ffmpeg drawtext на следующий HLS-сегмент этого зрителя (или короткий ~5-секундный TS
окно), однократный запуск, максимальное усилие - сигнал никогда не прерывает воспроизведение. Демон должен
быть запущенным с помощью ffmpeg, который на самом деле имеет фильтр drawtext, так что
service программа запуска выбирает сборку с поддержкой drawtext.
Управление подключениями¶
Средство отслеживания подключений¶
Управляет текущим состоянием соединения. Серверная часть выбрана с помощью $rSettings['redis_handler']:
Redis (preferred for scale):
- Соединения, хранящиеся в отсортированных наборах:
LINE#{identity}— подключения для пользователяSTREAM#{stream_id}— соединения для потокаSERVER#{server_id}— соединения на сервере
MySQL (fallback):
- Таблица:
lines_liveс полями:activity_id,user_id,stream_id,server_id,uuid,pid,hls_end
Закрывающие зрители (cron:users). В режиме Redis MAIN просматривает все средства просмотра сервера; в режиме MySQL
каждый сервер проверяет свой собственный, и MAIN также закрывает данные удаленного сервера или средства просмотра, которое проверяет
в и хранил молчание в течение UsersCronJob::ORPHAN_AFTER.
- Время (
date_start,hls_last_read,date_endстроки действия) - это часы MAIN: узел помечаетtime() - time_offset, и развертка узла также сравнивается по этим часам. - Просмотр, закрытый для тишины, заканчивается в тот момент, когда он был слышен в последний раз: собственная печать закончившего работу работника, последний Чтение HLS или последняя регистрация работника TS/VOD (каждые 300 секунд) плюс один период.
- Развертка сохраняет свои строки активности и записывает данные пакета только после удаления его записей; убитая зачистка или та, которая не смогла удалить их, оставляет их для следующей зачистки, которая регистрирует их в журнале однажды.
- uuid средства просмотра RTMP равен
ConnectionTracker::rtmpUuid(): nginx - идентификатор клиента rtmp и сервера. - Сигнал отключения в режиме Redis (
SIGNAL#…) истекает после непрочитыванияConnectionTracker::SIGNAL_TTL.
Ключевые методы:
ConnectionTracker::createConnection($data)
ConnectionTracker::updateConnection($connection, $changes, 'open'|'close')
ConnectionTracker::getConnection($uuid)
ConnectionTracker::getLineConnections($user_id)
ConnectionTracker::getCapacity()
Ограничитель подключения¶
Файл: src/Streaming/Protection/ConnectionLimiter.php
Устанавливает ограничения на подключение для каждого пользователя при превышении значения max_connections:
| Приоритет | Критерий | Действие |
|---|---|---|
| 2 | Тот же IP + тот же пользовательский агент | Убей первым |
| 1 | Тот же IP-адрес (любой UA) | Убей следующего |
| 0 | Какая-либо связь | Убить в качестве запасного варианта |
Настройки:
disallow_2nd_ip_con— принудительно использовать один IP-адрес для каждого пользователяip_subnet_match— соответствует подсети /24 вместо точного IP-адресаrestrict_same_ip— возвращает ошибку при несоответствии IP-адресов вместо уничтожения
Устройство для выключения¶
Файл: src/Streaming/Lifecycle/ShutdownHandler.php
Зарегистрирован через register_shutdown_function(). При выходе из PHP-процесса:
- Закройте запись о подключении в
lines_liveили Redis. - Удалите tmp-файлы со значением
CONS_TMP_PATH . $uuid. - Удалите поток по требованию из очереди, если это применимо.
балансировка нагрузки¶
Выбор сервера (StreamAuth::checkAccess)¶
Файл: src/Streaming/Auth/StreamAuth.php
Алгоритм:
- Получите доступные серверы:
server_online == true,server_type == 0,online_clients < total_clients. - Сортировка по вместимости (по возрастанию) — сначала загружается наименее загруженный.
- Применить маршрутизацию GeoIP (если
enable_geoip == 1): - Точное соответствие стране → выберите немедленно.
geoip_type == 'strict'→ исключить несоответствия.- В противном случае → присвоить приоритетный вес.
- Применить маршрутизацию через интернет-провайдера (если
enable_isp == 1): та же логика, что и у GeoIP. - Верните сервер с наименьшей пропускной способностью из группы с наивысшим приоритетом.
Выбор прокси-сервера (ProxySelector::Доступный прокси)¶
Файл: src/Streaming/Balancer/ProxySelector.php
Тот же алгоритм, что и StreamAuth::checkAccess(), но примененный к списку прокси-серверов.
Ограничение скорости и защита от наводнений¶
Три слоя:
1. nginx (уровень подключения)¶
20 запросов в секунду на IP-адрес с пакетом из 8 запросов. 30-минутное скользящее окно.
2. StreamingRequestBootstrap (IP-блокировка)¶
IP-блокировка на основе файлов. Файлы блоков создаются с помощью вышестоящей логики обнаружения наводнений.
3. Ограничитель подключений (для каждого пользователя)¶
Применяется после проверки токена. Ограничивает одновременные потоки для каждого пользователя на основе max_connections, закрывая сначала самые старые соединения (более старые соединения самого запрашивающего устройства перед другими). Средства просмотра, обслуживаемые демонами, отключаются с помощью демона — см. Daemon delivery.
4. Серверы, работающие только через прокси¶
Сервер с параметром enable_proxy принимает только запросы, поступающие через один из его прокси-серверов. auth.php проверяет TCP—пиринг nginx saw — XC_PEER_ADDR, значение которого равно $realip_remote_addr в расположении потока nginx.conf, а не заголовок запроса, которым управляет клиент.
Шифрование HLS¶
Клиентский HLS обслуживается демоном xc_fanout (см. Доставка демоном), поэтому происходит шифрование сторона демона:
StreamProcessзаписывает ключ AES-128 потока/IV вcontent/streams/<id>_.key/_.iv— перед тем, как он запустит PHP producer, который регистрируется у демона через несколько мгновений после запуска.- При регистрации приема (
FanoutClient::registerIngest), когда включеноencrypt_hls, ключ/IV передается демону, который шифрует сегменты HLS, которые он обслуживает. Каждый производитель передает их — потоки ffmpeg (включая дочерние элементы с обратной связью), контролируемые потоки и производителей PHP черезIngestFeeder::forStream()— потому что в списке воспроизведения всегда указывается ключ: демон, запущенный без него, подавал простые сегменты, которые ни один игрок не смог бы расшифровать. HLSGenerator::tokenizeDaemonPlaylist()переписывает URL-адреса сегментов плейлиста демона в ссылки с авторизацией для каждого сегмента/hls/<token>, которыеsegment.phpпроксируются от демона, и добавляет строку#EXT-X-KEY.- Ключ AES доставляется игрокам с помощью
key.php(src/Public/stream/key.php) с использованием того же механизма токенов.
Значение #EXT-X-MEDIA-SEQUENCE в плейлисте live привязывается к значению HlsSequence, поэтому он никогда не возвращается назад при переходе из прямого эфира в прямой эфир без изменения нумерации воспроизводимого потока (его состояние сохраняется в виде tmp/signals/hlsseq_<id>, поэтому оно сохраняется после перезапуска потока).
Представление¶
Ключевые проектные решения, касающиеся пропускной способности и задержки:
| Особенность | Механизм |
|---|---|
| Трансляция-онлайн-ожидание | AsyncFileOperations::awaitFileExists() ожидает _.pid/_.monitor/первого сегмента при появлении потока (и в байтовом пути VOD/timeshift). Оперативная доставка клиента осуществляется демоном, а не считывается с помощью PHP. |
| Нулевой режим работы процессора | time_nanosleep() через AsyncFileOperations::efficientSleep() |
| буферизация nginx | 128 буферов по 32 КБАЙТ на запрос |
| Объединение подключений в пул | Redis (предпочтительно) или постоянный MySQL |
| Чтение только из кэша | Настройки и пользовательские данные считываются из файлового кэша без запросов к базе данных |
| Ранний выход (VOD/timeshift) | Эти байтовые циклы запрашивают connection_status() для остановки при отключении клиента. В Live нет байтового цикла PHP для каждого пользователя (он обслуживается демоном). |
| Обновление настроек | Каждые 5 минут (300 секунд) для отслеживания изменений конфигурации без перезапуска |
Пути к файловой системе¶
STREAMS_PATH = /home/xc_vm/content/streams/
VOD_PATH = /home/xc_vm/content/vod/
ARCHIVE_PATH = /home/xc_vm/content/archive/
VIDEO_PATH = /home/xc_vm/content/video/
CONS_TMP_PATH = /home/xc_vm/tmp/opened_cons/
CACHE_TMP_PATH = /home/xc_vm/tmp/cache/
FLOOD_TMP_PATH = /home/xc_vm/tmp/flood/
SIGNALS_TMP_PATH = /home/xc_vm/tmp/signals/
SIGNALS_PATH = /home/xc_vm/signals/
Диагностика и оснастка¶
Автономный инструмент проверки целостности потока (tools/stream-check/stream_check.py) теперь доступен на отдельной странице - см. Диагностика и инструменты для потоковой передачи.
Обоснование проекта (ADR)¶
Почему live delivery отошла от tmpfs и от байтового пути PHP — решения, стоящие за текущим
xc_fanout архитектура — записывается в отчетах об архитектурных решениях (repo-внутренние примечания,
не является частью опубликованного сайта):
- ADR 0001 — Tmpfs-free streaming — PHP out of the byte path, native fan-out, in-RAM HLS.
- ADR 0002 —
xc_fanoutdaemon — the native live fan-out daemon. - ADR 0003 — Полное отключение демона — отмена устаревшего байтового пути для live.
Связанные файлы¶
| Файл | Цель |
|---|---|
src/Streaming/StreamingBootstrap.php |
основной загрузчик потоковой передачи |
src/Infrastructure/Bootstrap/StreamingRequestBootstrap.php |
Инициализация на уровне HTTP |
src/Streaming/Auth/StreamAuth.php |
выбор сервера и проверка подключения |
src/Streaming/Auth/StreamAuthMiddleware.php |
расшифровка токенов и заголовки ответов |
src/Streaming/Balancer/ProxySelector.php |
выбор прокси-сервера |
src/Streaming/Protection/ConnectionLimiter.php |
ограничения на подключение для каждого пользователя |
src/Streaming/Delivery/HLSGenerator.php |
Генерация плейлиста M3U8 |
src/Streaming/Delivery/StreamRedirector.php |
доступность потока и маршрутизация сервера |
src/Streaming/AsyncFileOperations.php |
неблокирующие утилиты для файловой системы |
src/Streaming/Lifecycle/ShutdownHandler.php |
очистка соединения при выходе |
src/Domain/Stream/ConnectionTracker.php |
состояние соединения в Redis/MySQL |
src/Domain/Stream/StreamProcess.php |
формирование команды (buildLive / buildNativeLive), передача контроля и согласование |
src/Streaming/Fanout/FanoutClient.php |
API управления демонами (прием, контроль, принудительный источник) |
src/Core/Init/LegacyInitializer.php |
настройка глобальной переменной для потоковой передачи |
tools/stream-check/stream_check.py |
проверка целостности очереди + пакет плейлистов + панель мониторинга живого буфера + графический редактор SVG |