Ориентация главы
Третий проект — TCP-чат (chatd): многокомнатный чат реального времени — пользователь подключается, входит в комнату и говорит, комната рассылает, личные сообщения, досылка пропущенных сообщений по номерам после переподключения. Проектно он стоит между configd (главы 139-140: долгоживущее состояние и конкурентная передача) и pushd (глава 144: веерная рассылка и конечные состояния) — чистейший образец переплетения трёх сущностей: состояние соединения × состояние комнаты × номера сообщений. Глава наследует шаблон пяти элементов решения главы 139 (проблема / варианты / решение / обоснование / проверка) — переиспользование проектной методологии опережает переиспользование кода. Шесть доменов проектирования: модель комнаты (форма связи комнаты и участников); протокол линии (разграничение сообщений в байтовом потоке TCP — повторный выбор глав 72/91); согласованность широковещания (номера и досылка — предыстория протокола номеров pushd); машина состояний соединения (жизненный цикл рукопожатие → вход → разговор → выход); граница хранения (водораздел онлайн-памяти и офлайн-истории); ёмкость и деградация (матрица стратегий для медленных участников и полных комнат).
141.1引入
Чат — классическая ловушка «выглядит просто»: разве не «получил сообщение — переслал всем в комнате»? За каждым «просто» реальной системы стоят решения: A говорит за секунду до входа B — должен ли B это видеть (семантика момента входа: граница видимости истории); между отправкой A и получением B из комнаты вышел C — как закрыть сессию C (конечное состояние сессии); двое говорят в одну секунду — какой порядок видит комната (право упорядочивания: кто решает порядок); B оборвал связь и переподключился — что с пропущенными сообщениями (протокол досылки: курсор номеров). Ошибка в ответе на любой вопрос превращается на продуктовом уровне в «сообщения теряются», «порядок путается», «призрачные сессии» — и все они — вопросы этапа проектирования, а не кодирования. Эта глава превращает каждый вопрос в явное решение — глава реализации (глава 142) исполнит каждое.
141.2概念
Требования и критерии приёмки
Пользовательские истории: UC1 соединение и рукопожатие (TCP-соединение → кадр рукопожатия с ником → сервер подтверждает или отказывает); UC2 вход/выход (кадры join/leave — после входа приходит хвост истории); UC3 говорение с рассылкой (кадр say → вся комната получает кадр рассылки с номером); UC4 личные сообщения (кадр dm → получает только адресат); UC5 досылка при переподключении (переподключение с последним номером → сервер досылает пропущенные кадры); UC6 список присутствующих (кадр who → список участников комнаты). Приёмка: (1) шесть UC с позитивными и негативными случаями; (2) упорядочивание рассылки: согласованный по всей комнате порядок при конкурентных репликах (номера, видимые всеми участниками одной комнаты, строго монотонны); (3) корректная досылка: кадры времени разрыва доставляются после переподключения все и без повторов; (4) конечное состояние сессии: после выхода/разрыва нет призрачных кадров (кадр закрытия приходит ровно один раз); (5) медленный участник не тормозит комнату (изоляция); (6) явные пути отказа для полной комнаты и конфликта ников; (7) OOM и по-точечные отказы без следов; (8) стабильность под нагрузкой 100 конкурентных × 10 комнат.
Решение 1: модель комнаты
Проблема: каковы отношения комнаты и участников, участника и соединения? Варианты: A комната = множество соединений (управляет соединениями напрямую); B комната = множество пользователей, пользователь независим от соединения (переподключение меняет соединение, не идентичность); C без сущности комнаты (сообщения разносятся таблицей маршрутизации). Решение: B. Основания: семантика переподключения (UC5 требует сохранять идентичность и курсор номеров — идентичность нельзя привязывать к соединению); маршрутизация личных сообщений (UC4 по пользователю, не по соединению); офлайн-история (объект досылки UC5 — пользователь, не соединение). A отвергнуто: обрыв соединения = потеря идентичности — переподключение становится новым пользователем (досылка невозможна). C отвергнуто: комната — продуктовое понятие (история и список участников — атрибуты комнаты) — таблица маршрутизации без сущности их не вмещает. Проверка: тест переподключения (UC5) проходит только при B. Форма данных: Map<房间名, 房间>; комната содержит Map<用户名, 用户> + кольцо сообщений (хвост истории) + счётчик номеров; пользователь содержит указатель текущего соединения (NULL = офлайн) + курсор.
Решение 2: протокол линии
Проблема: как разграничить сообщения в байтовом потоке TCP? Варианты: A голые текстовые строки (разделитель \n); B двоичный с префиксом длины; C JSON-строки (текстовая строка + JSON-нагрузка). Решение: C. Основания: читаемость человеком (обучение и отладка — telnet может говорить напрямую); структурность (тип кадра и поля через JSON — разбор главы 32 за один шаг); расширяемость (новые поля — ноль изменений протокола). A отвергнуто: поля пришлось бы экранировать самим (ник с пробелом — взрыв); B отвергнуто: отладка требует hexdump. Реализация разграничения: строчный разметчик главы 72 (разграничение \n) + JSON-разбор главы 32 — оба слоя — готовые темы. Проверка: матричный тест протокола (прямые и обратные случаи на каждый тип кадра). Это решение — зеркало pushd (там JSON поверх WS-кадров — здесь JSON поверх строк — нагрузка одна, слой разграничения следует за транспортом — слоистость превращает проектирование протокола в выбор одного слоя).
Решение 3: упорядочивание рассылки и согласованность
Проблема: кто определяет порядок конкурентных реплик? Варианты: A порядок отправителя (локальные номера); B одноточечное упорядочивание сервера (счётчик комнаты); C векторные часы (распределённое упорядочивание). Решение: B. Основание: комната — домен полного порядка внутри одного процесса (одна точка упорядочивания на комнату) — просто и достаточно; C готовит к распределённости (смысл лишь при многих инстансах — за пределами этого проекта). Механизм: кадр реплики прибыл → внутри блокировки комнаты берётся номер (счётчик++) → кадр рассылки несёт номер → доставка по номеру. Семантика согласованности: номера, видимые всеми участниками одной комнаты, строго монотонны и без дыр (дыра = потеря — запускает досылку); курсор участника записывает наибольший увиденный номер. Протокол досылки (UC5): кадр переподключения несёт курсор → сервер пересылает кадры из кольца истории начиная с курсор+1 → вне кольца (слишком старое) — ответ "too far behind" (клиент полностью обновляется). Проверка: аудит номеров при конкурентной нагрузке (каждый клиент проверяет монотонность без дыр — критерии (2)(3)).
Решение 4: машина состояний соединения
Гарантия конечного состояния: Close соединения ровно один раз (глава 67); жизненный цикл идентичности пользователя длиннее соединения (офлайн-счёт / очистка по тайм-ауту — следствие Решения 1). Линия обороны от призрачных кадров: кадр выхода отправляется самому пользователю после выгрузки очереди рассылки («ты вышел» — последний кадр) — последующие рассылки его больше не достигают (порядок «сначала удалить, потом рассылать»).
Решение 5: граница хранения
Проблема: где хранить сообщения и сколько? Варианты: A всё в памяти (кольцевой буфер); B всё на диске; C горячее в памяти + холодное на диске. Решение: A (кольцо в памяти) + объявление границы. Основание: ёмкостное предположение учебной формы (кольцо 1024 кадра на комнату — около 100 КБ на комнату); B/C — точки эволюции, не часть этого проекта (диск = последовательная запись главы 45 + воспроизведение при старте — путь расширения оставлен). Семантика кольца: новый кадр перезаписывает самый старый; досылка возможна только внутри кольца (вне кольца = too far behind). Проверка: тест закольцовывания (отправить больше длины кольца — самый старый перезаписан, номера по-прежнему монотонны).
Решение 6: ёмкость и деградация
Проблема: что делать с медленными участниками, полными комнатами и перегрузкой сервера? Матрица решений: медленный участник (отправка AGAIN дольше порога) → выселить с уведомлением (не безграничное ожидание — чат-версия трёх уровней pushd: семантика чата не позволяет терять кадры — потеря ломает «номера без дыр» — поэтому пропуск недоступен, только выселение); полная комната (предел участников) → кадр отказа во входе (с явной причиной); перегрузка (предел всех соединений) → отказ в рукопожатии. У каждого вида отказа есть свой кадр протокола (клиент понимает и может повторить) — путь отказа — гражданин первого класса. Проверка: тест с внедрением медленного участника (изоляция и семантика выселения — критерий (5)).
Разделение труда с pushd (глава 144)
chatd и pushd похожи формой (комнаты / рассылка / номера), но ограничения разные — таблица против смешения: транспорт (голый TCP против WebSocket — chatd строит рукопожатие и разграничение сам, pushd получает их даром); идентичность (пользователь независим от соединения против «соединение и есть идентичность» — переподключение с досылкой в chatd выталкивает наружу сущность пользователя); терпимость к потерям кадров (терять нельзя — номера без дыр против «терять можно, есть resync» — уровень пропуска pushd в chatd недоступен). Вывод: chatd учит «протокол с байтового нуля», pushd — «переиспользование высокоуровневых деталей» — один и тот же бизнес в двух транспортных слоях — тренировка слоистой чувствительности проектного взгляда.
141.3示例
Первая полная программа: атом разграничения протокола линии
Программа ниже из examples/io/line — исполняемый атом слоя разграничения протокола линии (строчные кадры \n):
*/ #include <stdio.h> #include <xrt.h> int main(void) { /* 混合行尾 + 末行无换行的日志样本。 */ static const char sLog[] = "INFO server started\r\n" "WARN queue is busy\n" "ERROR request failed"; xreader* pReader; xlinereader* pLines; xlineview Line; /* 借用:到下一次 Next 前有效 */ xlinenext Next; size_t iCount = 0u; int iResult = 1; */ pReader = xrtReaderFromMemory(XRT_BYTES_LITERAL(sLog)); pLines = xrtLineReaderTake(&pReader, 1024u); if ( pLines == NULL ) { xrtReaderDestroy(pReader); return 1; } /* 逐行消费:每行是借用视图,处理完再取下一行。 */ while ( (Next = xrtLineReaderNext(pLines, &Line)) == XLINE_NEXT_LINE ) { printf("%zu: %.*s\n", ++iCount, (int)Line.Text.Size, Line.Text.Data); } if ( Next == XLINE_NEXT_END ) { iResult = 0; } /* 接管模式:一个 Destroy 连底层 Reader 一起关闭。 */ if ( !xrtLineReaderDestroy(pLines) ) { iResult = 1; } return iResult; }
Что только что произошло. (1) Трёхстатный цикл разграничения строк — это движок приёма кадров chatd: LINE прибыл → JSON-разбор → диспетчеризация; END → закрытие соединения; ERROR → структурный разрыв. (2) Адаптация концов строк (все три распознаются) позволяет подключаться telnet/nc и любым клиентам — снисходительный вход (низший слой протокола снисходителен, верхний JSON строг — каждый слой на своём месте). (3) Этот атом тот же, что цитировался в главе 139 — два проекта делят один фундамент (прямое доказательство слоистого переиспользования).
Вторая полная программа: структурность нагрузки кадров
Вторая программа из examples/data/json — атом слоя нагрузки кадров (JSON):
*/ #include <stdio.h> #include <xrt.h> */ static xjsonvisitaction printJsonEvent( const xjsonevent* pEvent, ptr pUserData ) { (void)pUserData; if ( pEvent->HasName ) { printf("member: %.*s\n", (int)pEvent->Name.Size, pEvent->Name.Data); } return XJSON_VISIT_NEXT; } int main(void) { xjsonreadconfig ReadConfig; xjsonwriteconfig WriteConfig; xjsonwriter* pWriter; xvalue* pRoot; xvalue* pName; xstrview Name; str sText; size_t iSize; /* ---- 1) DOM:解析 → 随机访问 → 美化序列化 ---- */ pRoot = xrtJsonParse(XRT_STR_LITERAL( "{\"name\":\"xrt\",\"features\":[\"json\",\"http\"]}" )); if ( pRoot == NULL ) { return 1; } /* ObjectGet 返回借用指针(树拥有它);GetString 取出视图。 */ pName = xrtValueObjectGet(pRoot, XRT_STR_LITERAL("name")); if ( !xrtValueGetString(pName, &Name) ) { xrtValueRelease(pRoot); return 2; } printf("name = %.*s\n", (int)Name.Size, Name.Data); /* Stringify 产物是拥有式字符串(xrtFree);之后树即可释放。 */ sText = xrtJsonStringify(pRoot, true, &iSize); xrtValueRelease(pRoot); if ( sText == NULL ) { return 3; } printf("%.*s\n", (int)iSize, sText); xrtFree(sText); /* ---- 2) SAX:同一份输入按事件流走一遍,不建树 ---- */ xrtJsonReadConfigInit(&ReadConfig); if ( xrtJsonVisit( XRT_STR_LITERAL("{\"code\":200,\"ok\":true}"), &ReadConfig, printJsonEvent, NULL ) != XJSON_VISIT_DONE ) { return 4; } /* ---- 3) Writer:增量构建 {"code":200,"message":"OK"} ---- */ xrtJsonWriteConfigInit(&WriteConfig); pWriter = xrtJsonWriterCreate(&WriteConfig); if ( (pWriter == NULL) || !xrtJsonWriterObject(pWriter) || /* 开对象 */ !xrtJsonWriterName(pWriter, XRT_STR_LITERAL("code")) || !xrtJsonWriterInt(pWriter, 200) || !xrtJsonWriterName(pWriter, XRT_STR_LITERAL("message")) || !xrtJsonWriterString(pWriter, XRT_STR_LITERAL("OK")) || !xrtJsonWriterEnd(pWriter) || /* 闭对象 */ !xrtJsonWriterFinish(pWriter) /* 完整性校验 */ ) { xrtJsonWriterFree(pWriter); return 5; } /* Take 移交文本(xrtFree 释放);Take 后 writer 仍需 Free。 */ sText = xrtJsonWriterTake(pWriter, &iSize); xrtJsonWriterFree(pWriter); if ( sText == NULL ) { return 6; } printf("%.*s\n", (int)iSize, sText); xrtFree(sText); return 0; }
Что только что произошло. (1) DOM-разбор даёт доступ к полям (форма запроса member: code/ok) — прямой словарь диспетчеризации кадров chatd (маршрутизация по полю op — изоморфна on_message pushd). (2) Сериализационный круговорот (Writer производит корректный JSON) — форма генерации кадров рассылки. (3) Два атома (разграничение строк + JSON) вместе — весь протокол линии — репетиция реализации Решения 2: две готовые темы собирают один протокол — ноль собственного кода протокола.
Сводная таблица проектных решений
Одностраничный обзор шести решений (для ревью) — одна строка на домен, пять элементов сжаты в три колонки:
| Домен | Решение | Ключевые основания и проверка |
|---|---|---|
| Модель комнаты | пользователь независим от соединения | переподключение сохраняет идентичность (UC5 проходит); двухслойный Map |
| Протокол линии | разграничение строк + JSON | читаемость + структурность + ноль собственного разграничения |
| Упорядочивание | одноточечный номер комнаты | домен полного порядка в одном процессе; номера монотонны без дыр |
| Машина состояний | шесть состояний, включая офлайн | период идентичности длиннее соединения |
| Хранение | кольцо в памяти 1024 кадра | учебное ёмкостное предположение; диск — эволюция |
| Деградация | два уровня: выселение/отказ + кадры протокола | «номера без дыр» запрещает пропуск; отказы понятны |
Эта таблица — индекс сверки для главы реализации (глава 142): колонка «проверка» каждой строки — пункт тестового списка к моменту завершения реализации.
Методологическое сопоставление с проектной главой 138
Сопоставление двух исполнений шаблона проектных глав — проверка самой методологии: пять элементов решения в главе 138 развёрнуты тяжело (по разделу на решение), здесь сжаты в таблицу и разделы — степень детализации следует спорности решения (у настоящей множественности вариантов — разворот, у очевидного направления — сжатие — объём служит устранению двусмысленности); список приёмки прежде в обеих главах одинаков (шесть против восьми пунктов — здесь добавились ёмкость и нагрузка — у проекта с более высокой конкурентной плотностью приёмка естественно больше); сохранение отвергнутых альтернатив (в главе 138 шаблонизатор оставил точку расширения, здесь диск оставил точку эволюции — отвержение не уничтожает: когда основание исчезнет, путь назад остаётся). Три переносимых урока шаблона: шаблон — скелет, не таблица — порядок пяти элементов (проблема прежде) важнее формата; у каждого решения — проверка — решение без схемы проверки это желание, а не решение; решения взаимно проверяются («независимость идентичности» здесь определила форму «протокола досылки» — изолированные, каждое само по себе оптимальные решения могут противоречить в целом — главная работа проектного ревью и есть поиск таких пересечений).
141.4契约
- Пять элементов решения: у каждого домена проектирования полны проблема/варианты/решение/обоснование/проверка — глава реализации исполняет построчно.
- Модель комнаты: пользователь независим от соединения (переподключение сохраняет идентичность и курсор); комната = множество пользователей + кольцо сообщений + счётчик упорядочивания.
- Протокол линии: разграничение строк (снисходительное) + JSON-нагрузка (строгая) — два готовых слоя.
- Упорядочивание: одноточечное в комнате; номера строго монотонны без дыр; курсор записывает увиденное.
- Протокол досылки: переподключение с курсором → досылка внутри кольца, вне кольца too far behind.
- Конечное состояние сессии: после кадра выхода нет призрачных кадров (сначала удалить, потом рассылать); Close ровно один раз.
- Жизненный цикл идентичности: длиннее соединения; офлайн-очистка по тайм-ауту (настраивается).
- Граница хранения: кольцо в памяти (1024 кадра на комнату) + диск зарезервирован как эволюция.
- Матрица деградации: медленного участника выселить (пропуск запрещён — номера без дыр) / полную комнату — отказ / при перегрузке — отказ в рукопожатии; у каждого отказа — кадр протокола.
- Разделение с pushd: собственный протокол на голом TCP против переиспользования высокоуровневых деталей на WS — тренировочная пара одного бизнеса на двух транспортах.
Совместимость при эволюции протокола
Резервирование версионности протокола линии (упражнение в дальновидности на этапе проектирования): поле версии (кадр рукопожатия несёт "v":1 — сервер диспетчеризует разбор по версии — когда v2 добавит поля, клиент v1 не сломается: JSON-разбор естественно снисходителен к неизвестным полям — ещё один дивиденд выбора JSON); согласование возможностей (ответ на рукопожатие возвращает список возможностей сервера — клиент урезает функции по возможностям — who из UC6 может эволюционировать в постраничность, не ломая старых клиентов); путь деградации (неизвестный op — кадр "unknown op" без разрыва соединения — тот же рецепт, что снисходительность сообщений главы 140). Три пункта вместе — около двадцати строк проекта — покупают протоколу возможность эволюции вместо всеобщей синхронной смены версий при каждой правке. Срок жизни протоколов реальных IM-систем измеряется десятилетиями — совместимость — первое требование к протоколу, функциональность — второе.
Тестовые двойники и имитация клиентов
Тестовая стратегия chatd (определена на этапе проектирования, исполняется на этапе реализации): двойник клиента — скриптованный виртуальный пользователь (подключение → рукопожатие → вход → реплики / приём кадров / аудит курсора) — N двойников водит исполнитель главы 59; конфигурация формы сервера — нормальный / внедрение медленного / полная комната / закольцовывание — четыре серверных поведения тестовой матрицы; аудитор номеров — двойник при приёме кадра проверяет монотонность без дыр (у каждого клиента встроен проверщик критерия (2)); внедрение разрывов — двойник активно рвёт связь после заданного номера кадра (тест досылки) и пассивный разрыв (тест конечного состояния). Корректность двойников опирается на общую с настоящим клиентом библиотеку протокола (двойник — не вторая реализация — тестовая версия «нет второй реализации» главы 130).
Типичные ошибки
Симптом: после разрыва и переподключения появляется новый пользователь — досылка истории невозможна (старый курсор не находит хозяина); адресат личного сообщения при разрыве теряется (после его переподключения маршрут не находит).
Причина: соединение принято за идентичность. TCP-соединение — носитель сессии, а не сама сессия — семантика переподключения требует независимости идентичности от носителя (ядро Решения 1).
struct user { xnetstream* Conn; ... }; /* обрыв Conn = пользователь исчез: курсор и связи с комнатой теряются */
struct user { xnetstream* Conn; /* NULL = оффлайн */ uint64 Cursor; /* самый поздний увиденный номер — независимо от соединения */ ... /* поля идентичности */ }; /* переподключение: новый Conn цепляется к старому user — курсор непрерывен */
Симптом: два клиента видят одну и ту же партию сообщений в разном порядке (планировка их потоков приёма различна) — логика диалога ломается (ответ появляется раньше вопроса).
Причина: «порядок обработки сервером» принят за «порядок комнаты». Упорядочивание обязано брать номер явно (счётчик++ внутри блокировки) и номер становится основанием доставки — порядок прибытия лишь физическое совпадение, не семантическое обещание.
on_say(user, msg) { broadcast(room, msg); /* прямая отправка в порядке прибытия: порядок вне блокировки не гарантирован */ }
on_say(user, msg) { uint64 seq; lock(room); seq = room->Next++; unlock(room); broadcast(room, frame(seq, msg)); /* доставка с номером */ }
Симптом: клиент после выхода всё ещё изредка получает сообщения комнаты — сессия закрыта нечисто; хуже — после переподключения загрязнение старыми кадрами.
Причина: перепутан порядок удаления участника и рассылки (сначала рассылка, потом удаление — эта партия рассылки всё ещё достигает вышедшего).
broadcast(room, "user left"); /* сперва широковещание */ remove_member(room, user); /* потом удаление: эта партия рассылки включает вышедшего */
remove_member(room, user); /* сперва удаление */ send_to(user, "you left"); /* последний кадр самому пользователю (ровно один раз) */ broadcast(room, "user left"); /* рассылка комнате не включает вышедшего */
Словарь типов кадров, финализированный проектом (протокольный авторитет главы реализации): восходящие (рукопожатие / join / leave / say / dm / who / quit — у каждого таблица обязательных полей); нисходящие (ok / err / broadcast / dm / history_tail / too_far / you_left / member_list — у каждого поля и правило появления номеров); двусторонняя возможность (квитанция unknown на неизвестный op). Словарь в форме трёхсегментной таблицы (тип кадра / направление / поля + ограничения) — идея «данных как данных» из файлов ожиданий главы 140, применённая к протоколу. Критерий приёмки: каждая последовательность кадров шести UC находит в словаре все свои типы; err ошибочных ветвей несёт локализуемый код причины.
Практические задания
Выпишите для каждого из шести UC полную последовательность кадров (что отправляет клиент, что отвечает сервер — включая ошибочные ветви) — типы JSON-кадров таблицей. Критерии приёмки: у каждого UC по прямому и обратному случаю; поля кадров минимально полны (правила появления op / обязательных параметров / поля номера).
Нарисуйте три последовательности: двое говорят конкурентно (взятие номера и доставка переплетаются — согласовано по всей комнате); переподключение после разрыва в 5 кадрах (курсор + кольцо); отказ досылки после закольцовывания (too far behind). Критерий приёмки: цепочку номеров каждой последовательности можно вывести вручную — без дыр и повторов.
Оценка: 100 комнат × 50 пользователей × кольцо 1024 кадра × в среднем 200 байт/кадр — общий объём памяти; буфер на соединение при 1000 конкурентных соединениях (глава 65) — общая таблица бюджета. Дайте связь рекомендации по потолку ёмкости и порога отказа при перегрузке. Критерии приёмки: у каждого пункта бюджета есть источник (ссылка на главу); порог и бюджет согласованы.
Итоги
| Тема | Кратко |
|---|---|
| Шаблон проекта | пять элементов решения (проблема/варианты/решение/обоснование/проверка) — повторное использование шаблона главы 139 |
| Модель комнаты | пользователь независим от соединения; комната = множество пользователей + кольцо сообщений + упорядочиватель |
| Протокол линии | разграничение строк (снисходительный вход) + JSON-нагрузка (строгая структура) — два готовых слоя |
| Упорядочивание | одноточечный номер в комнате; номера монотонны без дыр; курсор записывает увиденное |
| Досылка | переподключение с курсором → досылка внутри кольца; вне кольца too far behind — полное обновление |
| Конечное состояние сессии | сначала удалить, потом рассылать; завершающий кадр самому — ровно один раз |
| Период идентичности | длиннее соединения; офлайн-очистка по тайм-ауту |
| Хранение | кольцо в памяти + диск как эволюция; закольцовывание перезаписывает старейшее |
| Деградация | медленного — выселить (пропуск запрещён) / полную — отказ / перегрузку — отказ; у отказа есть кадр |
| Разделение с pushd | собственный протокол на голом TCP против WebSocket с готовыми деталями — тренировочная пара одного бизнеса на двух транспортах |
📖 Справочники API в этой главе:core (25 записей)value (159 записей)net (247 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки