Matrix 与常规的 client-server 聊天有几个根本区别:
m.room.message、m.room.member、m.reaction...)覆盖日常场景;自定义类型可以拿来做投票、地图、CRDT、协同编辑器、游戏状态、IoT 遥测——任何需要"带鉴权的去中心化复制日志"的东西都行。curl 就能调试。联邦(Server-Server API,HTTPS+JSON) ┌─────────────────────────────────────┐ │ │ ┌───────────┐ │ ┌───────────┐ ┌──────────┐ │ ┌───────────┐ │ Element │──┼──▶ │ your. │ ◀───▶ │ matrix. │◀┼─── │ FluffyChat│ │ (Web) │ │ │ meldry. │ │ org │ │ │ (手机) │ │ │◀─┼─── │ com │ │ │ │─── │ │ └───────────┘ │ └───────────┘ └──────────┘ │ └───────────┘ │ ▲ ▲ │ │ │ │ │ │ ▼ ▼ │ │ ┌───────────┐ ┌──────────┐ │ │ │ 桥接、 │ │ AppSvc、 │ │ │ │ AppSvc │ │ 机器人 │ │ │ └───────────┘ └──────────┘ │ └─────────────────────────────────────┘ Client-Server API Server-Server API (Element 讲的协议) (主机服务器之间讲的协议)
your-name.meldry.com 上的一个用户为什么可以不经任何中间人直接跟 matrix.org 上的用户聊天。你挑客户端,Meldry 运行主机服务器,剩下的交给协议。
当 Element 或任何客户端跟你的主机服务器通讯时,它做的事情不外乎四类:
/sync——一个长连接轮询,返回自上次请求以来发生的一切:房间里的新事件、新邀请、在线状态变化、设备间消息(to-device)。客户端会永远保持一个 /sync 开着;服务器最多挂起 30 秒,然后把积累到的内容返回。PUT /rooms/{id}/send/{event_type}/{txn_id}——发送一个新事件(消息、反应、撤回)。txn_id 保证发送是幂等的,重试不会重复。mxc:// URI 引用。全部用登录时取得的 access token 做 bearer 认证。完整的 API 见 Matrix client-server 协议规范。
当你的主机服务器需要把一条消息发进一个在其他服务器上也有成员的房间时,它会:
对端服务器不会盲目信任发送方——它会校验每一个事件的签名,并确认事件的"auth chain"(解释为什么这个事件被允许的那一串 auth 事件)符合房间的权限等级规则。
当两台服务器并发添加事件时,状态解析 算法会按 auth_events 和一个确定的平局规则选出一份权威顺序,两边最终落到相同的状态。
房间里每个事件都有一个 prev_events 字段,列出它对应响应的那些事件的哈希。在多参与者跨多服务器的情况下,prev_events 可以指向多个前驱,形成一张有向无环图。客户端沿 DAG 反向回溯以展示历史;服务器用 DAG 来检测并在联邦恢复后修复分叉。
房间 是一份带鉴权规则、共享复制的事件日志。每个房间都有:
!abc123:server#general:server房间可以公开(在主机服务器目录里列出)或私有(邀请制)。房间不绑定到某一台主机服务器——起源于 matrix.org 的一个房间可以同时有成员在 your-name.meldry.com、在 kde.org、在 element.io,每一台主机服务器都复制一份自己的事件副本。
空间(Space) 是一种特殊的房间,它的"成员"是其他房间和其他空间。空间是 Matrix 给"我想要一个 Slack workspace"或"我想要一个 Discord 服务器"的答案——把相关房间组织成可浏览的层级。可以嵌套空间,标记某些房间为"推荐",也可以把空间的一部分选择性分享给外人。
底层实现上,空间就是一个 room type 为 m.space 的房间,通过 m.space.child 状态事件指向它的子节点。