CardDAV 是 RFC 6352 定义的开放标准协议,全称 vCard Extensions to Web Distributed Authoring and Versioning,本质是 WebDAV 的通讯录扩展,用来在网络上以标准化方式存储、查询、增删改和双向同步联系人。
它解决的核心问题是:手机、电脑、邮件客户端、私有云之间的通讯录不再是各管各的副本,而是共用同一个服务端”真相源”。
一、它在协议栈里的位置
HTTP/HTTPS
└─ WebDAV (RFC 4918:PROPFIND/PUT/DELETE/MKCOL/REPORT…)
└─ CardDAV (RFC 6352:通讯录集合、vCard 资源、查询报表)
└─ vCard (RFC 6350:联系人数据格式,如 FN/TEL/EMAIL/PHOTO)
- vCard ≠ CardDAV:vCard 是”一张名片”的文本格式(
.vcf);CardDAV 是”怎么把一堆 vCard 放在服务器上并多端同步”的协议。 - CardDAV ≠ CalDAV:CalDAV 管日历(iCalendar),CardDAV 管通讯录,二者常由同一账号同一服务商一起提供。
二、数据模型(最关键)
CardDAV 把通讯录建模成”目录树”:
- Principal(用户主体):
/carddav/下代表某个用户,通过DAV:current-user-principal发现。 - Address Book Collection(通讯录集合):一种特殊的 WebDAV 集合,类似一个文件夹,里面只能放 vCard。可以有多个(个人/工作/共享组)。
- Address Object Resource(联系人资源):集合下的每个子资源就是一个 vCard 文件,必须有全局唯一
UID,通过 URL 定位。
例:
https://carddav.example.com/carddav/user/
└─ contacts/ ← Address Book Collection
├─ alice.vcf ← vCard (UID=alice)
└─ bob.vcf ← vCard (UID=bob)
三、核心 HTTP 方法(客户端怎么操作)
| 动作 | 用的请求 |
|---|---|
| 发现当前用户入口 | PROPFIND 查 current-user-principal |
| 列出有哪些通讯录 | PROPFIND 查 addressbook-home-set |
| 建新通讯录 | MKCOL + CardDAV 资源类型 |
| 拉某个联系人 | GET |
| 新增/覆盖联系人 | PUT(带 If-Match 做并发控制) |
| 删联系人 | DELETE |
| 按条件查(如名字含”张”) | REPORT 用 addressbook-query |
| 增量同步 | REPORT 用 sync-collection(RFC 6578) |
| 改属性(显示名等) | PROPPATCH |
服务端通常用 ETag(单个 vCard 版本号)+ CTag/sync-token(整个通讯录变更令牌)做高效增量同步:客户端只拉”上次 token 之后变了的”,不用全量下载。
冲突处理靠 ETag 乐观锁:本地改了上传 PUT 时带旧 ETag,服务端若已变则返回 412,客户端再合并或提示冲突。
四、服务发现(不用背 URL)
按 RFC 6764,客户端拿到邮箱和密码后可以先试:
https://domain/.well-known/carddav
服务端返回 301/PROPFIND 跳转,再逐级 PROPFIND 出 principal → addressbook-home → 具体通讯录 URL。所以 iOS/macOS/Thunderbird 经常只填邮箱密码就能自动配好。
五、认证与安全
- 传输层:必须 HTTPS,否则明文 vCard 泄露隐私。
- 认证方式:
- Basic Auth(最常见,但建议配合应用专用密码)
- OAuth 2.0(Google 等强制要求,不支持密码直登)
- 部分企业服还接 WebDAV ACL 做共享通讯录权限
- 自托管常见栈:Radicale、Baikal、Nextcloud、cPanel Calendar/Contacts Server。
六、典型使用场景与客户端
- iOS/macOS:设置 → 通讯录 → 账户 → 其他 → 添加 CardDAV 账户(原生支持)
- Android:系统不内置,用 DAVx⁵ / OpenSync 把 CardDAV 注入系统通讯录
- 桌面邮件客户端:Thunderbird 原生;Outlook 不原生,需 CalDAV Synchronizer 插件
- 公有云:iCloud、Google Contacts(OAuth)、Zoho、Fastmail、Proton 等均可作为 CardDAV 服务端
- 企业/私有:把员工通讯录放 Nextcloud,全员只读同步;或把客户通讯录从 CRM 经 CardDAV 推到销售手机
一次手动 PUT 新建联系人的最小例子(curl):
curl -u user:app-pass -X PUT \
https://carddav.example.com/carddav/user/contacts/alice.vcf \
-H "Content-Type: text/vcard" \
--data-binary $'BEGIN:VCARD\nVERSION:3.0\nFN:Alice Chen\nTEL;CELL:+86-138...\nEMAIL:alice@example.com\nEND:VCARD'
七、工程上容易踩的坑
- vCard 版本:服务端可能只收 3.0 或 4.0,照片/国际化字段在跨版本时丢属性。
- UID 稳定性:换手机重新导入 vCard 若没保留 UID,会被当成新人而不是更新。
- 大通讯录性能:纯 CTag 全量比对在万级联系人以上偏慢,要依赖
sync-collection增量。 - Google 等特殊实现:不兼容完整 WebDAV(禁 LOCK/MOVE),客户端别依赖完整文件锁语义。
- 共享通讯录写冲突:多人同时改同一条,必须靠 ETag 乐观锁+客户端合并策略,协议本身不自动解冲突。
下面是 CardDAV 首次同步全流程时序图及配套说明。

时序图流程解读
整张图分三大阶段,对应三次”递进式发现”——这正是 CardDAV 区别于普通 REST API 的核心设计。
① 服务发现(RFC 6764)
客户端只需知道域名+账号密码,先访问 GET /.well-known/carddav,服务端返回 301 跳转到真实 principal 路径。这一步让用户无需记忆复杂 URL。
② 定位通讯录(PROPFIND 两轮)
- 第一轮
PROPFIND查 **current-user-principal** → 拿到/carddav/user/用户主体。 - 第二轮查 **
addressbook-home-set** → 拿到通讯录主页。 - 第三轮列出主页下的所有 Addressbook 集合(如 default、work)。
每一轮都是”问 A 拿到 B 的地址,再向 B 发问”的链式发现,直到定位到具体通讯录集合。
③ 首次全量同步(REPORT sync-collection)
- 客户端发
REPORT sync-collection,sync-token=0 表示”我从未同步过” → 触发全量返回。 - 服务端遍历所有 vCard 资源,以 207 Multi-Status 返回”资源 URL + ETag + 新增项”清单。
- 客户端拿到清单后,逐条 GET 拉取完整 vCard(带 ETag 做版本校验),解析写入本地数据库。
- 全部完成后保存最新的 sync-token / CTag——这是关键:下次同步只需传这个 token,服务端只返回增量变更,实现高效双向同步。
关键设计要点
| 机制 | 作用 |
|---|---|
| sync-token / CTag | 整个通讯录的变更令牌,首次=0,之后增量 |
| ETag | 单条 vCard 版本号,PUT 更新时乐观锁防冲突 |
| 207 Multi-Status | WebDAV 批量响应格式,一次报告多个资源状态 |
| UID 稳定性 | vCard 的 UID 不变 = 同一个人,避免重复导入 |
⚠️ 工程提示:首次同步是全量的,万级联系人时会明显偏慢,这是协议固有特性;后续同步才走增量。生产环境建议在首次同步时加进度回调 + 失败断点续传(记录已拉取的 UID 集合)。
「二次增量同步」时序图。基于首次同步已保存 sync-token 的前提,增量同步的核心逻辑是:只拉取变更、ETag 冲突处理、token 滚动更新。。下面是 CardDAV 二次增量同步时序图及流程解读。

与首次同步的核心差异
首次同步是 sync-token=0 的全量下载;二次同步的本质变化在于:客户端已持有上次保存的 sync-token,因此整轮只传输「该 token 之后的变更」,数据量从 O(N) 降到 O(变更数)。
三阶段流程解读
① 先推本地变更(「先推后拉」原则)
成熟的同步实现都遵循这个顺序——先把本地未上传的改动 PUT 上去,再去拉服务端,否则会出现”刚拉完又被本地旧数据覆盖”的丢更新问题。
- ETag 乐观锁:
PUT带If-Match: <旧ETag>,服务端比对一致才写入,返回新 ETag。 - 412 冲突是常态而非异常:图中
carol.vcf因为另一客户端已改过、ETag 过期,服务端返回 412 Precondition Failed。客户端的标准处理链路是:GET 最新版 → 合并字段 → 用最新 ETag 重新 PUT,这正是协议解冲突的唯一可靠方式。
⚠️ 工程要点:合并策略要自己定(如”手机号取最新、备注取并集”),CardDAV 协议不自动解冲突,处理不当会导致互相覆盖。
② 拉取服务端增量(REPORT sync-collection)
- 客户端把上次保存的 sync-token 放进
REPORT请求体。 - 服务端只返回该 token 之后的三类变更:新增(+)、修改(~)、删除(-),用 207 Multi-Status 批量承载。
- 对修改项,客户端用清单里带的新 ETag 逐条 GET 拉完整 vCard,更新本地 DB;对删除项直接在本地移除。
③ Token 滚动更新
- 响应中会附带新的 sync-token / CTag,代表”此刻”的通讯录快照点。
- 客户端持久化这个新 token——它是下一次同步的起点,形成闭环。
关键设计对比
| 维度 | 首次同步 | 二次增量同步 |
|---|---|---|
| sync-token | = 0(全量) | = 上次保存值(仅变更) |
| 数据传输 | 所有 vCard | 新增/修改/删除的差量 |
| 上传方向 | 通常只读 | 先推本地 → 再拉远端 |
| 冲突处理 | 不涉及 | ETag 412 → GET 合并 → 重 PUT |
| Token 处理 | 同步后保存 | 每次滚动更新 |
完整循环
至此,同步模型已闭环:① 推本地 → ② 拉增量 → ③ 更新 token,此后每次打开通讯录都重复这三步,全程只传输变更。