Ориентация главы
После аутентификации (глава 124) соединение «ваше» — а сила SSH в открытии множества каналов на одном соединении: session (shell/exec/sftp), direct-tcpip (основа переадресации главы 126), собственные типы. В главе триада плоскости данных: окно (ssh_channel_window: двунаправленное управление потоком RFC 4254 — удалённое окно + max-packet ограничивают отправку, локальное окно возвращается по потреблению; без выделений и владения сетью); ядро канала (ssh_channel_core: open/confirm/failure, состояния EOF/CLOSE, фиксация данных и оконные транзакции — без собственных буферов и без транспорта; кладётся в массивы/хеши для O(1)-маршрутизации по recipient); набор каналов (ssh_channels: слой динамического владения — узлы по мере надобности, стабильные адреса, жёсткие потолки; без фиксированных массивов, ноль простоя). Оконный механизм — SSH-редакция «противодавления» — то же семейство, что бюджеты записи главы 68 и управление потоком главы 96, но двунаправленное и на каждый канал отдельно.
127.1引入
Зачем каналам окна? TCP-буферы одного SSH-соединения разделяются всем соединением — если противоположная сторона канала A не читает, TCP-окно сжимается и данные канала B тоже встают (блокировка головы очереди). Окна на уровне каналов уточняют управление потоком до каждого канала: отправитель может послать максимум «окна, выданного противоположной стороной» байтов, продолжение — только по WINDOW_ADJUST; приёмник возвращает окно по мере потребления приложением. Эффект: медленный канал затыкает только себя (исчерпывает своё окно), быстрые бегут дальше — справедливость мультиплексирования гарантирована протоколом.
Тонкое место реализации — двухфазный возврат: после потребления данных приложением квота сперва входит в ReceivePending (удалённой стороне ещё не сообщили); по достижении порога или исчерпании окна собирается WINDOW_ADJUST, и лишь после надёжной постановки сообщения в очередь исполняется AdjustCommit — «избегая увеличить локальное окно при провале отправки» (заранее оповестить = перегранть окно = сломать протокол). uint64-статистика: внутренние непотреблённые/ожидающие возврата байты считают 64-битно (долгие соединения не заворачиваются от накопления за 4 ГиБ), линейное окно строго uint32 (требование RFC).
127.2概念
Окно: состояние двунаправленного управления потоком
Старт с нулевым окном — изящная способность: начальное локальное окно можно поставить в 0 — подождать, пока приложение действительно подготовит буферы, и лишь тогда Grant — ленивая инициализация «сначала канал, потом бюджет».
Ядро канала: open и машина состояний
EOF и CLOSE разделены: EOF закрывает только одно направление данных («я закончил отправлять», но принимать могу) — семантика закрытия главы 97 в редакции каналов; CLOSE завершает оба направления. Данные после close: прежде отданное приложению остаётся потребляемым, но WINDOW_ADJUST больше не порождается (механизм возврата умирает вместе с каналом). Способность запросов запрашивается независимо (CanSendRequest/CanReceiveRequest) — запросы shell/exec/x11 на канале и плоскость данных суть две ортогональные оси способностей. Без встроенного request token: каждый канал может независимо компоновать xsshreplyqueue (корреляционная очередь want-reply) — ёмкость и место хранения определяет приложение по конкурентности; простаивающие каналы платят ноль FIFO.
Транзакции данных: фиксация приёма отделена от потребления
Прибыли data: DataReceiveCommit (фиксация до того, как приложение возьмёт представление — оконный учёт) → приложение потребляет → DataConsume (квота в Pending) → оконная транзакция возвращает. Отправка симметрична: SendLimit ограничивает длину → пакет надёжно в очереди → DataSendCommit. Разделение фиксации и потребления и есть механизация противодавления: не потребляешь = не возвращаешь = окно удалённой стороны исчерпано = удалённая сторона перестаёт слать — pause/resume главы 110 в оконной редакции, исполняемое протоколом автоматически.
Набор каналов: динамическое владение
ssh_channels хранит по локальному channel id стабильные по адресу xsshchannel (каждый = ядро + опциональный динамический I/O + FIFO ответов): создание по мере надобности (узлы отображения только при Open/Accept — пустое соединение стоит ноль, пустой канал без фиксированных буферов); стабильные адреса (безопасно заимствовать до удаления — сессии соединения/асинхронные ожидания/состояние приложения вешают указатели); жёсткие потолки (по умолчанию 1024 канала / 64 ожидающих ответа на канал / окно 2 МиБ / пакет 32 КиБ / бюджеты приёма-отправки по 2 МиБ — настраивается под нагрузку, но не полагаться на безграничный рост); можно напрямую использовать как resolveproc (O(1)-маршрутизация recipient→channel).
127.3示例
Первая полная программа: оконная триада
Программа ниже из examples/channel_window — прямые операции над состоянием управления потоком:
#include <stdio.h> #include <xssh.h> /* 演示按远端窗口和 max-packet 对待发送数据分片。 */ int main(void) { xsshchannelwindow Window; uint32 iChunk; if ( !xrtSshChannelWindowInit( &Window, 100000u, 32768u, 100000u, 32768u, 50000u ) ) { return 1; } iChunk = xrtSshChannelSendLimit(&Window); if ( xrtSshChannelSendCommit(&Window, iChunk) != XSSH_OK ) { return 1; } printf("chunk=%u remaining=%u\n", iChunk, Window.SendWindow); return 0; }
Что только что произошло. (1) xrtSshChannelWindowInit(&窗, 100000, 32768, 100000, 32768, 50000) (окно) — шесть параметров: окно отправки/max-packet отправки/окно приёма/max-packet приёма/порог возврата — способности локальной и удалённей сторон объявляются за раз. (2) xrtSshChannelSendLimit берёт первую квоту отправки — min(远端窗口 100000, 远端 max-packet 32768)=32768 (min удалённого окна 100000 и удалённого max-packet 32768): max-packet — потолок на сообщение, окно — общий объём; меньшее из двух решает размер одной отправки. (3) Печать chunk и оставшегося окна — «сколько можно отправить за раз» в цикле отправки ведёт этот запрос. Реальная последовательность (send→commit→adjust→consume→adjust-ready→adjust-commit) покрыта тестами по всем путям; образец проверяет детерминированную форму вычисления квоты.
Вторая полная программа: open-транзакция ядра канала
Вторая программа из examples/channel_core — open и объявление бюджета:
#include <stdio.h> #include <xssh.h> /* 建立一个等待 confirmation 且不携带数据缓冲的本端 channel。 */ int main(void) { xsshchannelcore Channel; if ( !xrtSshChannelCoreOpenInit( &Channel, 3u, 1024u * 1024u, 32u * 1024u, 512u * 1024u ) ) { return 1; } printf( "channel=%zu local=%u phase=%d\n", sizeof(Channel), Channel.Local, (int)xrtSshChannelCorePhase(&Channel) ); return 0; }
Что только что произошло. (1) Вход инициализации (параметры включают старт локального recipient, начальные окно/max-packet, бюджеты request) — ядро канала на стеке; вывод sizeof (то же заявление о нулевой обузе, что в главах 117–120). (2) Печать local (локальный номер) и phase (начальная стадия) — публичное состояние ядра и есть вход маршрутизации и планирования. (3) Форма open-транзакции: OpenInit → построить линейный CHANNEL_OPEN → после фиксации ждать confirmation → ConfirmationCommit входит в OPEN — плоскость данных открыта только в OPEN; запрос стадии (phase) — проверка перед отправкой. Семья образцов: channels (набор и стабильные адреса), channel_request (запросы shell/exec), channel_pty (согласование терминала), channel_io (динамический I/O staging), channel_state/channel_message (состояние и сообщения), reply_queue (want-reply FIFO).
127.4契约
- Окно без выделений: нагрузку не хранит, фиксированных буферов не ставит — представления напрямую потребителю / кольцевой буфер / очередь без копий.
- Квота отправки:
SendLimit=min(远端窗口, 远端 max-packet)(удалённое окно, удалённый max-packet);SendCommitтолько после надёжной постановки в очередь; заворот — протокольная ошибка. - Двухфазный возврат: потребление→Pending→(порог/исчерпание)→AdjustLimit→AdjustCommit после надёжной очереди — провал никогда не увеличивает окно заранее.
- Динамическая ёмкость: Grant добавляет приёмную квоту; старт с нулевым окном; уже потреблённая квота повторно не тратится.
- 64-битная статистика: внутренние байты считаются uint64 без заворота; линейное окно строго uint32 (RFC).
- open-транзакция: OpenInit→фиксация на линии→Confirmation/FailureCommit; Accept/Reject симметричны — каждый шаг продвигается только после надёжной фиксации.
- Дисциплина стадий: плоскость данных открыта только в OPEN; EOF закрывает одно направление; CLOSE оба; после close данные потребляемы, но ADJUST больше нет.
- Запросы ортогональны: CanSend/ReceiveRequest запрашиваются независимо; token компонуется по мере надобности из replyqueue (простой стоит ноль).
- Набор: создание по мере надобности, адреса стабильны (до удаления), жёсткие потолки (умолчания 1024/64/2МиБ/32КиБ/2МиБ+2МиБ), O(1)-маршрутизация.
- Без пересечения границ: ядро не трогает transport; набор не строит socket/Engine/future/очередь — слои встречаются только явными вызовами.
Типичные ошибки
Симптом: после партии противоположная сторона «застревает» — её окно исчерпано, она ждёт вашего ADJUST, а вы потребили, но логики возврата не запустили.
Причина: Consume только учитывает, не возвращает — возврат ждёт порога или исчерпания (можно и активно опрашивать AdjustReady). Забыли этот слой — управление потоком односторонне мертво.
while ( receive_commit(&Ch, Data) == OK ) { consume_app(Data); xrtSshChannelCoreDataConsume(&Ch->Core, Data.Size); /* AdjustReady не проверен: окно удалённой стороны засыхает */ }
while ( ... ) { consume_app(Data); xrtSshChannelCoreDataConsume(&Ch->Core, Data.Size); if ( xrtSshChannelCoreAdjustReady(&Ch->Core) ) { n = xrtSshChannelCoreAdjustLimit(&Ch->Core); if ( send_window_adjust(&Conn, Ch, n) ) { xrtSshChannelCoreAdjustSendCommit(&Ch->Core, n); } } }
Симптом: редкие перегранты противоположной стороны — локальное окно «увеличено», а ADJUST потерялся в провале постановки в очередь.
Причина: контракт прямо требует двух фаз — AdjustCommit только после надёжной постановки сообщения в очередь. Commit вперёд, затем провал очереди = окно уже велико, сообщение не дошло — удалённая сторона шлёт по расширенному окну, локальная проверка отвергает, протокол ломается.
AdjustCommit(&Ch, n); /* сперва расширить */ if ( !send_adjust(n) ) { /* провал: окно уже не вернуть */ }
if ( send_window_adjust(&Conn, Ch, n) ) { /* сперва надёжно в очередь */ xrtSshChannelCoreAdjustSendCommit(&Ch->Core, n); /* учитывать только при успехе */ }
Симптом: обращение к каналу после Remove/Discard падает — «стабильность» стабильного адреса имеет границу.
Причина: адреса стабильны до удаления соответствующего элемента — после очистки набора (закрытие соединения) всё недействительно. Хранить указатель канала между соединениями — вне проектного употребления.
g_SavedChannel = xrtSshChannelsGet(&Set, Id); /* сохранено между соединениями */ /* g_SavedChannel используется и после закрытия соединения и очистки набора */
очистка в callback закрытия соединения - не оставлять висячих ссылок */Практические задания
При окне 100000 / max-packet 32768 сымитировать отправку: цикл SendLimit→списание→до исчерпания окна; внедрить ADJUST и восстановить. Критерии приёмки: каждая квота — верный min; пути исчерпания и восстановления ясны; внедрённый заворот отвергается.
Сымитировать оба пути «потребили партию→порог сработал→очередь успешна/неуспешна» — проверить, что Commit продвигается только после успеха очереди (при провале Pending сохраняется, повтор возможен). Критерии приёмки: внутренние состояния Pending/окна обоих путей сходятся с контрактом.
Три канала на одном соединении (разные окна); противоположная сторона A потребляет медленно (большая задержка ADJUST), B/C нормально — качать данные конкурентно. Критерии приёмки: пропускная способность B/C не зависит от исчерпания окна A (изоляция уровня каналов); A продолжается после восстановления; маршрутизация набора без ошибок весь путь (recipient O(1)).
Итоги
| Тема | Кратко |
|---|---|
| Триада | окно (управление потоком) / ядро (open + состояния + транзакции данных) / channels (набор) |
| Квота отправки | min(удалённое окно, удалённый max-packet); Commit после очереди; заворот — ошибка |
| Двухфазный возврат | Consume→Pending→порог→очередь→Commit — провал не расширяет окно заранее |
| Старт с нулевым окном | начальное окно 0 + ленивый Grant — сначала канал, потом буфер |
| Разрядность статистики | внутреннее uint64 без заворота; линия uint32 строго по RFC |
| open-транзакция | OpenInit→фиксация→Confirm/Failure; Accept/Reject симметричны |
| EOF/CLOSE | EOF однонаправленный; CLOSE двунаправленный; после close потребляемо, но ADJUST больше нет |
| Запросы ортогональны | способность запрашивается независимо; token по мере надобности из replyqueue |
| Набор | создание по мере надобности / адреса стабильны (до удаления) / жёсткие потолки, умолчания 1024·64·2МиБ·32КиБ |
| Суть управления потоком | не потребляешь = не возвращаешь = удалённая сторона стоп — pause/resume, автоматизированное протоколом |
📖 Справочники API в этой главе:xssh-ssh_channel_core (26 записей)xssh-ssh_channel_window (10 записей)xssh-ssh_channels (16 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки