Организация каталога приложения: одна единица трансляции и изоляция по поколениям
Каталоги C-проектов можно организовывать как угодно. Эта глава даёт проверенные в demo-single соглашения — это не обязательный стандарт, но они лучше всего сцеплены с семантикой изоляции поколений горячей перезагрузки.
Обзор
5.1 Введение
Первый вопрос нового приложения XServer — не «как написать обработчик», а «как разложить файлы». В C нет пакетного менеджера, который прочертит границы: пути include, видимость символов, разбиение единиц компиляции — всё на самодисциплине. Беспорядок в организации в сценарии горячей перезагрузки усиливается: перезагружается вся единица трансляции, и расположение любого файла влияет на компиляцию каждого поколения.
demo-single даёт лёгкие соглашения: каталог — это ответственность, одна единица трансляции, самодостаточные модули. На них держится полное приложение с 19 маршрутами, SQLite, шаблонами и контролируемыми страницами, и они выдержали нагрузку routebench с десятью тысячами маршрутов — соглашения хватает по масштабу.
5.2 Каталожные соглашения
myapp/
├── xs.exe /* единственный бинарник (хост) */
├── xs.json /* факты сборки: сервисы/порты/TLS/vhost */
├── main.c /* вход приложения: include-оркестровка + жизненный цикл */
├── route.h /* реестр маршрутов: URI × метод → обработчик */
├── includes/ /* dev_inc: дополнительный путь поиска заголовков скрипта */
├── librarys/ /* dev_lib: сторонние библиотеки (#pragma comment) */
├── modules/ /* переиспользуемые модули: http / db / template / resource / protocol */
├── route_http/ /* обработчики запросов: файл на ресурс */
├── page/ /* контролируемые страницы: вне wwwroot, без прямых ссылок */
├── template/ /* шаблоны xrtTemplate */
├── db/ /* файлы данных SQLite */
└── wwwroot/ /* корень статики (единственный каталог, доступный по URL) */
| Каталог | Ответственность | Критерий |
|---|---|---|
modules/ | Переиспользуемые возможности вне бизнеса | Можно скопировать в другое приложение как есть |
route_http/ | Бизнес-обработчики | Ссылается на modules, его никто не ссылается |
page/ template/ | Материалы динамического контента | Только через скрипт, недоступны по URL |
wwwroot/ | Статика | Единственный каталог, доступный по URL |
db/ | Персистентные данные | Перезагрузка его не трогает, общий для поколений |
Обратите внимание на контраст двух последних строк: wwwroot — «доступен по URL», db — «межпоколенческий» — соглашения кодируют одновременно границу доступа и границу жизненного цикла.
5.3 Оркестровка одной единицы трансляции
Всё приложение сливается в одну единицу трансляции: main.c подключает всё в порядке зависимостей. Оркестровка демо:
/* 1. контракт SDK */
#include <xsbase.h>
/* 2. переиспользуемые модули (снизу вверх) */
#include "modules/resource.h"
#include "modules/http.h"
#include "modules/database.h"
#include "modules/template.h"
#include "modules/protocol.h" /* каркас маршрутизации: последним, зависит от всех выше */
/* 3. бизнес-обработчики */
#include "route_http/basic.h"
#include "route_http/item.h"
/* 4. реестр маршрутов (ссылается на все обработчики) */
#include "route.h"
Почему одна единица трансляции безопасна
- Стоимость компиляции удерживается: TCC быстр; полная компиляция входа демо в 2,6 КБ — одна строка лога
script loaded (2.6 KB gen N); приложение routebench с десятью тысячами маршрутов компилируется так же мгновенно. - Простейший мир символов: нет меж-.c границ линковки; видимость static- и глобальных функций очевидна; нет скрытой ямы «в двух единицах компиляции по одноимённому глобалу».
- Естественное сцепление с изоляцией поколений: смена поколения = перекомпиляция всей единицы; все статики обновляются комплектом, не бывает сборки «наполовину новое, наполовину старое».
Статики изолированы по поколениям
Это важнейшая семантика данных соглашений. Исходник модуля маршрутизации демо говорит прямо:
/* Горячая перезагрузка создаёт новый набор статических переменных;
* старое поколение выполняет ServiceUnit только после ухода всех запросов. */
Иначе говоря: таблица маршрутов в route.h, дескриптор G_DB в database.h — всё это состояние поколения. После смены новое поколение стартует с чистой статической области (ServiceInit заново строит таблицу маршрутов, переоткрывает базу), а комплект старого уничтожается при обнулении ссылок. То, что должно пережить смену (счётчики соединений, кэши), передаётся явно через ServiceSwap (глава 28).
5.4 Паттерн переиспользования modules
Хороший модуль отвечает требованию «скопировал в другое приложение — и работает». Возьмём ReplyJSON из modules/http.h как пример границы:
/* ReplyJSON лишь заимствует pObject; xrtValueRelease остаётся за вызывающим. */
static bool ReplyJSON(XS_HttpReq* pReq, uint16 iStatus, const xvalue* pObject)
{
/* сериализация → ReplyRaw пишет заголовок и тело → владение оговорено в комментарии */
...
}
Что только что произошло — все три признака приличного модуля в пяти строках:
- Зависимость только вниз: xsbase.h + xrt, ничего из route_http;
- Комментарий владения: первая строка явно говорит «заимствует, не забирает» — вызывающему не нужно гадать про освобождение;
- static-функции: в одной единице трансляции static = «приватно для модуля», без засорения пространства имён приложения.
5.5 Контракт
- Зависимости: порядок include = порядок зависимостей (SDK → modules → route_http → route.h); между модулями тоже строго однонаправленно.
- Именование: в одной единице трансляции все static-символы делят общее пространство имён — символы модуля несут префикс модуля (например
RouteHTTP_,DB_,Template_). - Жизненный цикл: статики принадлежат поколению; файловые ресурсы (db/, page/, template/) общие для поколений и при смене не пересоздаются.
Частые ошибки
- Симптом
- Компиляция скрипта падает сразу: ошибка повторного определения.
- Причина
- В одной единице трансляции все .h разворачиваются в одну область видимости; одноимённые static-функции неизбежно конфликтуют — это не проблема линковщика, взрыв происходит на компиляции.
/* modules/a.h */ static void Init(void);
/* modules/b.h */ static void Init(void);
/* main.c включает оба → ошибка компиляции */static void A_Init(void);
static void B_Init(void); /* префиксы модулей */- Симптом
- После горячей перезагрузки счётчики обнуляются, кэши исчезают — «я же не перезапускал».
- Причина
- Статики изолированы по поколениям: смена поколения = новый комплект. Это не баг, а семантика — но если вы ждали продолжения, вы попали.
static uint64 G_iTotal; /* всего запросов
* после перезагрузки = 0, «данные пропали» *//* экспорт через ServiceSwap → возврат через xsSwapTake
* (полная реализация в главе 28) */- Симптом
- Прямой переход по пути файла даёт 404 — «файл же лежит».
- Причина
- По URL доступен только wwwroot; page/ — контролируемый каталог, чтение только через LoadPage в скрипте (глава 19).
/* страницу загрузок положили в page/download.html
* и ждёте /download.html *//* публичные ресурсы → wwwroot/
* контролируемые → page/ + LoadPage в маршруте */Упражнения
По критериям 5.2 проверьте приложение из упражнения главы 4: у каждого ли файла однозначное место? Нет ли смешения (шаблоны и статика в одном каталоге)?
Создайте modules/counter.h с Counter_Inc() / Counter_Get(): префикс модуля, зависимости только от стандартной библиотеки, полные комментарии владения. Вызовите из двух разных обработчиков.
Накапливайте счётчик запросов модулем counter; после рестарта убедитесь в обнулении; затем прочитайте объявление ServiceSwap демо (XS_ServiceSwapProc в sdk/xsbase.h) и набросайте межпоколенческую передачу счётчика (достаточно псевдокода).
Итоги
| Тезис | Вывод |
|---|---|
| Каталог = ответственность | modules (переиспользование) / route_http (бизнес) / page·template (контроль) / wwwroot (публикация) / db (между поколениями) |
| Одна единица трансляции | main.c подключает всё по порядку зависимостей; стоимость компиляции TCC удерживается |
| Изоляция поколений | Статики принадлежат поколению; продолжение жизни — только через ServiceSwap |
| Признаки модуля | Зависимость вниз, комментарии владения, префиксные static-символы |
| Граница доступа | По URL — только wwwroot; остальное через скрипт |
- справочник xrt · template — полный API шаблонизатора из каталога template/
- справочник xrt · map — структура данных под таблицей маршрутов modules/protocol.h