教程
发布于: 2025年10月21日
作者: pbdecoder.online
什么是 CBOR?简洁二进制对象表示完整指南
CBOR(简洁二进制对象表示)完整指南 - 一种类似 Protocol Buffers 的二进制数据序列化格式,包含示例和对比分析
cbor
二进制格式
数据序列化
编码
json替代
什么是 CBOR?简洁二进制对象表示完整指南
概述
CBOR(Concise Binary Object Representation,简洁二进制对象表示)是在 RFC 7049 和 RFC 8949 中定义的二进制数据序列化格式。与 Protocol Buffers 类似,CBOR 设计为紧凑、快速,适用于受限环境。虽然 CBOR 数据本身是二进制的,人类无法直接阅读,但它有一种诊断表示法,可以将 CBOR 数据以人类可读的形式表示,用于调试和文档目的。
什么是 CBOR?
基本定义
CBOR 是一种以紧凑方式表示结构化数据的二进制编码格式。它的设计目标是:
- 简洁:比 JSON 和 XML 更小
- 快速:编码和解码速度快
- 自描述:不需要模式定义
- 可扩展:支持自定义数据类型
- 互操作性:跨平台和语言工作
- 二进制格式:人类无法直接阅读(除诊断表示法外)
核心特性
特性 | CBOR | JSON | Protocol Buffers |
---|---|---|---|
需要模式 | 否 | 否 | 是 |
人类可读 | 否(二进制) | 是 | 否 |
大小效率 | 高 | 低 | 非常高 |
解析速度 | 快 | 中等 | 非常快 |
数据类型 | 丰富(23种类型) | 有限(6种类型) | 丰富(自定义) |
CBOR 数据类型
CBOR 支持按主要类型组织的丰富数据类型集:
主要类型
// 主要类型 0:无符号整数(0-23, 24-255, 16位, 32位, 64位)
0, 1, 23, 24, 255, 65535, 4294967295
// 主要类型 1:负整数
-1, -24, -256, -65536
// 主要类型 2:字节字符串
h'48656c6c6f' // 十六进制表示的 "Hello"
// 主要类型 3:文本字符串
"Hello, World!"
// 主要类型 4:数组
[1, 2, 3, "hello", true]
// 主要类型 5:映射(对象)
{"name": "John", "age": 30, "active": true}
// 主要类型 6:语义标签
1(1609459200) // Unix 时间戳标签
// 主要类型 7:浮点数、简单值、中断
true, false, null, undefined, 3.14159
CBOR 诊断表示法
虽然 CBOR 数据以二进制格式存储且人类无法直接阅读,但 CBOR 规范定义了一种诊断表示法,提供 CBOR 数据的人类可读表示。这种表示法主要用于:
- 文档编写:在规范中解释 CBOR 数据结构
- 调试:在开发过程中理解 CBOR 消息的内容
- 测试:编写具有可读 CBOR 数据表示的测试用例
诊断表示法示例
// 二进制 CBOR 数据(十六进制):0x83010203
// 诊断表示法:[1, 2, 3]
// 二进制 CBOR 数据(十六进制):0xA26161016162820203
// 诊断表示法:{"a": 1, "b": [2, 3]}
// 带语义标签的二进制 CBOR 数据
// 诊断表示法:1(1609459200) // Unix 时间戳
// 诊断表示法:32("https://example.com") // URI 标签
重要说明
诊断表示法不是实际的 CBOR 格式 - 它只是表示二进制 CBOR 数据内容的人类可读方式。在应用程序中使用 CBOR 时,您始终处理的是紧凑的二进制表示。
CBOR 与其他格式对比
大小对比示例
让我们比较不同格式中的相同数据:
// JSON(67 字节)
{
"name": "Alice",
"age": 25,
"active": true,
"scores": [95, 87, 92]
}
// CBOR(42 字节)- 诊断表示法(人类可读的表示形式)
{
"name": "Alice",
"age": 25,
"active": true,
"scores": [95, 87, 92]
}
// 实际的 CBOR 二进制数据(十六进制):
// A4646E616D65654C69636563616765186961637469766566F5667363...
// Protocol Buffers(约20字节,需要模式)
// 需要 .proto 定义文件
使用 CBOR
编码示例(JavaScript)
const cbor = require('cbor');
// 要编码的数据
const data = {
name: "Alice",
age: 25,
active: true,
scores: [95, 87, 92],
timestamp: new Date()
};
// 编码为 CBOR
const encoded = cbor.encode(data);
console.log('CBOR 字节数:', encoded.length);
console.log('CBOR 十六进制:', encoded.toString('hex'));
// 从 CBOR 解码
const decoded = cbor.decode(encoded);
console.log('解码结果:', decoded);
编码示例(Python)
import cbor2
import datetime
# 要编码的数据
data = {
'name': 'Alice',
'age': 25,
'active': True,
'scores': [95, 87, 92],
'timestamp': datetime.datetime.now()
}
# 编码为 CBOR
encoded = cbor2.dumps(data)
print(f'CBOR 字节数: {len(encoded)}')
print(f'CBOR 十六进制: {encoded.hex()}')
# 从 CBOR 解码
decoded = cbor2.loads(encoded)
print(f'解码结果: {decoded}')
流式处理示例
const cbor = require('cbor');
const fs = require('fs');
// 创建 CBOR 编码器流
const encoder = new cbor.Encoder();
const output = fs.createWriteStream('data.cbor');
encoder.pipe(output);
// 流式传输多个对象
encoder.write({id: 1, name: "Alice"});
encoder.write({id: 2, name: "Bob"});
encoder.write({id: 3, name: "Charlie"});
encoder.end();
// 使用解码器流读取
const decoder = new cbor.Decoder();
const input = fs.createReadStream('data.cbor');
input.pipe(decoder);
decoder.on('data', (obj) => {
console.log('解码对象:', obj);
});
CBOR 二进制格式结构
基本结构
CBOR 使用简单的编码方案,每个数据项都以初始字节开始:
初始字节 = 主要类型(3位)+ 附加信息(5位)
位: 7 6 5 | 4 3 2 1 0
------+----------
主要 | 附加
类型 | 信息
编码示例
// 正整数 42
// 主要类型 0,附加信息 24(后跟1字节)
0x18, 0x2A
// 文本字符串 "CBOR"
// 主要类型 3,长度 4
0x64, 0x43, 0x42, 0x4F, 0x52
// 数组 [1, 2, 3]
// 主要类型 4,长度 3,然后是元素
0x83, 0x01, 0x02, 0x03
// 映射 {"a": 1}
// 主要类型 5,长度 1,然后是键值对
0xA1, 0x61, 0x61, 0x01
CBOR 高级特性
语义标签
CBOR 支持特殊数据类型的语义标签:
// 常见语义标签
const taggedData = {
// 标签 0:标准日期/时间字符串
datetime: cbor.Tagged(0, "2023-12-25T10:30:00Z"),
// 标签 1:基于纪元的日期/时间
timestamp: cbor.Tagged(1, 1703505000),
// 标签 2:正大数
bigint: cbor.Tagged(2, Buffer.from([0x01, 0x00, 0x00, 0x00, 0x00])),
// 标签 21:期望 Base64url 编码
base64url: cbor.Tagged(21, "SGVsbG8gV29ybGQ"),
// 标签 32:URI
uri: cbor.Tagged(32, "https://example.com")
};
不定长项目
CBOR 支持不定长数组和映射的流式处理:
// 不定长数组
const indefiniteArray = cbor.encode([
cbor.BREAK, // 不定长的特殊标记
1, 2, 3, 4, 5
]);
// 不定长映射
const indefiniteMap = cbor.encode(new Map([
[cbor.BREAK, null], // 不定长标记
["key1", "value1"],
["key2", "value2"]
]));
使用场景和应用
物联网和受限设备
// 传感器数据传输
const sensorData = {
deviceId: "sensor-001",
temperature: 23.5,
humidity: 65.2,
battery: 87,
timestamp: Date.now()
};
// CBOR 因其小尺寸而非常适合物联网
const cborData = cbor.encode(sensorData);
// 通过 LoRaWAN、NB-IoT 等传输
Web API
// Express.js 的 CBOR 中间件
app.use('/api/cbor', (req, res, next) => {
if (req.headers['content-type'] === 'application/cbor') {
let body = Buffer.alloc(0);
req.on('data', chunk => {
body = Buffer.concat([body, chunk]);
});
req.on('end', () => {
req.body = cbor.decode(body);
next();
});
} else {
next();
}
});
// API 端点
app.post('/api/cbor/data', (req, res) => {
// 处理 CBOR 数据
const result = processData(req.body);
// 用 CBOR 响应
res.setHeader('Content-Type', 'application/cbor');
res.send(cbor.encode(result));
});
配置文件
// config.cbor - 二进制配置
const config = {
server: {
host: "localhost",
port: 8080,
ssl: true
},
database: {
url: "mongodb://localhost:27017",
options: {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000
}
},
features: {
authentication: true,
logging: true,
metrics: false
}
};
// 保存为 CBOR
fs.writeFileSync('config.cbor', cbor.encode(config));
// 加载 CBOR 配置
const loadedConfig = cbor.decode(fs.readFileSync('config.cbor'));
性能考虑
编码性能
const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;
const testData = {
users: Array.from({length: 1000}, (_, i) => ({
id: i,
name: `用户 ${i}`,
email: `user${i}@example.com`,
active: i % 2 === 0,
scores: [Math.random() * 100, Math.random() * 100]
}))
};
suite
.add('JSON.stringify', () => {
JSON.stringify(testData);
})
.add('CBOR.encode', () => {
cbor.encode(testData);
})
.on('complete', function() {
console.log('最快的是 ' + this.filter('fastest').map('name'));
})
.run();
内存使用
// 大数据集的内存高效流式处理
const stream = require('stream');
class CBORProcessor extends stream.Transform {
constructor() {
super({ objectMode: true });
}
_transform(chunk, encoding, callback) {
try {
// 处理每个 CBOR 对象
const processed = this.processObject(chunk);
this.push(cbor.encode(processed));
callback();
} catch (error) {
callback(error);
}
}
processObject(obj) {
// 您的处理逻辑
return obj;
}
}
最佳实践
1. 选择合适的数据类型
// 好的做法:使用合适的数值类型
const data = {
count: 42, // 小整数
price: 19.99, // 浮点数
id: BigInt(123456789012345) // 大整数
};
// 避免:所有内容都用字符串
const badData = {
count: "42", // 应该是数字
price: "19.99", // 应该是数字
id: "123456789012345" // 可以是 BigInt
};
2. 使用语义标签
// 好的做法:为特殊类型使用语义标签
const eventData = {
eventId: "evt-123",
timestamp: cbor.Tagged(1, Math.floor(Date.now() / 1000)),
location: cbor.Tagged(32, "https://maps.example.com/location/123"),
metadata: cbor.Tagged(21, base64UrlEncode(metadataBuffer))
};
3. 优雅地处理错误
function safeCBORDecode(buffer) {
try {
return cbor.decode(buffer);
} catch (error) {
if (error.message.includes('Unexpected end of CBOR data')) {
console.error('接收到不完整的 CBOR 数据');
return null;
}
throw error;
}
}
常见陷阱
1. 不定长度混淆
// 错误:混合定长和不定长
const wrongArray = [cbor.BREAK, 1, 2, 3]; // 不要这样做
// 正确:适当的不定长数组
const rightArray = cbor.encodeCanonical([1, 2, 3], {
indefinite: true
});
2. 标签误用
// 错误:为数据类型使用错误的标签
const wrongDate = cbor.Tagged(2, "2023-12-25"); // 标签 2 用于大数
// 正确:日期的正确标签
const rightDate = cbor.Tagged(0, "2023-12-25T00:00:00Z");
总结
CBOR 是以下应用的绝佳选择:
- 需要紧凑二进制编码而不需要模式要求的应用
- 需要丰富数据类型支持超越 JSON 限制的应用
- 在受限环境中需要快速解析的应用
- 需要自描述格式进行灵活数据交换的应用
- 需要大数据集流式处理能力的应用
虽然 Protocol Buffers 对于具有稳定模式的高性能应用可能更高效,但 CBOR 在效率、灵活性和易用性之间提供了很好的平衡,使其非常适合物联网、Web API 和配置文件。
延伸阅读
- RFC 8949: 简洁二进制对象表示 (CBOR)
- CBOR.io - 官方网站
- CBOR Playground - 在线 CBOR 编码器/解码器
- IANA CBOR 标签注册表