Ориентация главы
Серия SSH завершается — детали предыдущих шести глав собираются здесь. ssh_client компонует ядро/потоковую сессию/динамические каналы поверх предоставленного вызывающим xnetstream: не создаёт скрытый Engine, не блокирует Worker, не преаллоцирует каналы или буферы сообщений, не вводит вторую машину состояний (четыре «не» — SSH-редакция «нет второй реализации»). Жизненный цикл соединения: после ClientInit таблица событий и указатель данных уходят прямо в xrtNetStreamConnect (или, на уже построенном потоке, ClientAttach) — с открытием TCP ReadyTimeout (умолчание 30 секунд) единообразно ограничивает обмен версиями → KEX → доверие хоста → аутентификацию; событие Ready — единственная точка публикации SSH-пригодности. Три точки взаимодействия: доверие хоста (по умолчанию отказ — после DEFER событие HostKey отдаёт право решения), аутентификация (событие Authenticate при NEED_MORE у провайдера), неизвестные расширения протокола (callback Packet сохраняет полную способность обработки). Слой удобства Dial (DNS/Happy-Eyeballs/тайм-аут/отмена) обрезается независимо — прокси и собственные транспорты идут через Attach без ограничений.
129.1引入
Трудность «слоя финальной сборки» — закрытие ответственностей: всё состояние соединения (версия/KEX/аутентификация/каналы/переадресация) по главам деталей имеет своих хозяев; клиент нанизывает их в одно событийное целое — но поведенческие границы каждого слоя неизменны (ядро остаётся тем же ядром, окно тем же окном). Клей закрытия — таблица событий (конфигурационная структура events клиента): Ready/Error/Close/HostKey/Authenticate/Data/Drain/Global/Packet — вся поверхность взаимодействия приложения; изоморфна событийной цепочке HTTP-сервера главы 104.
Двухслойное сцепление противодавления стоит посмотреть сперва: при AGAIN TCP-очереди полный шифрованный пакет остаётся в единственном владении транспорта, а новые входы SSH приостанавливаются; callback-и LowWater/Drain сперва повторяют внутренний пакет, и лишь после фиксации внутренней транзакции и действительного осушения TCP-очереди публикуется Drain клиента — новые сообщения, отправленные приложением внутри Drain, не обгонят прежде удержанный пакет (гарантия порядка — корень корректности туннелей переадресации). OOM так же точен: провал внутреннего резервирования DATA ставит соединение на HOLD; после освобождения памяти — PacketRetry — «OOM не фиксирует полупакет».
129.2概念
Соединение и бюджет тайм-аутов
Три точки взаимодействия
- Доверие хоста: по умолчанию отказ — после возврата HostKey ядром DEFER приложение получает момент решения через событие
HostKeyи отвечаетxrtSshClientHostKeyAccept/Reject(двухшаговое доверие глав 122/123 в клиентской интерактивной форме — событие не синхронное суждение внутри callback: можно сверить хранилище, спросить пользователя, проверить по внешнему каналу и решить асинхронно). - Аутентификация: возврат провайдером NEED_MORE вызывает событие
Authenticate— когда учётные данные готовы (спросили пользователя/взяли у агента), продолжаетxrtSshClientContinue(асинхронный уст методов главы 124). - Аварийный выход Packet: callback
Packetисполняется до фиксации низлежащей read-транзакции — полная способность обработки неизвестных расширений протокола и собственных типов каналов (разбор переадресации главы 126 висит ровно здесь) — клиент неизвестных сообщений не глотает.
Пути данных и управляющих сообщений
xrtSshClientSend — быстрый путь нагрузки: добавляется только SSH-кодирование пакета + один перенос владения в TCP-очередь (ноль транзитных копий). xrtSshClientBuild даёт растущее с нуля, переиспользуемое, с жёстким потолком непрерывное scratch для мутирующих управляющих сообщений (буфер построения собственных сообщений). Стандартные DATA/stderr сперва входят в staging канального I/O, после фиксации стреляет событие Data (приложение проверяет без копий или читает явно). Глобальные ответы: динамический ограниченный FIFO GlobalReplies/ReplyReserve в паре с событием Global (токен-корреляция — уже видели в главе 125).
Каналы и финал жизненного цикла
xrtSshClientChannelOpen — базовый вход для собственных типов каналов: вызывающий пишет поля типа, клиент ведает динамической нумерацией/окнами/корреляцией ответов/фиксацией/откатом — классический session и helper direct-tcpip построены на нём (вторая набор-состояний каналов не ведётся). ChannelFlush строит из очереди отправки канала куски, совместно ограниченные «удалённым окном + max-packet + TCP-противодавлением» — слияние трёх бюджетных слоёв. Финал: OwnsChannel проверяет принадлежность (не отдать чужое соединение в очередь отправки); Drain/Abort идут по семантике главы 110.
129.3示例
Первая полная программа: заявление об отсутствии скрытого рантайма
Программа ниже из examples/client — форма ресурсов минимального клиента:
#include <stdio.h> #include <xssh.h> /* 展示客户端由外部 Engine 和 Stream 驱动,不创建隐藏运行时。 */ int main(void) { xsshclientconfig Config; xsshclient Client; if ( !xrtSshClientConfigInit(&Config) || !xrtSshClientInit(&Client, &Config, NULL, NULL) ) { return 1; } printf("state=%d channels=%zu\n", (int)xrtSshClientState(&Client), Config.Channels.MaxChannels); return xrtSshClientClear(&Client) ? 0 : 1; }
Что только что произошло. (1) xrtSshClientConfigInit + xrtSshClientInit(&Client, &Config, NULL, NULL) — клиент на стеке; печатаются xrtSshClientState (начальное состояние) и Channels.MaxChannels (умолчание 1024). (2) Заголовочный комментарий — проектный манифест: «управляется внешним Engine и Stream, скрытый рантайм не создаёт» — транспортное ядро главы 121 «без буферов», KEX главы 122 «без socket», здесь «без Engine» — все семь слоёв с нулём скрытого; прозрачность сборки есть накопление прозрачности деталей. (3) Финал — ClientClear: очистка неподключённого клиента без обузы. Реальная сборка (таблица событий в NetStreamConnect) — в образцах client_session/client_core.
Вторая полная программа: конфигурация слоя Dial
Вторая программа из examples/client_dial — бюджеты перед соединением:
#include <stdio.h> #include <xssh.h> /* 展示 SSH Dial 与 TCP Dial 共享同一套可裁剪连接策略。 */ int main(void) { xnetdialconfig DialConfig; xsshclientconfig ClientConfig; xsshclient Client; xrtNetDialConfigInit(&DialConfig); DialConfig.Timeout = 10000; DialConfig.FallbackDelay = 250; if ( !xrtSshClientConfigInit(&ClientConfig) || !xrtSshClientInit(&Client, &ClientConfig, NULL, NULL) ) { return 1; } printf("dial-timeout=%llu fallback=%llu state=%d\n", (unsigned long long)DialConfig.Timeout, (unsigned long long)DialConfig.FallbackDelay, (int)xrtSshClientState(&Client)); return xrtSshClientClear(&Client) ? 0 : 1; }
Что только что произошло. (1) Timeout=10s/FallbackDelay=250ms у xrtNetDialConfigInit — бюджет стороны TCP (DNS/установление/откат Happy-Eyeballs), двухуровневая независимость от ReadyTimeout (сторона SSH): TCP 10 секунд + рукопожатие и аутентификация 30 секунд; суммарный бюджет ясен и настраиваем (два рода дедлайнов главы 99 в SSH-редакции). (2) Callback завершения Dial означает лишь финал TCP — пригодность SSH по-прежнему публикует только Ready (семантика callback не подменяется). (3) Прокси/собственные транспорты Dial не ограничены — путь ClientAttach всегда открыт (стык комбинации «прокси-туннель главы 70 + SSH»). Семья: client_core (сборка ядра + аутентификации паролем), client_session (поток сессии), client_future (форма Future), client_auth_ed25519 (аутентификация открытым ключом), client_pty (согласование терминала).
129.4契约
- Четыре «не»: без скрытого Engine / без блокировки Worker / без преаллокации каналов и буферов сообщений / без второй машины состояний.
- Сборка соединения: таблица событий уходит в NetStreamConnect; готовый поток делает
ClientAttachна Worker-владельце; scratch привязывается к пулу буферов Worker после открытия TCP. - Двухуровневый тайм-аут: ReadyTimeout (умолчание 30с, ноль отключает) ведает версией→KEX→доверием→аутентификацией; Timeout у Dial ведает DNS/установлением — независимо.
- Ready единственно: состояние пригодности SSH публикуется только событием Ready; callback завершения Dial ≠ SSH готов.
- Хост по умолчанию на отказ: DEFER → событие HostKey → Accept/Reject решаются асинхронно.
- Асинхронный уст аутентификации: NEED_MORE → событие Authenticate → Continue.
- Аварийный выход Packet: исполняется до фиксации read-транзакции; полная способность для неизвестных расширений и собственных каналов; клиент неизвестных сообщений не глотает.
- Пути отправки: Send = быстрый путь без транзита; Build = переиспользуемый scratch; слияние трёх бюджетов в ChannelFlush.
- Порядок Drain: внутренние пакеты повторяются первыми; публикация лишь после фиксации транзакции и осушения очереди — новые сообщения не обгоняют.
- Точность OOM: полупакет не фиксируется; провал резервирования — HOLD; после освобождения PacketRetry.
- Глобальные ответы: динамический ограниченный FIFO + токен-коррелированное событие Global.
Типичные ошибки
Симптом: в callback Dial сразу открывается канал — ошибка или падение; SSH ещё рукопожатается.
Причина: callback Dial означает лишь, что TCP поднялся; версия/KEX/доверие/аутентификация дальше — Ready и есть сигнал готовности.
on_dial_done(...) { xrtSshClientChannelOpen(Client, "session", ...); /* посреди рукопожатия: ошибка */ }
on_dial_done(...) { /* это только TCP - ждать Ready */ } on_ready(...) { /* открывать каналы/слать запросы переадресации только здесь */ xrtSshClientChannelOpen(Client, "session", ...); }
Симптом: соединение застревает и в конце ReadyTimeout — событие никто не Accept/Reject-нул.
Причина: событие HostKey — точка решения, требующая ответа (отказ по умолчанию — защита от «не настроена политика доверия», а не «событие можно игнорировать»). После сверки хранилища/вопроса пользователю обязательно ответьте.
on_host_key(...) { audit_log(Key); /* записано, но не отвечено: соединение висит до тайм-аута */ }
on_host_key(...) { audit_log(Key); if ( known_hosts_check(Key) == TRUST ) { xrtSshClientHostKeyAccept(Client); } else { xrtSshClientHostKeyReject(Client); } }
Симптом: при целях в десятки тысяч конкурентности потолок 1024 каналов незаметно перехватывает — половина соединений не может открыть новые каналы; или наоборот — на встраиваемых целях нетронутые бюджеты давят память.
Причина: дефолты вроде Channels.MaxChannels — универсальные значения (контракт набора главы 125: «настраивать под нагрузку, на безграничный рост не полагаться») — параметры развёртывания не частное дело библиотеки.
xrtSshClientConfigInit(&Config); /* всё по умолчанию в продакшн */
xrtSshClientConfigInit(&Config); Config.Channels.MaxChannels = my_model.channels; Config.Channels.ReceiveWindow = my_model.window; Config.GlobalReplyLimit = my_model.global_tokens; /* объявить явно по модели нагрузки - бюджет есть контракт развёртывания */
Практические задания
Против реального SSH-сервера (локальный sshd или контейнер) напечатать последовательность событий целого соединения: Dial→(TCP)→версия→KEX→HostKey→аутентификация→Ready→канал→Close. Критерии приёмки: последовательность сходится с диаграммой этой главы; двухуровневые тайм-ауты срабатывают каждый независимо (эксперименты с короткими значениями).
В событии HostKey запустить асинхронную проверку (имитация внешней сверки с задержкой 200 мс) и затем Accept — проверка способности события решать асинхронно. Контраст с путём немедленного синхронного отказа. Критерии приёмки: тайминг Ready/Close обоих путей верен; соединение живёт на протяжении решения (без тайм-аута).
Упражнение финальной сборки: Dial + аутентификация паролем + HostKey (интеграция known_hosts) + локальная переадресация (форма -L) + 3 конкурентных канала + грациозный Drain — пригодный мини ssh -L. Критерии приёмки: HTTP-запросы успешно ходят через туннель туда-обратно; три канала конкурентно без помех; Ctrl-C запускает Drain, вся цепочка выходит чисто (сверка бюджетов и ссылок).
Итоги
| Тема | Кратко |
|---|---|
| Четыре «не» | без скрытого Engine / без блокировки Worker / ноль преаллокаций / без второй машины состояний |
| Сборка | таблица событий в NetStreamConnect или Attach на Worker-владельце |
| Двухуровневый тайм-аут | ReadyTimeout (весь путь SSH, умолчание 30с) независимо от Timeout у Dial |
| Ready единственно | пригодность SSH публикует только Ready — завершение Dial ≠ готовность |
| Доверие хоста | отказ по умолчанию; DEFER → событие HostKey → Accept/Reject с обязательным ответом |
| Аутентификация асинхронно | NEED_MORE → Authenticate → Continue |
| Аварийный выход Packet | исполняется до фиксации; неизвестные сообщения не глотаются — способность собственных каналов |
| Отправка | Send без транзита / Build scratch / Flush сливает три бюджета |
| Порядок Drain | внутренние пакеты первыми; публикация при осушении — новые сообщения не обгоняют |
| Глобальные ответы | ограниченный FIFO + токен для события Global |
| Карта сборки | 117 транспорт → 118 KEX → 119 доверие → 120 аутентификация → 121 каналы → 122 переадресация → 123 клиент |
📖 Справочники API в этой главе:xssh-ssh_client (31 записей)xssh-ssh_client_core (9 записей)xssh-ssh_session_stream (14 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки