A memcached text protocol client library implemented in MoonBit.
moon add lexchb/memcached// 内存转录:服务器依次回答 STORED 与一个 VALUE 块。
let conn = @memcached.ScriptedConnection::new([
b"STORED\r\n", b"VALUE foo 0 3\r\nbar\r\nEND\r\n",
])
let client = @memcached.Client::new(conn)
println(client.set("foo", b"bar")) // Status::Stored
for value in client.get(["foo"]) {
println(value.key) // foo
println(value.data) // b"bar"
}| 路径 | 作用 |
|---|---|
| README.md | 仓库首页入口:项目目标、安装、快速开始与验收信息 |
| README.mbt.md | 本文件:完整设计文档(数据模型、编解码流程、校验、边界) |
| memcached.mbt | 协议编解码层:请求编码、响应解码、错误类型 |
| transport.mbt | 传输层抽象:Connection trait 与内存版 ScriptedConnection |
| client.mbt | 客户端层:Client、请求校验、便捷命令方法 |
| memcached_test.mbt | 黑盒测试(只使用公开 API) |
| memcached_wbtest.mbt | 白盒测试:直接验证私有编解码与校验函数 |
| cmd/main/main.mbt | 可运行的演示程序(moon run cmd/main) |
| moon.mod / moon.pkg | 模块与包的元数据、依赖声明 |
┌─────────────────────────────────────────┐
│ client.mbt Client / 校验 / 便捷方法 │ 面向使用者的 API
└───────────────┬─────────────┬───────────┘
│ │
┌───────────────▼──┐ ┌──────▼──────────────┐
│ memcached.mbt │ │ transport.mbt │
│ Request::to_bytes│ │ trait Connection │
│ Decoder │ │ ScriptedConnection │
│ 协议错误类型 │ │ TransportError │
└──────────────────┘ └─────────────────────┘| 变体 | 生成的一行命令 |
|---|---|
| Storage(op, key, flags, exptime, value, cas, noreply) | <op> <key> <flags> <exptime> <bytes> [<cas>] [noreply]\r\n<data>\r\n |
| Get(keys, with_cas) | get <key>... 或 gets <key>... |
| Gat(keys, exptime, with_cas) | gat <exptime> <key>... 或 gats <exptime> <key>... |
| Delete(key, noreply) | delete <key> [noreply] |
| Incr(key, delta, noreply) | incr <key> <delta> [noreply] |
| Decr(key, delta, noreply) | decr <key> <delta> [noreply] |
| Touch(key, exptime, noreply) | touch <key> <exptime> [noreply] |
| Version | version |
| Stats(sub) | stats 或 stats [<sub>](<sub> 可以是多个词,如 detail on) |
| FlushAll(delay, noreply) | flush_all [<delay>] [noreply] |
| Verbosity(level, noreply) | verbosity <level> [noreply] |
| CacheMemlimit(megabytes, noreply) | cache_memlimit <megabytes> [noreply] |
| SlabsReassign(src, dst, noreply) | slabs reassign <src> <dst> [noreply](src 为 -1 时由服务器自选源 slab) |
| SlabsAutomove(mode, noreply) | slabs automove <mode> [noreply](mode 为 0 关 / 1 开 / 2 激进) |
| Quit | quit |
| 命令 | 是否有应答 |
|---|---|
| Get / Gat / Version / Stats | 恒为「有」 |
| Storage / Delete / Incr / Decr / Touch / FlushAll / Verbosity / CacheMemlimit / SlabsReassign / SlabsAutomove | 取 noreply 的相反数 |
| Quit | 恒为「无」(协议规定不回复) |
| 类型 | 含义 |
|---|---|
| Response::Values(Array[RetrievedValue]) | get/gets/gat/gats 的结果,可能为空(未命中) |
| Response::Status(Status) | 存储类、delete、touch、flush_all、verbosity、cache_memlimit、slabs 的终止状态行 |
| Response::Counter(UInt64) | incr/decr 执行后计数器的值 |
| Response::Version(String) | version 的版本字符串 |
| Response::Stats(Array[StatEntry]) | stats 的 STAT 行,直到 END |
| Status | Stored / NotStored / Exists / NotFound / Deleted / Touched / Ok |
| RetrievedValue | 一个 VALUE 块:key、flags、data(字节)、cas(仅 gets/gats 有值) |
| StatEntry | 一条 STAT <name> <value>:name、value(都是字符串) |
| 错误类型 | 变体 | 触发场景 |
|---|---|---|
| ProtocolError | Remote(RemoteErrorKind, String) | 服务器回了 ERROR / CLIENT_ERROR / SERVER_ERROR |
| ProtocolError | Malformed(String) | 收到的字节不符合文本协议,或本地请求不合法 |
| ProtocolError | Desynchronised(Int, Int) | 缓冲区超过上限仍未解出完整响应,流已失去分帧(两个字段依次是上限与已缓冲字节数) |
| TransportError | Io(String) | 底层通道失败,例如响应读一半连接被关闭 |
set foo 7 60 3\r\nbar\r\n # Storage(Set, "foo", 7, 60, b"bar", None, false)
set foo 0 0 3 noreply\r\nbar\r\n # Storage(Set, "foo", 0, 0, b"bar", None, true)
cas foo 0 0 3 42\r\nbar\r\n # Storage(Cas, "foo", 0, 0, b"bar", Some(42), false)
get foo bar\r\n # Get(["foo","bar"], with_cas=false)
gets foo bar\r\n # Get(["foo","bar"], with_cas=true)
gat 30 foo bar\r\n # Gat(["foo","bar"], 30, with_cas=false)
touch foo 30\r\n # Touch("foo", 30, noreply=false)
delete foo\r\n
delete foo noreply\r\n
incr counter 3\r\n
version\r\n
stats items\r\n
flush_all\r\n # FlushAll(None, noreply=false)
flush_all 10\r\n # FlushAll(Some(10), noreply=false)
verbosity 2\r\n
cache_memlimit 64\r\n # CacheMemlimit(64, noreply=false)
slabs reassign -1 2\r\n # SlabsReassign(-1, 2, noreply=false)
slabs automove 2 noreply\r\n # SlabsAutomove(2, noreply=true)
quit\r\nDecoder::next
└─ decode_response 在一段缓冲里找第一个 \r\n,定位首行
├─ "VALUE ..." ──► parse_value_header ──► decode_value_blocks(循环)
├─ "STAT ..." ──► parse_stat_line ────► decode_stats(循环)
├─ "VERSION ..." ─► Version(去掉 8 字节前缀后的文本)
├─ "END" ──► Values([]) (get 全部未命中)
└─ 其它 ──► decode_status (状态行 / 错误行 / 计数器数字)| 方法 | 语义 |
|---|---|
| Decoder::new() | 空缓冲区,上限为 DEFAULT_BUFFER_LIMIT(8 MiB) |
| Decoder::with_limit(limit) | 空缓冲区,上限为 limit |
| Decoder::limit() | 这个解码器接受的上限字节数:既是缓冲区上限,也是它接受的 VALUE 块上限 |
| Decoder::feed(view) | 把新收到的字节追加到缓冲区尾部 |
| Decoder::buffered() | 尚未消费的字节数 |
| Decoder::next() | 尝试解出一个完整响应;不足一个响应时返回 None;成功时把已消费字节从缓冲区头部移除;Remote 错误行在抛出前也一并消费(见下) |
| Decoder::resync() | 丢弃整个缓冲区,用于流已失去分帧后的重新开始 |
Client { conn : &Connection, decoder : Decoder }| 入口 | 接受的请求 | 行为 |
|---|---|---|
| execute(request) | expects_reply() 为 true | 写出请求,读到一整个响应才返回 |
| send(request) | expects_reply() 为 false:quit,或带 noreply 的变更类命令 | 只写出请求,不读任何字节 |
| 方法 | 命令 | 返回值 |
|---|---|---|
| store(op, key, value, flags, exptime, cas) | 任意存储命令(可指定 flags / 过期时间 / CAS) | Status |
| set / add / replace / append / prepend | 对应存储命令;flags / exptime 以同名可选参数透传,默认 0 | Status |
| cas(key, value, cas) | cas,带上期望令牌;同样接受可选的 flags / exptime | Status |
| get(keys) / gets(keys) | get / gets | Array[RetrievedValue] |
| gat(keys, exptime) / gats(keys, exptime) | gat / gats,取回的同时把过期时间重置 | Array[RetrievedValue] |
| delete(key) | delete | Status |
| incr(key, delta) / decr(key, delta) | incr / decr | UInt64(执行后的值) |
| touch(key, exptime) | touch,只续期不取值 | Status |
| version() | version | String |
| stats() / stats_of(section) | stats / stats <section> | Array[StatEntry] |
| flush_all(delay) | flush_all [<delay>],None 表示立即 | Status |
| verbosity(level) | verbosity <level> | Status |
| cache_memlimit(megabytes) | cache_memlimit <megabytes>,调整 item 内存上限 | Status |
| slabs_reassign(src, dst) | slabs reassign <src> <dst>,src 为 -1 时由服务器自选 | Status |
| slabs_automove(mode) | slabs automove <mode>(0 关 / 1 开 / 2 激进) | Status |
| quit() | quit | Unit |
| 槽位 | 含义 |
|---|---|
| Some(response) | 这个请求有应答,response 就是它 |
| None | 这个请求的应答被协议抑制(带 noreply 的变更类命令),没有字节可读 |
| 请求 | 检查内容 |
|---|---|
| Storage / Delete / Incr / Decr / Touch | 校验 key |
| Get / Gat | key 列表至少一个(否则生成 get \r\n 毫无意义),并逐个校验 key |
| Stats | 有子命令时,段名是命令行里的一串「词」:段名可以带参数(stats detail on、stats cachedump 1 100),词之间只允许单个空格,且每个词都要满足 key 的规则(非空、≤250 字节、无空白与控制字符)。空段名、首尾空格、连续两个空格都会留下一个空词,一律拒绝 |
| FlushAll | delay 为 Some(n) 时要求 n >= 0 |
| Verbosity | level < 0 即拒绝 |
| CacheMemlimit | megabytes < 0 即拒绝 |
| SlabsReassign | src 不得小于 -1(-1 表示由服务器自选源 slab);dst 不得小于 0——只有源可以交给服务器挑,目标必须指明一个 class |
| SlabsAutomove | mode 只接受 0 / 1 / 2 |
| Version / Quit | 无需校验 |
trait Connection {
fn write(Self, BytesView) -> Unit raise TransportError
fn read(Self, Int) -> Bytes raise TransportError // 空结果 = 对端已关闭
fn close(Self) -> Unit raise TransportError
}moon check # 类型检查(CI 的第一步)
moon build # 构建整个模块(含 cmd/main,CI 的第二步)
moon test # 跑测试(CI 的第三步)
moon coverage analyze # 查看未被测试覆盖的行
moon run cmd/main # 跑演示程序
moon info && moon fmt # 更新接口文件并格式化pub(open) trait Connection {
fn write(Self, BytesView) -> Unit raise TransportError
fn read(Self, Int) -> Bytes raise TransportError
fn close(Self) -> Unit raise TransportError
}pub suberror ProtocolError {
Remote(RemoteErrorKind, String)
Malformed(String)
Desynchronised(Int, Int)
} derive(Debug)pub struct Decoder {
buffer : Bytes
limit : Int
}pub(all) enum Request {
Storage(op~ : StorageOp, key~ : String, flags~ : Int, exptime~ : Int, value~ : Bytes, cas~ : UInt64?, noreply~ : Bool)
Get(keys~ : Array[String], with_cas~ : Bool)
Gat(keys~ : Array[String], exptime~ : Int, with_cas~ : Bool)
Delete(key~ : String, noreply~ : Bool)
Incr(key~ : String, delta~ : UInt64, noreply~ : Bool)
Decr(key~ : String, delta~ : UInt64, noreply~ : Bool)
Touch(key~ : String, exptime~ : Int, noreply~ : Bool)
Version
Stats(sub~ : String?)
FlushAll(delay~ : Int?, noreply~ : Bool)
Verbosity(level~ : Int, noreply~ : Bool)
CacheMemlimit(megabytes~ : Int, noreply~ : Bool)
SlabsReassign(src~ : Int, dst~ : Int, noreply~ : Bool)
SlabsAutomove(mode~ : Int, noreply~ : Bool)
Quit
} derive(Eq, Debug)impl Connection for ScriptedConnectionInstall
Download zipA memcached text protocol client library implemented in MoonBit.