msgpack::unpacker
msgpack::unpacker 从包含 msgpack 格式数据的缓冲区中解包 msgpack::object。 msgpack 提供了两个解包功能。当客户端控制缓冲区时使用一个,当客户端不想控制缓冲区时使用另一个。这两种方法都使用 msgpack::object_handle 来访问未打包的数据。
Accessing unpacked data(访问解压数据)
当你调用解包函数时,你需要传递 msgpack::object_handle 作为参数。这是客户端的 msgpack::object_handle 接口:
class object_handle {
public:
object_handle();
const object& get() const;
msgpack::unique_ptr<msgpack::zone>& zone();
};
For example:
void some_function(const char* data, std::size_t len) {
// Default construct msgpack::object_handle
msgpack::object_handle result;
// Pass the msgpack::object_handle
unpack(result, data, len);
// Get msgpack::object from msgpack::object_handle (shallow copy)
msgpack::object obj(result.get());
// Get msgpack::zone from msgpack::object_handle (move)
msgpack::unique_ptr<msgpack::zone> z(result.zone());
...
msgpack::object 具有引用语义。所以 msgpack::object 的拷贝构造和拷贝赋值是浅拷贝。 msgpack::object_handle 管理 msgpack::object 的生命周期。 msgpack::object 是在 msgpack::zone 的内部缓冲区上构造的。当 msgpack::object_handle 被销毁时,被销毁的 msgpack::object_handle 中的 msgpack::object 将成为一个悬空引用。如果要保持 msgpack::object 的生命周期,则需要保持 msgpack::object_handle 的生命周期如下:
msgpack::object_handle some_function(const char* data, std::size_t len) {
// Default construct msgpack::object_handle
msgpack::object_handle result;
// Pass the msgpack::object_handle
unpack(result, data, len);
return result;
}
void foo() {
...
msgpack::object_handle handle = some_function(data, len);
// Use object
msgpack::object obj = handle.get();
您还可以使用 zone() 从 msgpack::object_handle 获取 msgpack::zone。那是针对高级用例的。在典型的用例中,使用 msgpack::object_handle 并从中获取 msgpack::object 就足够了。
当您想精确控制一个缓冲存储器时,引用的参数很有用。解包后,如果解包 msgpack::object 引用缓冲区,则 referenced 返回 true,否则 referenced 为 false。当您调用 unpack() 时,缓冲区是您传递的参数。当你调用 unpacker::next() 时,缓冲区是 unpacker 的内部缓冲区。
Client controls a buffer
如果 msgpack 用户、客户端已经准备好包含 msgpack 格式数据的缓冲区,请使用以下函数:
// simple (since C++11)
object_handle unpack(
const char* data,
std::size_t len,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// get referenced information (since C++11)
object_handle unpack(
const char* data,
std::size_t len,
bool& referenced,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// set and get offset information (since C++11)
object_handle unpack(
const char* data,
std::size_t len,
std::size_t& off,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// get referenced information
// set and get offset information
// (since C++11)
object_handle unpack(
const char* data,
std::size_t len,
std::size_t& off,
bool& referenced,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// simple
void unpack(
object_handle& result,
const char* data,
std::size_t len,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// get referenced information
void unpack(
object_handle& result,
const char* data,
std::size_t len,
bool& referenced,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// set and get offset information
void unpack(
object_handle& result,
const char* data,
std::size_t len,
std::size_t& off,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// get referenced information
// set and get offset information
void unpack(
object_handle& result,
const char* data,
std::size_t len,
std::size_t& off,
bool& referenced,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
If the buffer cointains one msgpack data, just pass the buffer address and length:
object_handle result;
const char* data = ...
std::size_t len = ...
unpack(result, data, len);
result = unpack(data, len); // since C++11
如果缓冲区包含多个 msgpack 数据,请使用以下函数:
object_handle result;
const char* data = ...
std::size_t len = ...
std::size_t off = ...
unpack(result, data, len, off);
result = unpack(data, len, off); // since C++11
如果知道msgpack数据的个数,可以这样写:
// Caller knows there are three msgpack data in the buffer.
void some_function(const char* buffer, std::size_t len) {
msgpack::object_handle result1;
msgpack::object_handle result2;
msgpack::object_handle result3;
std::size_t off = 0;
// Unpack the first msgpack data.
// off is updated when function is returned.
unpack(result1, buffer, len, off);
// Unpack the second msgpack data.
// off is updated when function is returned.
unpack(result2, buffer, len, off);
// Unpack the third msgpack data.
// off is updated when function is returned.
unpack(result3, buffer, len, off);
assert(len == off);
...
如果不知道msgpack数据的个数但是数据完整,可以这样写:
void some_function(const char* buffer, std::size_t len) {
std::size_t off = 0;
while (off != len) {
msgpack::object_handle result;
unpack(result, buffer, len, off);
msgpack::object obj(result.get());
// Use obj
}
}
那些 msgpack::unpack 函数可能会抛出 msgpack::unpack_error。
- 如果缓冲区的 msgpack 数据格式无效,则会收到带有“parse error”消息的 msgpack::unpack_error。
- 如果来自 off(offset) 位置的缓冲区包含不完整的 msgpack 数据,则会收到带有“insufficient bytes”消息的 msgpack::unpack_error。
为了使用这些解包功能,客户端需要准备好几个完整的msgpack数据。如果客户端可能得到不完整的 msgpack 数据,则使用 msgpack::unpacker。
Limit size of elements
您可以将 unpack_limit 传递给 unpack()。
这是 unpack_limit 的定义:
class unpack_limit {
public:
unpack_limit(
std::size_t array = 0xffffffff,
std::size_t map = 0xffffffff,
std::size_t str = 0xffffffff,
std::size_t bin = 0xffffffff,
std::size_t ext = 0xffffffff,
std::size_t depth = 0xffffffff);
std::size_t array() const;
std::size_t map() const;
std::size_t str() const;
std::size_t bin() const;
std::size_t ext() const;
std::size_t depth() const;
};
默认限制为 0xffffffff。它们是 msgpack 格式的最大值。严格来说,深度的限制不是由 msgpack 格式定义的。
如果元素数量超过 unpack_limit,则 unpack 函数会抛出以下异常:
struct size_overflow : public unpack_error {
};
struct array_size_overflow : public size_overflow {
};
struct map_size_overflow : public size_overflow {
};
struct str_size_overflow : public size_overflow {
};
struct bin_size_overflow : public size_overflow {
};
struct ext_size_overflow : public size_overflow {
};
struct depth_size_overflow : public size_overflow {
};
您可以保护您的应用程序免受格式错误的 msgpack 格式数据的影响。
See https://github.com/msgpack/msgpack-c/blob/master/test/limit.cpp
Memory management
默认情况下,客户端缓冲区中的所有 msgpack 数据都将复制到 msgpack::zone。您可以在解包后释放包含 msgpack 格式数据的缓冲区。
您可以使用 unpack_reference_func 自定义行为。该函数在 STR、BIN 和 EXT 解包时调用。如果函数返回true,则对应于STR、BIN和EXT的msgpack::object指向客户端缓冲区。所以客户端需要保持缓冲区的生命周期至少直到 msgpack::object 被销毁。如果函数返回 false,则 msgpack::object 指向从客户端缓冲区复制的 msgpack::zone 上的 STR、BIN 和 EXT 有效负载。
unpack_reference_func 定义如下:
typedef bool (*unpack_reference_func)(type::object_type type, std::size_t length, void* user_data);
type::object_type is defined as follows:
namespace type {
enum object_type {
NIL = MSGPACK_OBJECT_NIL,
BOOLEAN = MSGPACK_OBJECT_BOOLEAN,
POSITIVE_INTEGER = MSGPACK_OBJECT_POSITIVE_INTEGER,
NEGATIVE_INTEGER = MSGPACK_OBJECT_NEGATIVE_INTEGER,
DOUBLE = MSGPACK_OBJECT_DOUBLE,
STR = MSGPACK_OBJECT_STR,
BIN = MSGPACK_OBJECT_BIN,
ARRAY = MSGPACK_OBJECT_ARRAY,
MAP = MSGPACK_OBJECT_MAP,
EXT = MSGPACK_OBJECT_EXT
};
}
当 msgpack 从缓冲区解包对象时,unpack_reference_func 被调用,‘type’ 作为解包类型,‘length’ 作为有效负载大小,以及’user_data’ 这三个作为参数传递给 msgpack::unpack()。您可以将 STR、BIN 和 EXT 作为类型参数。其他类型永远不会出现,因为具有其他类型的 msgpack::object 是通过转换创建的。相反,STR、BIN 和 EXT 的有效载荷可以直接从 msgpack::object 使用。
“size”表示有效负载大小。 STR 和 BIN 的有效载荷大小,它是实际数据大小。对于 EXT,它是 EXT 的类型和数据大小。
这是 unpack_reference_func 的示例实现。
bool my_reference_func(type::object_type type, std::size_t length, void* user_data) {
switch (type) {
case: type::STR:
// Small strings are copied.
if (length < 32) return false;
break;
case: type::BIN:
// BIN is always copied.
return false;
case: type::EXT:
// fixext's are copied.
switch (length) {
case 1+1:
case 2+1:
case 4+1:
case 8+1:
case 16+1:
return false;
}
break;
default:
assert(false);
}
// otherwise referenced.
return true;
}
当字符串大小等于或大于 32bytes 时,内存布局如下:
您可以使用以下函数获取解压后的 msgpack::object 是否引用缓冲区:
// get referenced information
void unpack(
object_handle& result,
const char* data,
std::size_t len,
bool& referenced,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
// get referenced information
// set and get offset information
void unpack(
object_handle& result,
const char* data,
std::size_t len,
std::size_t& off,
bool& referenced,
unpack_reference_func f = nullptr,
void* user_data = nullptr,
unpack_limit const& limit = unpack_limit());
如果 referenced 为真,则 msgpack::object 引用作为参数 ‘data’ 传递的缓冲区,否则 msgpack::object 不引用缓冲区。
msgpack controls a buffer
msgpack 提供了一个名为 msgpack::unpacker 的缓冲区管理功能。 msgpack::unpacker 适用于以下动机:
- msgpack 数据被截断,客户端不知道什么时候完成。这是您开发流式应用程序时的典型情况。
- 您希望在不仔细管理内存的情况下最大限度地减少复制操作。
这是 msgpack::unpacker 的基本(不是全部)接口:
#ifndef MSGPACK_UNPACKER_INIT_BUFFER_SIZE
#define MSGPACK_UNPACKER_INIT_BUFFER_SIZE (64*1024)
#endif
#ifndef MSGPACK_UNPACKER_RESERVE_SIZE
#define MSGPACK_UNPACKER_RESERVE_SIZE (32*1024)
#endif
class unpacker {
public:
unpacker(unpack_reference_func f = &unpacker::default_reference_func,
void* user_data = nullptr,
std::size_t init_buffer_size = MSGPACK_UNPACKER_INIT_BUFFER_SIZE,
unpack_limit const& limit = unpack_limit());
void reserve_buffer(std::size_t size = MSGPACK_UNPACKER_RESERVE_SIZE);
char* buffer();
void buffer_consumed(std::size_t size);
bool next(object_handle& result);
};
这是使用 msgpack::unpacker 的基本模式:
// The size may decided by receive performance, transmit layer's protocol and so on.
std::size_t const try_read_size = 100;
msgpack::unpacker unp;
// Message receive loop
while (/* block until input becomes readable */) {
unp.reserve_buffer(try_read_size);
// unp has at least try_read_size buffer on this point.
// input is a kind of I/O library object.
// read message to msgpack::unpacker's internal buffer directly.
std::size_t actual_read_size = input.readsome(unp.buffer(), try_read_size);
// tell msgpack::unpacker actual consumed size.
unp.buffer_consumed(actual_read_size);
msgpack::object_handle result;
// Message pack data loop
while(unp.next(result)) {
msgpack::object obj(result.get());
// Use obj
}
// All complete msgpack message is proccessed at this point,
// then continue to read addtional message.
}
如果处理了一个完整的 msgpack 消息,则 msgpack::unpacker::next() 返回 true。如果 msgpack 消息正确但不充分,则返回 false。但是,解析过程继续进行,上下文信息保存在 msgpack::unpacker 中。它有助于平衡解析的负载。
当 msgpack 消息包含二进制数据、字符串数据或 ext 数据时,它们不会被复制,而是默认从 msgpack::object 引用。请参阅以下实现:
inline bool unpacker::default_reference_func(type::object_type type, uint64_t len, void*)
{
return true;
}
您还可以自定义 unpack_reference_func。即使你使用引用,你也不需要控制缓冲区的生命周期。缓冲区的生命周期由 msgpack 使用 msgpack::zone 的 finalizer_array 和 msgpack::unpacker 的引用计数机制来控制。
因此,在大多数情况下,默认行为就足够了。如果您想在接收 msgpack 数据模式可预测时控制内存消耗的峰值,自定义 unpack_reference_func 可能会很有用。
您可以使用以下函数从 msgpack::unpacker::next() 获取参考信息:
bool next(object_handle& result, bool& referenced);
但是,大多数情况下您不需要使用该版本的 next(),因为引用的内存由 unpacker 管理。
unpacker behavior
如果您想详细了解内部机制,请参阅内部机制,幻灯片是在未实现 ext 支持和 unpack_reference_func 时编写的。
Q.E.D.