Ориентация главы
Заключительная глава тома 9. Три предыдущие дали обновление (93), кадры (94), потоки (95); эта добавляет последние два инженерных звена двунаправленного канала и сцепляет всю цепочку.Протокол закрытия: круг xrtWsCloseWrite/CloseParse, семантические домены кодов закрытия (1000 норма/1001 ухожу/1002 протокольная ошибка...), принуждение на стороне записи (причина обязана быть легальным UTF-8, общая длина в пределе, зарезервированные коды не выходят в сеть) — грациозное закрытие это рукопожатие «обе стороны знают, что конец».permessage-deflate: расширение сжатия WebSocket — слой согласования (строгое различение offer/response, window bits, context takeover), потоковые объекты xwsinflater/xwsdeflater (продвижение фрагментами без агрегации, двойные потолки, RSV1 только на первом кадре), путь сжатой отправки слоя Stream. Напоследок «полное путешествие одного сообщения» сцепляет девять глав тома от разбора HTTP до сжатых фрагментированных кадров — с главы 98 начинается высокоуровневый мир xhttp.
97.1引入
Два инженерных вопроса завершают том 9. Первый: как соединению «красиво попрощаться»? Разорвать TCP — противоположная сторона не отличит «нормальное завершение» от «сетевой аварии/обрезки промежуточным устройством»: мониторинг, тревоги и стратегия переподключения все откажут. Контрольный кадр Close несёт код состояния и причину, делая закрытие подтверждённым обеими сторонами протокольным событием: инициатор шлёт Close, противоположная сторона отвечает Close, каждая входит в финал — снимок CloseInfo сообщает «почему ушла» противоположная сторона. Второй: JSON-сообщения высоко повторяемы (имена ключей, структура); сжатие текста экономит 70-90% полосы — но подход Content-Encoding HTTP (глава 93) на двунаправленном потоке нужно перепроектировать: сжатие по сообщениям, а не по соединению; словарь переиспользуется через сообщения (context takeover); контрольные кадры не сжимаются никогда — это permessage-deflate, и согласование, и потоковая обработка которого суть инженерия протокольного слоя.
97.2概念
Рукопожатие закрытия и семантика кодов
Семантические домены кодов закрытия делятся на три полосы.1000-1003: семейство нормального закрытия (1000 норма, 1001 точка уходит, 1002 протокольная ошибка, 1003 неприемлемый тип данных);1008-1011: семейство ошибок (1008 нарушение политики, 1009 сообщение слишком велико, 1010 нет расширения, 1011 внутренняя ошибка);3000-4999: прикладные пользовательские (библиотека не толкует);зарезервированные коды (1005 «без кода», 1006 «аварийное закрытие», 1015 «провал рукопожатия TLS») — только выражение API, выход в сеть запрещён: они описывают «ситуации без кадра Close», и сторона записи отвергает их наотрез. Правила нагрузки: код 2 байта + причина (легальный UTF-8, всего ≤123 — XWS_CLOSE_PAYLOAD_MAX) — сторона письма отказывает на нарушении, сторона чтения отвергает уродливую нагрузку.
permessage-deflate: слой согласования
Имя расширения permessage-deflate, параметров четыре: server_max_window_bits/client_max_window_bits (окно сжатия 9-15 — меньше экономит память, степень сжатия чуть падает), server_no_context_takeover/client_no_context_takeover (сброс словаря на границе сообщений — экономия памяти, степень через сообщения падает). Правила согласования строго принуждаются слоем: offer и response валидируются раздельно, повтор параметров отвергается, нелегальные window bits отвергаются, response не вправе вводить возможности, которых не было в offer, context takeover един на обоих концах. Принцип ответа — минимальное соответствующее подмножество: возвращаются только необходимые параметры (offer предложил server_max_window_bits=10; приняв, отвечаем ровно это). xrtWsDeflateDirection переводит результат согласования в односторонние рабочие параметры — параметры приёма и отправки могут различаться.
Потоковые объекты сжатия
xwsinflater (распаковка)/xwsdeflater (упаковка) — потоковые объекты в той же позе, что декомпрессия главы 29 и декодирование главы 93:
- Продвижение фрагментами: вход и выход по кускам, без агрегации целого сообщения — большое сообщение держит память постоянной.
- Вывод в обратный вызов: распакованные данные идут прямо в буфер вызывающего или очередь отправки.
- Двойной потолок: общий объём распаковки ограничен и потолком сообщения, и потолком Inflate — оборона от бомб сжатия (3 байта входа заявляют 4 ГБ выхода? потолок перехватывает первым).
- no-context-takeover: при согласовании словарь сбрасывается на границе сообщений.
- Планирование ёмкости:
xrtWsDeflaterBoundдаёт верхнюю границу до записи — вход в бюджетирование SendLimit. - Пути отказа: отказ обратного вызова/OOM/ошибка данных с ясной семантикой reset/abort/destroy.
Правило RSV1: флаг сжатия RSV1 разрешён только на первом кадре данных сжатого сообщения — continuation его не повторяет, контрольные кадры не сжимаются никогда. Это исполнение «ненулевой RSV = согласовано расширение» из главы 95.
Полное путешествие одного сообщения (панорама тома 9)
API девяти глав по местам в одном сообщении — это и есть приёмочная форма «блочной структуры».
97.3示例
Первая полная программа: круг нагрузки закрытия
Программа ниже из examples/websocket/close/main.c — выписывание/разбор кода и причины туда-обратно:
#include <xrt.h> #include <stdio.h> */ /* 写出并解析一个带 UTF-8 原因的 Close 控制帧负载。 */ int main(void) { uint8 Payload[XWS_CLOSE_PAYLOAD_MAX]; xwsclose Close; xbytesview Input; size_t iSize; if ( !xrtWsCloseWrite( XWS_CLOSE_NORMAL, XRT_STR_LITERAL("shutdown"), Payload, sizeof(Payload), &iSize ) ) { return 1; } Input.Data = Payload; Input.Size = iSize; if ( !xrtWsCloseParse(Input, &Close) ) { return 2; } printf( "code=%u reason=%.*s\n", (unsigned)Close.Code, (int)Close.Reason.Size, Close.Reason.Data ); return 0; }
Что только что произошло. (1) CloseWrite(1000, "shutdown") даёт нагрузку 9 байтов (код 2 + причина 8) — тройное принуждение стороны письма на входе: причина легальный UTF-8, всего ≤123, код вне зарезервированной полосы. (2) CloseParse разбирает круг в xwsclose{Code, представление Reason} — код 1000 семантически «нормальное закрытие», причина для людей (журнал/аудит). (3) Эксперимент с зарезервированными кодами: попытка записать 1005/1006/1015 отказывает сразу — это «выразительные коды» уровня API (CloseInfo у Stream использует 1006 для «закрыто аварийно, кадра Close не получил»), а не отправляемые значения. Этот «отказ стороны письма» останавливает самую частую протокольную ошибку реализаторов. (4) Close слоя Stream (глава 96) внутри и есть эта пара функций — отношение слоёв: эта глава слой нагрузки, 95-я слой соединения.
Вторая полная программа: ответ согласования сжатия
Вторая программа из examples/websocket/deflate/main.c — минимальный соответствующий ответ от offer к response:
#include <stdio.h> #include <xrt.h> */ /* 展示解析客户端 offer 并构造最小合规服务端响应。 */ int main(void) { xstrview Text = XRT_STR_LITERAL( "permessage-deflate; server_max_window_bits=10; " "client_max_window_bits" ); xwsextension Extension; xwsdeflate Offer; xwsdeflate Response; char Output[XWS_DEFLATE_MAX_SIZE + 1u]; size_t iOffset = 0; size_t iSize; if ( xrtWsExtensionNext( Text, &iOffset, &Extension ) != XHTTP_NEXT_ITEM ) { return 1; } if ( !xrtWsDeflateOfferParse( &Extension, &Offer ) || !xrtWsDeflateAccept( &Offer, &Response ) || !xrtWsDeflateResponseWrite( &Response, Output, XWS_DEFLATE_MAX_SIZE, &iSize ) ) { return 2; } Output[iSize] = '\0'; printf("response=%s\n", Output); return 0; }
Что только что произошло. (1) Offer предлагает server_max_window_bits=10 (окно сжатия сервера 1 КБ) и уведомляет, что клиент может принести client_max_window_bits. (2) Ответ принимает 10 и пишет минимальное соответствующее подмножество — только необходимый параметр: permessage-deflate; server_max_window_bits=10, без эха «уведомления возможности» клиентской стороны.Почему минимально: каждый параметр, отражённый в response, — обязательство: ответили client_max_window_bits — обязались ограничить окно; больше отзовали — больше обязались — меньше манёвра. Строгая валидация слоя согласования (повтор параметров/нелегальные значения/непредложенные возможности) встроена целиком. (3) Строка ответа ложится в поле ответа Sec-WebSocket-Extensions — в композиции с ответом рукопожатия главы 94. Сопроводительные образцы deflater/inflater демонстрируют двустороннюю работу потоковых объектов, extension_tour — полный обход слоя расширений.
97.4契约
- Нагрузка закрытия: код 2 байта (сетевой порядок) + опциональная причина (UTF-8); всего ≤
XWS_CLOSE_PAYLOAD_MAX(125); сторона письма принуждает три правила, сторона чтения отвергает уродливое. - Семантические домены кодов: 1000-1003 нормальное семейство / 1008-1011 семейство ошибок / 3000-4999 прикладные / 1005/1006/1015 зарезервированы (только выражение API, выход запрещён).
- Рукопожатие закрытия: Close одноразово, противоположная сторона отвечает автоматически, тайм-аут принуждает финал, CloseInfo снимает финал (гарантия слоя соединения главы 96).
- Согласование сжатия: offer/response валидируются раздельно и строго; повтор/нелегальность/непредложенность — отказ; ответ — минимальное соответствующее подмножество;
DeflateDirectionдаёт односторонние параметры. - Параметры сжатия: window bits 9-15; context takeover един на обоих концах; no-takeover сбрасывает словарь на границе сообщений.
- Потоковые объекты: продвижение фрагментами без агрегации; вывод в обратный вызов; распаковка под двойным потолком сообщение+Inflate (оборона от бомб);
DeflaterBoundпланирует ёмкость; ясные reset/abort/destroy. - Правило RSV1: флаг сжатия только на первом кадре данных; continuation не повторяет; контрольные кадры не сжимаются никогда.
- Путь отправки: семейство
TextCompressedсоздаёт Deflater по надобности (глава 96); бюджет включает верхнюю границу сжатого вывода. - Обрезка:
WEBSOCKET_CLOSE/_DEFLATE/_DEFLATER/_INFLATER/_EXTENSIONнезависимые макросы — несжатые соединения несут ноль кода сжатия.
Типичные ошибки
Симптом: CloseWrite(1006, ...) отказывает сразу; или запихнули 200-байтовое «подробное объяснение» как причину — отказ тоже.
Причина: 1005/1006/1015 — значения API, выражающие «ситуации без кадра Close»; протокол запрещает их в сеть; потолок нагрузки 125 байтов (код 2 + причина 123). Сверхдлинная причина идёт прикладным сообщением; причина Close несёт только резюме.
xrtWsCloseWrite(1006, XRT_STR_LITERAL("abnormal"), ...); /* зарезервированный код: отказ */ xrtWsCloseWrite(1000, LongParagraph, ...); /* сверх предела: отказ */
xrtWsCloseWrite(1000, XRT_STR_LITERAL("policy: quota"), ...); /* код из семантического домена; причина одной фразой; детали - прикладным сообщением или в журнал */
Симптом: самописный ответ эхом возвращает все параметры offer — клиент инициализируется как «сервер обязался client_max_window_bits», а сервер соответствующего окна не реализовал; поток сжатия перепутан.
Причина: каждый параметр response — обязательство. Минимальное соответствующее подмножество — безопасная поза протокола: отвечайте только то, что реально исполните.
/* вернуть offer как есть */ response = offer_string; /* отозваны не обещанные возможности */
/* ответ слоем согласования: минимальное подмножество и все проверки встроены */ xrtWsDeflateInit(&Offer); parse_offer(Fields, &Offer); accept = deflate_accept(&Offer); /* только необходимые параметры */
Симптом: злая противоположная сторона шлёт 3 байта сжатого куска, заявляя 4 ГБ на выходе — распаковщик честно производит, пока OOM не убьёт.
Причина: степень сжатия — поверхность атаки. Потолок сообщения (конфигурация главы 95) и потолок Inflate как двойное ограничение — стандартная оборона стека протокола; поставив только одно, оставляешь обход (фрагментированные сообщения обходят потолок сообщения; множество сообщений — потолок Inflate).
xrtWsInflaterConfigInit(&Config); /* строить без потолка? опасно */ xrtWsInflaterCreate(&Config, NULL); feed_everything(&Infl, PeerData); /* бомба доходит до памяти */
/* потолок сообщения + потолок Inflate заданы явно (порядок по бизнесу: напр. 1МБ / 8МБ) */ MessageConfig.MaxMessage = 1024 * 1024; InflateLimit = 8 * 1024 * 1024; /* сверх предела - протокольная ошибка: ответить Close 1009 (сообщение слишком велико) и разорвать */
Практические задания
Пять экспериментов с CloseWrite/Parse: 1000 + короткая причина, 1001 + пустая причина, 1005 (зарезервированный), причина с нелегальным UTF-8, причина 130 байтов — первые три проверяют принуждение стороны письма, последние два — пути отказа. Критерии приёмки: легальные группы сходятся в обе стороны; ошибки нелегальных групп различают три причины.
Переделайте образцы deflater/inflater: сторона отправки сжимает JSON на 10 КБ в кадр RSV1 (первый кадр RSV1, фрагментация, FIN), сторона приёма распаковывает и пересобирает. Посчитайте байты до/после сжатия и пиковую память (глава 6). Критерии приёмки: круг туда-обратно даёт тот же оригинал; граница фрагментации посреди сжатых данных тоже корректна; пиковая память не зависит от размера сообщения (потоковость).
Добавьте к службе эха главы 96 всё: согласование сжатия (минимальный ответ на offer), сжатые приём/отправку (правило RSV1), протокол закрытия (оба пути — нормальный Close 1000 и отказ по политике 1008), потолок сообщения + двойной потолок Inflate, Ping/Pong (сердцебиение в том числе под противодавлением). Сразитесь с WebSocket браузера (консоль разработчика). Критерии приёмки: большое сообщение (1 МБ текста) корректно проходит сжатый круг; злой вход (сверхпотолочные сообщения/уродливые кадры/Close с зарезервированным кодом) весь отвергается по протоколу с чистым закрытием; вся цепочка нигде не буферизует целое сообщение.
Итоги
| Тема | Кратко |
|---|---|
| Нагрузка закрытия | код (2B сетевой порядок) + причина UTF-8; ≤125 байтов; три принуждения стороны письма, сторона чтения отвергает уродливое |
| Домены кодов | 1000-1003 норма / 1008-1011 ошибки / 3000-4999 прикладные / 1005·1006·1015 зарезервированы, выход запрещён |
| Рукопожатие закрытия | одноразовая отправка, автоответ, финал по тайм-ауту, снимок CloseInfo |
| Правила согласования | offer/response раздельно и строго; повтор/нелегальность/непредложенность - отказ; ответ минимальным подмножеством |
| Параметры согласования | window bits 9-15; context takeover един; no-takeover сбрасывает по сообщениям |
| Потоковые объекты | inflater/deflater продвигаются фрагментами, вывод в обратный вызов, ясные reset/abort/destroy |
| Оборона от бомб | потолок сообщения + потолок Inflate двойное ограничение; сверх предела ответ 1009 |
| Правило RSV1 | флаг сжатия только на первом кадре данных; continuation не повторяет; контрольные кадры никогда не сжимаются |
| Планирование ёмкости | DeflaterBound считает верхнюю границу до входа в бюджет SendLimit |
| Панорама | путешествие одного сообщения сцепляет API девяти глав, 89->93->94->95->96 |
📖 Справочники API в этой главе:websocket (86 записей)websocket_stream (45 записей)http (72 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки