Skip to content

网络与并发编程指南 ​

对标版本:XTC v1.0-rc | 状态:异步/并行/通道/状态机/调度器/选已落地


并发模型 ​

玄铁采用 双层架构:

层级机制说明
OS 线程层工作窃取线程池执行 CPU 密集型任务,线程数 = CPU 核数
用户态调度层无栈状态机 + 调度器管理 I/O 等待任务,就绪队列 + 定时器堆

异步 { ... } 的执行层由编译器按块内容自动选择(路由规则见 8.1.4)。

异步任务 ​

异步 { ... } 将代码块作为并发任务执行,立即返回任务句柄。

xuantie
设 task = 异步 {
    示("任务执行中...")
    时.睡(1000)
    示("任务完成")
}
示("主线程继续执行...")
等待(task)
示("任务已结束")

执行流程:

  1. 编译器统计异步块内的挂起点(等待/收/发/选/时.睡)位置
  2. 仅在语句级 → 编译为无栈状态机(poll 函数 + 状态结构体),xt_fiber_spawn 直接提交用户态调度器
  3. 循环体内含挂起点 → 整体编译为独立函数,xt_async_spawn 提交线程池
  4. 主线程执行到 等待 或程序末尾时驱动调度器事件循环
  5. 任务的 返 回值可被 等待 取回(无 返 则得 空)

并行执行 ​

并行 { {块1}, {块2}, ... } 并发执行多个代码块。

xuantie
设 results = 并行 {
    { 时.睡(500); 返 "A" }
    { 时.睡(300); 返 "B" }
    { 返 "C" }
}
// results = ["A", "B", "C"]

行为约定与禁忌(必读) ​

  1. 主线程的 收/发 默认阻塞(空等货、满等位)。主线程阻塞期间会顺带驱动 fiber 调度器,不会死锁;但「永远等不到货」的 收 会让程序永远停在那里——这不是卡死,是阻塞语义。
  2. fiber 任务的返回值只能取一次。第二次 等待 同一个已完成的 fiber 得到的是 空(槽位已回收复用)。需要多次使用的结果请先存进变量。
  3. fiber 内捕获的外层局部变量是 spawn 时刻的值副本。spawn 之后外层再修改,fiber 内看到的是旧值;fiber 内对它的修改也不会写回外层(但修改在 fiber 自身内部跨挂起点持久生效,v0.19 起)。需要共享请用通道传递,或改用全局变量。
  4. 等待(fiber) 会驱动调度器直到全局静止。若程序里还有长时间睡眠的其他 fiber,等待 会等它们一并结束才返回——设计如此,不是挂起。
  5. fiber 句柄上限 65535 个。正常逐个 等待 的程序会触发槽位回收,实际远够;只创建不等待会持续占用槽位。
  6. 不要把「循环内挂起」当成零成本并发。它独占 worker 线程;线程池容量(CPU 核数量级)内没问题,成百上千个这种任务会退化。海量并发场景请把挂起点移到循环外(语句级),走 fiber。
  7. 嵌套在回调函数内的异步块捕获外部变量仍是已知局限。

自动路由规则(重要) ​

编译器统计异步块内的挂起点(等待、收、发、选、时.睡、连、流.读、流.写)出现的位置:

挂起点位置执行层代价
仅在语句级(不在任何循环体里)fiber 调度器微秒级切换,几乎零占用
任意挂起点出现在循环体(当/遍历/循)内线程池运行期间独占一个 worker 线程

路由到线程池时编译器会给出诊断提示,例如:

xuantie
编译提示: 异步块检测到 1 个循环内挂起点(等待/收/发/选/时.睡),已整体编译为线程池任务...

为什么循环内挂起要走线程池:fiber 是无栈状态机,挂起恢复依赖编译期生成的状态跳转;循环体内的挂起恢复需要保留循环索引等局部状态,当前实现选择把这类代码放到真正的 OS 线程上执行——阻塞语义天然正确,但会占用一个 worker。

推荐模式:

xuantie
// ① 语句级挂起 → fiber,最轻量
设 任务 = 异步 { 时.睡(100); 返 42 }
设 返回值 = 等待(任务)          // 返回值 = 42

// ② 循环内挂起 → 自动路由线程池,语义正确,放心写
设 消费者 = 异步 {
    当 真 {
        设 货 = 通道.收()     // 阻塞等待,不占 fiber
        示("收到: " & 货)
    }
}

// ③ 非阻塞试探:用 选 的超时 0,而不是直接 收
设 idx = 选([通道], 0)        // 立即返回:有货返下标,没货返 -1

通道 ​

道(容量) 创建带缓冲的并发通道。v0.3 起支持阻塞收发(条件变量)。

基本用法 ​

xuantie
设 ch = 道(4)      // 容量为 4 的通道
ch.发("hello")     // 非阻塞发送(满则丢弃); 状态机中为阻塞发送
ch.发("world")
设 msg = ch.收()   // 非阻塞接收(空则返回空); 状态机中为阻塞接收
示(msg)

阻塞收发(状态机内) ​

在 异步 块内使用 .发() / .收() 时,编译器自动使用阻塞版本:

xuantie
设 ch = 道(3)

设 consumer = 异步 {
    设 sum = 0
    设 i = 0
    当 i < 3 {
        sum = sum + ch.收()  // 阻塞等待,不占 OS 线程
        i = i + 1
    }
    示("Sum: " & sum.转字())
}

设 producer = 异步 {
    ch.发(1)
    ch.发(2)
    ch.发(3)
}

等待(producer)
等待(consumer)

多通道选择(选) ​

选(通道数组, 超时毫秒) 等待多个通道中第一个就绪的,返回通道索引。超时返回 -1。

xuantie
设 fast = 道(1)
设 slow = 道(1)

设 sel = 异步 {
    设 idx = 选([fast, slow], 5000)
    若 idx == 0 { 示("fast 就绪") }
    抑 idx == 1 { 示("slow 就绪") }
    否 { 示("超时") }
}

主线程泵送语义:主线程在 选 等待期间会驱动用户态调度器(与 收/发 的无限等待一致),因此发送方是 fiber(无循环挂起的异步块)时数据也能正常到达。代价:fiber 内有长睡眠或线程池有在途任务时,选 的返回可能晚于你给的超时毫秒数(等待会持续到 fiber 跑完一轮);超时到期仍未就绪才返回 -1。如果你需要严格守时的多路等待,把发送方移出 fiber(用含循环挂起的异步块走线程池),或把超时拆成多次短轮询。

线程安全 ​

通道操作(发/收/选)已加互斥锁+条件变量保护,多线程并发安全。


HTTP 请求 ​

URL 编码与解码 ​

求 不对 URL 做任何编码:地址里带中文、空格等非 ASCII/保留字符时必须自己先编码,否则服务端会拒答——单参 求 对这种拒答只报到"成功但体长为 0",很容易误判。两个内置函数成对使用:

xuantie
设 地址 = "https://example.com/search?q=" & URL编码("玄铁 语言 & 文档")
示(地址)                                  // ...?q=%E7%8E%84%E9%93%81%20%E8%AF%AD%E8%A8%80%20%26%20%E6%96%87%E6%A1%A3
示(URL解码("%E7%8E%84%E9%93%81"))         // 玄铁
  • URL编码(串):按 RFC 3986,A-Z a-z 0-9 - _ . ~ 直通,其余按 UTF-8 字节转 %XX(大写);空格 → %20(不是 form 的 +)。
  • URL解码(串):%XX(十六进制大小写均可)→ 原字节;+ 原样保留(RFC 3986,不做 form 的空格还原)。转义不完整、非十六进制、或解码结果不是合法 UTF-8,一律明确报错退出(提示带偏移)。
  • 字节到字符串(字节):字节流按 UTF-8 严格校验后转字符串(非法字节明确报错退出);配合 字节(容量) 建缓冲与 .追加(字节值) 逐字节写入使用。

简单 GET(单参形态) ​

求(url) 发起 HTTP GET 请求,返回 结果 对象,成功时 值 为最终响应体文本。支持 https://(TLS 走系统原生通道,零外部依赖:Windows 用 Schannel,macOS 用 SecureTransport;Linux 当前无 TLS 通道,https 请求会明确失败)。

行为要点:

  • 自动跟随重定向:301/302/303/307/308 最多 5 跳,返回最终页内容(Location 支持绝对/协议相对/绝对路径/相对路径四种形态);
  • 默认 User-Agent 为完整 Chrome 标识(大量站点对空 UA 返回跳转壳或拒答);需要自定义时用双参形态的 头 选项;
  • 超时:连接 10 秒、收发静默 30 秒(超时返回失败而非挂死);DNS 解析走 getaddrinfo,多 A 记录站点逐 IP 故障转移;
  • chunked 传输自动解码,二进制响应体安全定长处理。
xuantie
设 resp = 求("http://api.example.com/data")
若 resp.成功 {
    示("响应: " & resp.值)
} 否 {
    示("请求失败: " & resp.错误)
}

完整客户端(双参形态) ​

求(url, 选项) 发起自定义请求,选项字典全部可选:

键说明默认
方法"GET"/"POST"/"PUT"/"DELETE" 等"GET"
头字典,逐个写入请求头,如 {"Content-Type": "application/json"}无
体请求体文本(非 GET 时常配)无
超时收发超时毫秒数(连接超时暂不支持)不限

成功时 值 为字典:{"状态码": 整, "体": 字, "头": 字典};Transfer-Encoding: chunked 会自动解码。

xuantie
设 r = 求("https://api.example.com/items", {
    "方法": "POST",
    "头": {"Content-Type": "application/json"},
    "体": "{\"名\":\"玄铁\"}",
    "超时": 8000
})
若 r.成功 {
    设 v = r.值
    示("状态码: " & v["状态码"])
    示("响应体: " & v["体"])
    示("Content-Type: " & v["头"]["Content-Type"])
}

注意:单参形态(8.3.1)若遇 chunked 响应不解码(原样返回含块帧的文本);https 请求建议用双参形态拿完整状态码/响应头。

求 与 异步块的关系(易踩坑) ​

求 不是 fiber 挂起点——它内部是完整的阻塞 HTTP 事务,不会像 连/读/写 那样让出调度器。推论:

xuantie
// ✅ 推荐:整个块只有 求,没有别的挂起点 → 自动路由线程池,语义正确
设 抓取 = 异步 {
    设 r = 求("https://example.com")
    若 r.成功 { 示("拿到 " & r.值.长度 & " 字节") }
}
等待(抓取)

// ⚠️ 避免:同一个块里既有 fiber 挂起点(等待/收/发/选/时.睡/连/读/写)
// 又有 求 —— 块会变 fiber,求 会卡住调度线程直到事务完成

并发抓取的正确姿势:每个 URL 一个独立 异步 块(各自只含 求,自动去线程池),主线程逐个 等待 汇总:

xuantie
设 们 = []
遍历 u 于 ["http://a.com/1", "http://a.com/2"] {
    设 地址 = u                      // 循环内声明,逐迭代捕获
    们.追加(异步 {
        设 r = 求(地址)
        若 r.成功 { 示(地址 & " -> " & r.值.长度 & " 字节") }
    })
}
遍历 t 于 们 { 等待(t) }

TCP 网络 ​

玄铁的网络 I/O 有两种执行上下文,同一个 读/写/连 在两种上下文里的底层行为完全不同(语义一致,占用模型不同):

上下文触发方式底层行为代价
阻塞上下文主线程 / 普通函数 / 听 的回调 / 线程池异步任务真正的阻塞 recv/send,独占当前线程一个操作占一个线程
fiber 上下文异步 {} 块内(语句级,不在循环体里)非阻塞 + 就绪挂起,等待时不占任何线程微秒级切换

「我这个写法会落到哪个上下文」由编译器按 8.1.4 的自动路由规则决定。本节所有示例都会标明落在哪一层。

服务端(监听) ​

听(端口, 回调) 启动 TCP 监听,立即返回(不等连接)。每来一个新连接,运行时为它在独立的 OS 线程里调用一次回调,回调参数就是该连接的 流。

默认只绑回环。 两参形态只监听 127.0.0.1(仅本机可连)——这是刻意的安全默认:服务在开发机上随手一跑就对整个局域网开门,是这类语言最常见的静默缺口。要对外提供服务,用三参形态显式写地址:

xuantie
听(8000, 函(流) { /* ... */ }, "0.0.0.0")        // 对外(所有网卡)
听(8000, 函(流) { /* ... */ }, "192.168.1.10")   // 只绑某块网卡

地址不可用(端口被占、地址非法、第三个参数不是字符串)时 听 返回失败结果,错误消息里带具体地址与端口,不会静默回退成对外监听;参数个数不是 2 或 3 时编译期直接报错。

xuantie
听(8000, 函(流) {
    流.写("欢迎!\n")
    循 {
        设 数据 = 流.读()
        若 数据 == 空 { 断 }      // 对端关闭 → 读返回 空 → 退出循环
        流.写("回显: " & 数据)
    }
    流.关()
})
输("按回车停止...")               // 关键:主线程必须有事可做,否则程序直接退出

关键点 1:回调里是阻塞上下文,可以放心用阻塞写法。 因为每个连接独占一个真实线程,循 环里阻塞 读 天经地义——它只卡住「这一个连接」的线程,不影响其他连接,也不影响 fiber 调度器。

关键点 2:听 不挡退出。 听 启动后立刻返回,如果主线程一路跑到程序末尾,进程退出、监听随之消失。所以服务端末尾通常用 输(...) 或一个永不结束的循环把主线程留住:

xuantie
听(8000, 函(流) { /* ... */ })
示("服务已启动")
循 { 时.睡(1000) }   // 或者:输("按回车停止...")

关键点 3:多个回调并发跑,共享数据要走通道或全局。 回调之间是真正的多线程并发。例如统计在线人数:

xuantie
设 在线 = 0          // 全局变量,所有回调线程共享
听(8000, 函(流) {
    在线 = 在线 + 1   // 简单计数可以;复杂共享状态请用 通道 汇总(见 8.2)
    循 {
        设 数据 = 流.读()
        若 数据 == 空 { 断 }
        流.写("当前在线: " & 在线)
    }
    在线 = 在线 - 1
    流.关()
})

关键点 4:目前没有「停止监听」原语。 听 一旦启动就到进程结束,这是已知边界;需要可关停的服务请把「是否继续」判断放在回调内部逻辑里。

关键点 5(必知坑):TCP 没有消息边界,读 不等于「读一条消息」。 阻塞路径下每次 读 最多返回 4096 字节,而且对端一次 写 的数据可能被拆成多次 读 到达(也可能多次 写 被合并成一次 读)。不要假设 读 到的就是一整条消息。 如果协议需要完整消息,自己加分隔符或长度头,循环拼:

xuantie
// 按行协议:循环读直到凑出一行(\n 结尾)
函 读一行(流) {
    设 缓冲 = ""
    循 {
        设 块 = 流.读()
        若 块 == 空 { 返 空 }                 // 对端关闭
        缓冲 = 缓冲 & 块
        若 缓冲.包含?("\n") {
            设 行集 = 缓冲.分割("\n")
            返 行集[0]                              // 取第一行(分割语义见参考手册 6.4 节)
        }
    }
}

客户端(连接) ​

连(地址) 连接到 TCP 服务端,返回 结果——必须判 成功 再取用,地址格式 "主机:端口":

xuantie
设 联 = 连("127.0.0.1:8000")
若 联.成功 {
    设 流 = 联.值
    流.写("hello")
    设 回 = 流.读()
    若 回 != 空 { 示("服务端说: " & 回) }
    流.关()
} 否 {
    示("连接失败: " & 联.错误)
}

关键点 1:读 返回 空 只有一个含义——对端关了(或已关)。 它不表示「暂时没数据」:阻塞路径下没数据会一直等到有数据或对端关闭;fiber 路径下没数据会挂起等就绪。所以「读到 空」永远按连接结束处理:

xuantie
设 数据 = 流.读()
若 数据 == 空 {
    // 对端已关闭,别再 写/读,直接收尾
    流.关()
    返 空
}

关键点 2:写 返回 真/假,且保证「写完全部」。 写大数据时底层会自动分多次发送(部分发送的内部续传),返回 真 代表全部发出;返回 假 代表连接已坏。不需要自己处理「只发出去一半」的情况。

关键点 3:关 通常可以不写。 socket 由 ARC 内存管理,引用归零自动关闭;长寿命循环(如服务端回调)里建议显式 流.关() 及时释放。

Socket 方法 ​

方法说明
.读()读取数据;有数据返回字符串,对端关闭返回 空。阻塞路径每次最多 4096 字节
.读(上限)同上,但最多读 上限 字节。上限仅在 fiber 上下文生效(见 8.4.4 细则 1)
.写(内容)写入字符串(自动分次发完),成功 真 / 失败 假
.发(内容)同 .写()
.关()关闭连接

闭包回调注意:听 的回调经 xt_closure_call1 按 env 感知约定调用——捕获外层变量的闭包可正常使用。

异步块内的网络 I/O(fiber 感知,v0.19+) ​

一分钟原理 ​

异步 {} 块(语句级、不含循环内挂起点)会被编译成 fiber(无栈状态机),跑在 fiber 调度器上。块内的 连、流.读()、流.写() 是挂起点:socket 自动转为非阻塞;操作不能立即完成时,fiber 连人带状态挂进「socket 就绪等待表」,调度线程立刻去驱动别的 fiber;数据到达时 fiber 被精确唤醒,从挂起点继续——等待期间不占用任何线程。

收益:成百上千个并发连接不再需要成百上千个线程,一个调度线程就能撑起全部「大部分时间在等数据」的连接。

标准客户端模板 ​

xuantie
设 客 = 异步 {
    设 r = 连("127.0.0.1:8000")          // 挂起点:连接建立前不占线程
    若 非 r.成功 { 示("连接失败") ; 返 空 }
    设 流 = r.值
    流.写("你好")                         // 挂起点:写不完就挂起等可写,自动续传
    设 回 = 流.读()                       // 挂起点:没数据就挂起等就绪
    示("收到: " & 回)
    流.关()
}
等待(客)                                  // 主线程在这里驱动调度器直到 fiber 完成

与阻塞版逐字对比你会发现写法完全一样——差别只在底层不占线程。这就是 fiber 感知 I/O 的设计目标:同一套代码,放进 异步 {} 就自动获得高并发能力。

多连接扇出模板(fiber 的主战场) ​

由于玄铁的 fiber 切换是微秒级,几百个并发连接的正确写法是「每个连接一个 fiber」,而不是开几百个线程:

xuantie
设 们 = []
设 i = 0
当 i < 100 {
    设 j = i                            // 循环内声明,逐迭代捕获(关键!写成直接用 i 会全拿到终值)
    们.追加(异步 {
        设 r = 连("127.0.0.1:8000")
        若 非 r.成功 { 返 空 }
        设 流 = r.值
        流.写("我是 " & j)
        设 回 = 流.读()
        若 回 != 空 { 示("#{j} 号收到: " & 回) }
        流.关()
    })
    i = i + 1
}
遍历 t 于 们 { 等待(t) }                  // 等全部 100 个 fiber 完成

注意 设 j = i 这一行:循环变量 i 只有一个存储,直接捕获会让所有 fiber 读到循环结束后的终值;设 j = i 在循环体内声明新变量,捕获按创建时刻快照(逐迭代语义),每个 fiber 拿到自己那轮的值。

写法纪律(反直觉点,必读) ​

纪律一:挂起语句独立成句,不要把挂起点包进 若 块。 fiber 是无栈状态机,含挂起点的 若 会被拆成多个「状态片」,片与片之间块内语句会重复执行(既有语义):

xuantie
// ❌ 反例:若 块里有挂起点,块里的 示 可能被执行多次
异步 {
    若 r.成功 {
        示("连上了")            // 状态片重放时这行会再打印一次!
        设 回 = r.值.读()
        示("收到: " & 回)
    }
}

// ✅ 正例:挂起点全部独立成句,若 只包纯判断
异步 {
    设 r = 连("127.0.0.1:8000")
    若 非 r.成功 { 返 空 }      // 若 块里没有挂起点,安全
    设 流 = r.值
    设 回 = 流.读()
    若 回 != 空 { 示("收到: " & 回) }
}

纪律二:需要「读循环」的连接处理,请交给线程池而不是 fiber。 挂起点出现在循环体里时,整个异步块自动路由到线程池(8.1.4)——这不是降级,是设计:循环内挂起需要保留循环现场,fiber 做不到,线程天然正确。所以服务端长连接收包循环这样写就对:

xuantie
// 循环里有 读 → 整块自动去线程池,阻塞语义,完全正确
设 长连处理 = 异步 {
    设 r = 连("127.0.0.1:8000")
    若 非 r.成功 { 返 空 }
    设 流 = r.值
    当 真 {
        设 数据 = 流.读()       // 线程池里阻塞读,独占一个 worker
        若 数据 == 空 { 断 }
        示("收到: " & 数据)
    }
}

而**「一次性问答」式连接**(连上 → 写 → 读 → 关)全部语句级,走 fiber,最省:

xuantie
设 问答 = 异步 {
    设 r = 连("127.0.0.1:8000")
    若 非 r.成功 { 返 空 }
    设 流 = r.值
    流.写("问")
    设 答 = 流.读()             // 全在语句级 → fiber,挂起不占线程
    示("答: " & 答)
    流.关()
}

细则与边界(逐条都是实测行为) ​

  1. 读(上限) 只在 fiber 内生效。 流.读(16) 在 fiber 里最多读 16 字节;在阻塞上下文(主线程/听 回调)该参数被忽略,固定每次最多 4096 字节。结论:无论哪个上下文,都不要依赖单次 读 到的字节数,要完整消息请用 8.4.1 关键点 5 的循环拼法。
  2. fiber 内 .读()/.写() 保留给 socket 流(如同 .收/.发 保留给通道)。对字典/实例的同名方法在 fiber 内调用会显式报错,不会静默出错。文件.读/写 是模块函数,完全不受影响。
  3. fiber 内的变量跨挂起点持久:设 的局部变量、对捕获变量的修改,在挂起恢复后都保持(v0.19 起;更早版本会丢成 空/旧值——如果你在老版本上写 fiber 网络代码,请升级)。
  4. 同一个 socket 不要跨上下文混用。 socket 一旦被 fiber 碰过就会置为非阻塞;之后再拿到阻塞上下文用虽然做了兼容(内部重试,行为不变),但这是为容错准备的降级,不是设计用法。一个流,从生到死待在同一个上下文里。
  5. 读 没有超时参数。 需要「超时放弃」时,用对端关闭触发的模式:看门狗 fiber 到时后让对端(或本端持有方)关 流——挂起的 读 会被唤醒并返回 空,按连接结束统一处理:
xuantie
设 看门 = 异步 {
    时.睡(3000)
    若 服务端流盒子[0] != 空 { 服务端流盒子[0].关() }   // 对端关闭 → 读方唤醒得 空
}
  1. TLS(HTTPS)流暂不支持 fiber 感知 I/O;求 走自己的阻塞路径,不受影响。
  2. fiber 句柄上限 65535(见 8.1.3 第 5 条);socket 就绪等待表上限 1024 个并发挂起 I/O,超出显式报错退出(不静默丢)。

HTTP 服务端(铁铺包 HTTP) ​

HTTP.服务(端口, 处理函) 以纯玄铁实现启动 HTTP/1.1 服务(基于 听)。处理函收请求字典 {"方法","路径","查询","头","体"},返回响应字典 {"状态码","头","体"}(均可缺省,默认 200/空体)。每连接一请求一响应(Connection: close)。

xuantie
引 "HTTP" 予 HTTP

HTTP.服务(8080, 函(请求) {
    若 请求["路径"] == "/api" {
        返 {"状态码": 200, "头": {"Content-Type": "application/json"}, "体": "{\"ok\":真}"}
    }
    返 {"状态码": 404, "体": "没有 " & 请求["路径"]}
})
示("服务已启动 :8080")
输("按回车停止...")

关键点与写法:

  1. 处理函跑在连接线程里(阻塞上下文)——基于 听,每连接一个独立线程。处理函里可以用 文件.读 等阻塞操作,也可以访问外层变量(闭包捕获正常)。
  2. 读 POST 体:请求["体"] 已按 Content-Length 收齐;请求["方法"] 区分动作。回显接口示例:
xuantie
HTTP.服务(8080, 函(请求) {
    若 请求["方法"] == "POST" 且 请求["路径"] == "/echo" {
        返 {"状态码": 200, "体": "你发了: " & 请求["体"]}
    }
    返 {"状态码": 404, "体": "没有 " & 请求["路径"]}
})
  1. 路径与查询串分离:/a?x=1 进来,请求["路径"] 是 /a,请求["查询"] 是 x=1,解析查询参数用 分割("&") 再按 = 拆。
  2. 不支持 keep-alive:每个连接处理一个请求就关闭(HTTP/1.1 Connection: close 语义)。浏览器/fetch 会自然兼容(后续请求自动开新连接)。
  3. 响应头自动补:Content-Length 按 体.字节数 自动写入,Connection: close 自动带上;你只需给业务头。
  4. 关停:目前没有「停止监听」原语,服务随进程生命周期;开发期直接结束进程即可。

即时通讯 (IM) 完整示例 ​

项目 examples/im/ 包含完整的服务端+客户端示例。

xuantie
// 服务端
听(8000, 函(流) {
    示("新连接")
    流.写("系统: 欢迎进入聊天室!")
    循 {
        设 数据 = 流.读()
        若 数据 == 空 { 断 }
        示("收到: " & 数据)
    }
})
示("服务端已就绪")
输("按回车停止...")

线程安全速查 ​

组件线程安全性说明
通道 (道)安全 ✓xt_chan_mutex_t + 条件变量全程持锁
通道(阻塞收发)安全 ✓条件变量等待,不占 OS 线程
选 (选)安全 ✓轮询+条件变量,超时安全
异步 (异步)安全 ✓状态机 poll 函数由调度器单线程调用
并行 (并行)安全 ✓cloneEnv 隔离
Socket I/O安全 ✓每个 socket 独立,无共享状态
ARC安全 ✓atomic_fetch_add/sub
调度器注意 ⚠线程池回调唤醒 fiber,无锁竞态(当前单线程未触发)
全局变量不安全 ✗用户代码需自行加锁
弱引用安全 ✓g_weak_mutex 保护

命令速查 ​

关键字说明
异步 { ... }异步执行代码块(自动检测状态机)
等待(task)等待异步任务完成
并行 { {a}, {b} }并行执行多个代码块
道(n)创建容量为 n 的通道
.发(v)通道/流 发送(状态机中阻塞)
.收()通道接收(状态机中阻塞)
选(arr, ms)多通道选择,返回索引,超时返回 -1
求(url)HTTP GET(返回体文本)
求(url, 选项)完整 HTTP 客户端(方法/头/体/超时,回 状态码/体/头)
连(addr)TCP 连接(fiber 内非阻塞挂起)
听(port, cb, 绑定地址?)TCP 监听(回调支持捕获闭包);缺省只绑回环 127.0.0.1,对外须显式写 "0.0.0.0"
HTTP.服务(port, fn)HTTP 服务端(铁铺包 HTTP)
.读() / .读(上限)Socket 读取(上限仅 fiber 内生效;fiber 内为就绪挂起)
.写(s)Socket 写入(fiber 内为可写挂起,自动续传)
.关()Socket 关闭

基于 VitePress 静态构建 · 文档内容与仓库 GUIDE/ 同源

玄铁玄铁 XUANTIE · 中文系统级编程语言MARK 玄·折 120×120 · STROKE 8 · NO CURVES