Зачем это читать

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

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

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

Взрослые компании называют это VDI. Но сам по себе VDI не существует: без протокола доставки рабочего стола он просто не работает.

SPICE — один из таких протоколов. Open source, изначально заточен под Linux и без лицензионных отчислений. Если вы строите VDI на открытом стеке, рано или поздно вы с ним столкнётесь.

Дальше будет не обзор возможностей и не маркетинг. Мы вместе разберём, как SPICE устроен изнутри: какие архитектурные решения там приняты, зачем они такие и к каким практическим последствиям это приводит.

Немного истории (без занудства)

SPICE родился внутри Qumranet — израильского стартапа, который создал KVM. В 2008-м Red Hat купил Qumranet, а в декабре 2009-го открыл код SPICE.

Почему Red Hat вложился в собственный протокол, когда RDP и Citrix уже существовали? Однозначного ответа нет, но среди факторов наверняка были:

  • отсутствие открытого протокола для Linux VDI

  • желание контролировать весь стек (KVM + libvirt + SPICE)

  • независимость от лицензионной политики Microsoft и Citrix

Насколько каждый из этих факторов был решающим — сказать сложно. Но результат налицо: SPICE стал одним из основных протоколов доставки рабочего стола для oVirt, Proxmox, GNOME Boxes и остального open source VDI.

Архитектура: каналы и TCP

SPICE — клиент-серверный протокол. Серверная часть (libspice) живёт в QEMU, клиент (remote-viewer, spice-gtk) — на устройстве пользователя.

Первое архитектурное решение: только TCP

Это важно понять сразу, потому что отсюда растут многие ограничения.

SPICE работает исключительно поверх TCP. Никакого UDP. Точка. В spice-devel ещё в 2011 году обсуждали добавление UDP для аудио — воз и ныне там.

Что это означает на практике:

В LAN (потери <0.1%, латентность <5мс) — всё хорошо. TCP справляется, пользователи довольны.

В WAN с хорошим каналом (потери <0.5%, латентность <50мс) — терпимо. Есть нюансы, но жить можно.

В WAN с потерями (1%+ packet loss) — начинаются проблемы. TCP реагирует на потери ретрансмитами, латентность растёт, окно уменьшается. Там, где UDP-протокол просто потеряет кадр и пойдёт дальше, TCP будет героически доставлять каждый байт — и пользователь будет страдать.

Почему выбрали TCP? Простота. Не нужно возиться с NAT traversal, UDP hole punching, QoS для разных типов трафика. TCP просто работает через любой корпоративный файрвол. Для 2007-2009 года, когда проектировался протокол, это был разумный выбор. Сейчас, возможно, решили бы иначе.

Второе архитектурное решение: независимые каналы

Всё общение разбито на каналы — каждый со своим TCP-соединением:

Канал

Что делает

Почему важен для UX

Display

Картинка

Пропускная способность

Inputs

Клавиатура, мышь

Латентность — 100мс задержки и пользователь взбесится

Cursor

Курсор отдельно

Курсор должен «прилипать» к движению мыши мгновенно

Playback

Звук

Джиттер критичен

USB

Проброс устройств

Зависит от устройства

Отдельный канал для курсора — это не академическое решение, а практическая необходимость. Когда Display Channel загружен тяжёлым контентом, курсор всё равно должен двигаться плавно. Если бы курсор шёл в общем потоке с картинкой — каждый раз, когда на экране что-то сложное, мышь бы «залипала». Люди такое не прощают.

Разделение на каналы позволяет приоритизировать: input важнее, чем очередной кадр видео. Но — и это важно — приоритизация происходит на уровне приложения, а не протокола. SPICE не имеет встроенного QoS. Это ещё одно упрощение, за которое иногда приходится платить.

Каналы SPICE — клиент слева, сервер справа, параллельные TCP-соединения между ними
Каналы SPICE — клиент слева, сервер справа, параллельные TCP-соединения между ними

Image-based: осознанный выбор, а не ограничение

Здесь нужно кое-что объяснить, потому что эту тему часто понимают неправильно.

Протоколы удалённого рабочего стола делятся на два типа:

Command-based: передаём графические команды («нарисуй прямоугольник», «залей область»). Клиент рендерит сам. Так работает X11, так частично работает RDP.

Image-based: передаём готовые изображения изменившихся областей. Клиент просто показывает картинки.

SPICE — image-based протокол. И это не потому, что разработчики не осилили command-based. Это сознательный архитектурный выбор.

Почему image-based?

Универсальность. Image-based работает с чем угодно — браузер, CAD, игра, терминал. Протоколу без разницы, что рисует приложение. Всё превращается в пиксели.

Независимость от гостя. Command-based требует глубокой интеграции с графической подсистемой гостевой ОС. Image-based нужен только framebuffer — а его даёт любая система.

Предсказуемость. С command-based можно нарваться на приложение, которое генерирует «плохие» команды — и всё встанет колом. Image-based деградирует предсказуемо и пропорционально объёму изменений: больше изменений на экране — больше трафика. Никаких сюрпризов.

Цена решения

Трафик. Image-based в среднем требует больше bandwidth, чем хороший command-based. Для типичного офисного десктопа — порядка 1-5 Мбит/с (очень грубо, зависит от миллиона факторов). Для активной работы с видео — значительно больше.

В LAN это не проблема. В WAN с ограниченной полосой — фактор, который нужно учитывать.

QXL и реальность современных десктопов

Теперь — о том, что происходит на стороне гостевой ОС.

SPICE может работать с обычным VGA — тогда QEMU просто читает framebuffer и отправляет изменения. Но для приемлемой производительности нужен QXL — паравиртуальное графическое устройство.

Как задумывалось

Драйвер QXL в гостевой ОС перехватывает графические команды и транслирует их в QXL-команды. Эти команды попадают в command ring — область shared memory между гостем и хостом. libspice вытягивает их и решает: простые операции (заливка, копирование области) можно отправить клиенту как команды, сложные — растеризовать и отправить как изображения.

В теории это даёт лучшее из обоих миров: command-based для простых операций, image-based для сложных.

Как получилось

На практике 2D-команды QXL почти не используются. Почему?

Современные десктопы работают через композитные оконные менеджеры — Mutter, KWin, DWM. Им нужен OpenGL. В виртуалке без GPU passthrough аппаратного GPU нет, есть только QXL. Поэтому композитор работает через llvmpipe — программный растеризатор Mesa, который эмулирует OpenGL на CPU.

Результат: композитор рендерит всё в offscreen buffer и отдаёт QXL готовый framebuffer. 2D-команды не задействуются. QXL становится просто framebuffer'ом с дополнительными фичами.

Что это значит

Это не баг и не провал архитектуры. SPICE изначально проектировался как image-based протокол. 2D-команды QXL — оптимизация для специфических случаев (скроллинг, X11 без композитинга, классический Windows GDI), а не основной путь.

Когда SPICE создавался в 2007-2009, композитинг ещё не был повсеместным. Сейчас мир изменился, и основной режим работы — передача изображений. Но протокол к этому готов, потому что так и задумывался.

(Впрочем, можно отключить композитинг в гостевой ОС — и тогда 2D-команды заработают. Но кто будет это делать в 2025 году?)

Display Channel: как сжимаем картинку

Display Channel — сердце протокола. Здесь происходит самое интересное.

Pipeline

  1. Детекция изменений — отслеживание dirty regions в framebuffer

  2. Оптимизация — отбрасывание областей, которые уже перекрыты новыми

  3. Классификация — эвристика решает, какой кодек применить

  4. Сжатие — применяется выбранный кодек

  5. Передача — данные уходят клиенту

  6. Декодирование — клиент восстанавливает картинку

Всё это происходит много раз в секунду. Скорость критична.

Pipeline от framebuffer до клиента
Pipeline от framebuffer до клиента

Кодеки: инженерные компромиссы

SPICE использует несколько алгоритмов сжатия. Это не «у нас много кодеков, мы крутые» — это необходимость. Разный контент требует разных подходов.

QUIC

Собственная разработка Qumranet/Red Hat на базе алгоритма SFALIC (Starosolski, 2007). Использует предсказание соседних пикселей + кодирование Голомба-Райса.

Где хорош: фотографии, видеокадры, градиенты — всё с плавными переходами цветов. Степень сжатия порядка 3-5x для такого контента (оценка, не точное измерение).

Где плох: текст, UI с резкими границами. Предсказание не работает на резких переходах, кодек тратит биты впустую.

Название сбивает с толку — этот QUIC не имеет отношения к QUIC-протоколу от Google.

LZ / GLZ

LZ — классический словарный алгоритм. Ищет повторяющиеся последовательности, заменяет ссылками.

GLZ — расширение с глобальным словарём между кадрами. Идея простая: пользователь переключается между окнами, каждое окно уже отрисовывалось. GLZ находит совпадения в ранее переданных данных и отправляет короткие ссылки вместо полных изображений.

Где хорош: текст, UI, офисная работа с переключением между приложениями. По субъективным оценкам, GLZ экономит 20-50% трафика по сравнению с обычным LZ для типичной офисной нагрузки.

Ограничение: размер словаря конечен. При интенсивной работе старые данные вытесняются, и экономия падает.

LZ4

Появился позже в SPICE-стеке как альтернатива классическим LZ-алгоритмам, в том числе для сценариев со слабыми клиентами (ARM, thin clients).

Особенность: сжимает хуже (порядка 1.5-2x), но декодирует в разы быстрее. Компромисс: больше трафика, но клиент не греется.

Когда использовать: Raspberry Pi, Android-устройства, тонкие клиенты со слабым CPU. Если сеть быстрая, а процессор медленный — лучше передать больше данных, но не грузить CPU декомпрессией.

Как SPICE выбирает кодек

По умолчанию — режим auto_glz. Эвристика смотрит на цветовое распределение: много уникальных цветов → QUIC, мало цветов с повторами → GLZ.

Эвристика не идеальна. Код с подсветкой синтаксиса может быть принят за «фотографию» и сжат QUIC, что неоптимально. Но в большинстве случаев работает.

Можно форсировать кодек:

remote-viewer --spice-preferred-compression=auto_glz spice://host:5900
Какой кодек для какого контента
Какой кодек для какого контента

Оптимизации: кэш и скроллинг

Image Cache

Идея: передать картинку один раз — хорошо. Не передавать повторно — ещё лучше.

Сервер вычисляет хеш каждого изображения. Если картинка уже в кэше клиента — отправляется только ID. Клиент достаёт её из кэша.

Где работает: переключение окон, повторяющиеся иконки, возврат к ранее показанному контенту.

Где не работает: видео (каждый кадр уникален), интенсивная работа с переполнением кэша.

COPY_BITS

При скроллинге содержимое окна сдвигается. Наивный подход — передать весь видимый контент заново. Умный подход:

  1. Сервер определяет, что это скролл

  2. Отправляет команду «скопируй область из (x1,y1) в (x2,y2)»

  3. Передаёт только новый контент в освободившейся полосе

Результат: вместо мегабайта — килобайты.

Ограничение: работает с QXL, композитные менеджеры могут ломать детекцию. При быстром скролле не успевает — переходит к полным перерисовкам.

Итоги: для кого этот протокол

Давайте честно.

SPICE хорош, когда:

  • Linux VDI в контролируемой сети (датацентр, корпоративный LAN)

  • Бюджет ограничен, лицензии — существенная статья расходов

  • Нужна предсказуемость open source без vendor lock-in

  • Нагрузка типовая: офис, разработка, терминалы

SPICE — компромисс, когда:

  • WAN с нестабильным каналом (TCP-only даёт о себе знать)

  • Много видео и мультимедиа (M-JPEG — это 90-е)

  • Нужны продвинутые фичи типа 3D-ускорения

SPICE не подходит, когда:

  • Пользователи через океан с 2% packet loss

  • Критичны GPU-ускоренные workloads

  • Нужна enterprise поддержка с SLA

Это не «плохой протокол». Это протокол с понятной нишей и честными ограничениями. В своей нише — Linux VDI в LAN — он работает хорошо и стоит ноль рублей. За пределами ниши — есть варианты лучше, но они стоят денег.

В следующих статьях разберём остальные каналы: почему курсор всё-таки отдельно, как работает agent mouse mode, что происходит с USB.