第 2 章 / 共 51 章
卷一 · 起步与心智模型

安装与首次运行:构建、启动日志、第一个请求

从零到发出第一个请求只需要两样东西:一个 GCC 和一个 Python。本章所有日志与响应都是 demo-single 的实机运行结果——你可以逐字复现。

本章导读

站位:本章目标不是"装完",而是读懂启动日志——它是你日后排错的第一手证据(第 6 章会用到本章建立的日志解读能力)。已完成过构建的读者可直接跳到 2.3。

2.1 引入

部署一个新服务栈,最磨人的往往不是写代码,而是环境准备:Node 要 nvm 和 node_modules,Go 要 toolchain 和 GOPATH,传统 C 服务要自建构建流水线。XServer 的答案是把它全部折叠进构建期与二进制内部:构建时用你的 GCC 编出 xs.exe,同时把 TCC 编译器、全套 SDK 头文件、系统库定义经 LZMA 压缩嵌进二进制;运行期目标机上什么都不需要装。

所以"安装 XServer"实际上是两个动作:构建一次宿主,然后运行一个现成的应用目录(demo-single)。我们从这两个动作开始。

2.2 构建宿主

前置条件只有两个:GCC(可用 --cc 指定)和 Python 3。在 xserver 仓库根目录:

命令行 · Windows
$ build.bat                # 最小变体:default
$ build.bat sqlite xtp     # 编入 SQLite 与 XTP2 扩展
$ build.bat --list-extensions
可选扩展: sqlite, xtp

Linux 下等价命令是 bash build.sh sqlite xtp。构建流水线做了五件事:

编 LZMA 打包器 │ ▼ 生成 VFS 资源(SDK 头 + TCC include + 系统库 .def → C 字节数组) │ ▼ 编译扩展对象(-DXS_USE_SQLITE / XS_USE_XTP2,按变体) │ ▼ 编译宿主(main.c + libtcc + VFS + 资源) │ ▼ 临时目录试链 → 成功才原子替换 xs.exe ← 失败永远保留上一个可用版本

最后一步值得强调:链接成功才替换产物。构建失败时,release 目录里的 xs.exe 保持原样——这个"失败保护"有专门的测试用例看护,你在任何时候中断构建都不会得到一个坏二进制。

2.3 运行 demo 与日志解读

demo-single 是仓库自带的完整示例应用(路由、模板、SQLite、受控页面)。进入目录直接运行:

命令行 · 实机输出
$ cd release/demo-single
$ ./xs.exe
[xs] config loaded: D:\GIT\xserver\release\demo-single/xs.json (1 servers)
[xs] server 'demo' class=http 127.0.0.1:9080 tls=off enabled
[xs]   default-host 'app' path=...\wwwroot devfile=...\main.c
[xs] engine started, workers=12
[xs] script loaded: ...\main.c (2.6 KB gen 1)
[demo] database opened: ...\db\main.db
[xs] server 'demo' http bound on 127.0.0.1:9080
[xs] running, press Ctrl+C to stop

刚才发生了什么——逐行拆开:

日志行含义看点
config loaded (1 servers)xs.json 解析通过fail-fast:字段非法会在这里直接退出并给出字段名
server 'demo' class=http ...服务配置回显核对监听地址 / 端口 / TLS 的最后一眼
engine started, workers=12并发引擎就绪worker 数来自 engine.workers,0 = xrt 默认(本机 12)
script loaded (2.6 KB gen 1)TCC 编译完成注意 gen 1——这是第 3 章的主角"代"
[demo] database opened你的 ServiceInit 在跑带 [demo] 前缀的行来自应用脚本自己
http bound on 127.0.0.1:9080监听生效bind 失败会整体回滚已绑定的监听
running, press Ctrl+C to stop进入服务态Ctrl+C / SIGTERM 触发五阶段优雅停机
注意日志顺序的语义:脚本编译成功之后才 bind 端口。这个顺序保证了对外可见时应用一定已初始化完毕——它与热重载的"先编译后换代"是同一条纪律(卷四)。

2.4 发起请求

另开一个终端,向 demo 发起几个请求(全部为实机输出):

命令行 · 实机输出
$ curl -s http://127.0.0.1:9080/json
{"server":"xs3 demo","ok":true,"host":"app"}

$ curl -s http://127.0.0.1:9080/text
demo-single ok (from includes/demo_extra.h)

$ curl -s http://127.0.0.1:9080/api/v1/items/42
{"id":42,"name":"bench-write","created":1788494894}

$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9080/nope
404
$ curl -sI http://127.0.0.1:9080/json
HTTP/1.1 405 Method Not Allowed
Allow: GET

刚才发生了什么:

  • /json 走的是应用脚本里的 Handle_JSON——用 xvalue 构造对象后序列化(第 18 章的主题);
  • /api/v1/items/42 命中了动态路由 /api/v1/items/{id},参数 42 被段捕获后查 SQLite(第 17 章);
  • /nope 未命中任何路由与应用处理,返回 404;
  • HEAD /json 得到 405 + Allow: GET——路由方法掩码里只注册了 GET,HEAD 在进入处理器之前就被方法槽拒绝。这是一个真实的路由细节,第 17 章会讲怎么处理 HEAD。

2.5 契约

  • 线程线:workers 数在启动时固定,热重载不可变(变更会被拒绝)。
  • 变体线:构建变体决定脚本可用的符号——default 变体没有 sqlite3_* / xtp2_*,用了就编译失败。
  • 数据线:demo 的 db/main.db 是真实数据文件;练习时随便改,重新构建仓库即可复原。

常见错误

端口被占用,启动失败
症状
bind failed,进程退出;如果配了多个监听,之前已成功的端口也不会留下一半服务。
原因
目标端口被占用。XServer 的装配是事务式的:任一监听失败,已绑定的监听会被完整回滚再退出——不会出现"半个服务在跑"的中间态。
✗ 重试盲盒
./xs
bind failed
./xs          /* 反复重试,不知道谁占了端口 */
✓ 先查占用再改配置
netstat -ano | findstr 9080
/* 改 xs.json 的 port,或结束占用进程 */
在错误目录双击运行 xs.exe
症状
config load failed——即使你确认 xs.json 就在某个目录里。
原因
不带参数运行时,XServer 在可执行文件所在目录找 xs.json,而不是当前工作目录。把 xs.exe 单独拷走双击,它自然找不到应用。
✗ 错误
copy xs.exe C:\tools\
C:\tools\xs.exe      /* config load failed */
✓ 正确
/* xs.json 与源码目录和 exe 放一起;
 * 或显式指定配置路径 */
./xs D:\apps\myapp\xs.json

编程练习

基础换端口重启

把 demo 的 xs.json 端口从 9080 改成 19080,重启并用 curl 验证。

验收:日志出现 http bound on 127.0.0.1:19080,curl /json 返回与本章一致的 JSON。
进阶切换变体

用 build.bat sqlite 重建宿主,再运行 demo,确认 /api/list 正常;然后换回 default 变体运行,观察脚本编译报什么错。

验收:能说出"default 变体下 demo 为什么起不来",以及错误发生在哪个阶段(提示:对照 2.3 的日志顺序)。
挑战加一条自己的路由

在 demo 的 route.h 注册 /hello(GET),处理器返回你的名字。可以重启验证——热重载是卷四的事,现在先用"重启生效"的心智。

验收:curl /hello 返回预期内容;日志出现 script loaded ... gen 2(重启也会累加代数吗?动手验证并在第 3 章开头找答案)。

小结

要点结论
环境要求GCC + Python 3,仅此而已
构建变体build.bat [sqlite] [xtp];链接成功才原子替换
启动顺序config → engine → script 编译 → ServiceInit → bind → running
日志关键行gen N(代)、workers=N、bound、[app 前缀] 行来自你的脚本
配置查找默认在 exe 所在目录找 xs.json;可用参数显式指定
xrt 延伸阅读