探索 Go 中字节解析 API

栏目: Go · 发布时间: 7年前

内容简介:很多年前,我开始研究 Linux 的 Netlink 进程间通信接口。Netlink 被用于从 Linux 内核检索信息,并且为了跨越内核边界,信息通常被打包到 Netlink 的属性中。经过一些实验,我为 Go 创建了自己的随着时间的推移,包中的 API 已经有很大的改变了。特别是 Netlink 属性总是处理起来相当复杂。今天,我们将探索一些我为处理 Netlink 属性所创建的字节解析 API。这里描述的技术应该也能广泛应用于许多其他的 Go 库和应用程序中!Netlink 属性被以类型 / 长度 /

很多年前,我开始研究 Linux 的 Netlink 进程间通信接口。Netlink 被用于从 Linux 内核检索信息,并且为了跨越内核边界,信息通常被打包到 Netlink 的属性中。经过一些实验,我为 Go 创建了自己的 netlink 包 。

随着时间的推移,包中的 API 已经有很大的改变了。特别是 Netlink 属性总是处理起来相当复杂。今天,我们将探索一些我为处理 Netlink 属性所创建的字节解析 API。这里描述的技术应该也能广泛应用于许多其他的 Go 库和应用程序中!

Netlink 属性简介

Netlink 属性被以类型 / 长度 / 值或 TLV 格式打包,与许多二进制网络协议情况一样。这种格式具有很好的扩展性,因为许多属性可以在单个字节切片中被打包成自发自收的形式。

属性中的值可以包含:

  • 无符号 8/16/32/64 位整形
  • 以 null 结尾的 C 字符串
  • 任意 C 结构字节
  • 嵌套 Netlink 属性
  • Netlink 属性数组

为了我们的目标,我们可以在 Go 中像下面这样定义一个 Netlink 属性:

type Attribute struct {
	// The type of this Attribute, typically matched to a constant.
	Type uint16

	// Length omitted; Data will be a byte slice of the appropriate length.

	// An arbitrary payload which is specified by Type.
	Data []byte
}

今天,我们将略过低级的字节解析逻辑而有利于讨论各种高级 API,但是你能从 我关于 Netlink 系列博客 中学习到更多的关于处理 Netlink 属性的知识。

字节解析 API 第一版

单个字节切片可以包含许多 Netlink 属性。让我们来定义一个初始的解析函数,它接收一个字节切片的输入并且返回一个属性切片。

// UnmarshalAttributes unpacks a slice of Attributes from a single byte slice.
func UnmarshalAttributes(b []byte) ([]Attribute, error) {
	// ...
}

举个例子,假设我们想从属性切片中解包一个 uint16 和 string 值。你可以放心地忽略 parseUint16 和 parseString ;它们将处理一些 Netlink 属性数据中棘手的部分。

为了解包属性数据,我们可以在 Type 属性上使用循环和匹配:

attrs, err := netlink.UnmarshalAttributes(b)
if err != nil {
	return err
}

var (
	num uint16
	str string
)

for _, a := range attrs {
	switch a.Type {
	case 1:
		num = parseUint16(a.Data[0:2])
	case 2:
		str = parseString(a.Data)
	}
}

fmt.Printf("num: %d, str: %q", num, str)
// num: 1, str: "hello world"

这样可以正常工作,但是有一个问题:如果我们 uint16 值的字节切片比 2 字节多或者少,会出现什么情况呢?

// A panic waiting to happen!
num = parseUint16(a.Data[0:2])

如果它少于 2 字节,此代码将出现 panic,并且让你的应用程序挂掉。如果它超过 2 字节,我们就默默地忽略任何额外的数据(这个值实际上不是 uint16 !)。

添加验证和错误处理

我们稍微修改一下我们的解析函数。每一个都应该做一些内部验证,如果字节切片不满足我们的限制,我们可以返回一个 error。

attrs, err := netlink.UnmarshalAttributes(b)
if err != nil {
	return err
}

var (
	num uint16
	str string

	// Used to check for errors without shadowing num and str later.
	err error
)

for _, a := range attrs {
	// This works, but it's a bit verbose.
	// Be cautious of variable shadowing as well!
	switch a.Type {
	case 1:
		num, err = parseUint16(a.Data)
	case 2:
		str, err = parseString(a.Data)
	}
	if err != nil {
		return err
	}
}

fmt.Printf("num: %d, str: %q", num, str)
// num: 1, str: "hello world"

这样也是有效的,但你必须对你的错误检查策略保持谨慎,并且确保你不会意外地使用 := 赋值运算符屏蔽掉你尝试解包的其中一个变量。

我们可以进一步改进这种模式吗?

一个类似迭代器的解析 API

上述的策略正常运行了许多年,但在编写了一些 Netlink 交互包之后,我决定开始改进 API。

新的 API 使用类似迭代器的模式,其灵感来自于标准库中的 bufio.Scanner API。Go 的博客 Errors are values 这篇文章同样为解释这个策略做了出色的工作。

netlink.AttributeDecoder 类型就是一个类似迭代器的解析 API。在使用了 netlink.NewAttributeDecoder 构造器之后,许多方法被暴露出来,其能够与内部属性切片进行交互:

Next
Type
Err

在尝试这个新的 API 时,让我们重温前面的例子:

ad, err := netlink.NewAttributeDecoder(b)
if err != nil {
	return err
}

var (
	num uint16
	str string
)

// Continue advancing the internal pointer until done or error.
for ad.Next() {
	// Check the current attribute's type and extract it as appropriate.
	switch ad.Type() {
	case 1:
		// If data isn't a uint16, an error will be captured internally.
		num = ad.Uint16()
	case 2:
		str = ad.String()
	}
}

// Check for the first error encountered during iteration.
if err := ad.Err(); err != nil {
	return err
}

fmt.Printf("num: %d, str: %q", num, str)
// num: 1, str: "hello world"

有很多种方法可以用于迭代器期间提取的数据,例如 Uint8/16/32/64 、 Bytes 、 string 和所有的最有用的方法,包括: Do 。

Do 是一种特殊用途的方法,允许解码器处理任意数据,如 C 结构、嵌套的 Netlink 属性、Netlink 数组。它能接受一个闭包,并将解码器所指向的当前数据传递给闭包。

为了处理嵌套 Netlink 属性,创建另外的包含一个 Do 闭包的 AttributrEncoder :

ad.Do(func(b []byte) error) {
	nad, err := netlink.NewAttributeDecoder(b)
	if err != nil {
		return err
	}

	if err := handleNested(nad); err != nil {
		return err
	}

	// Make sure to propagate internal errors to the top-level decoder!
	return nad.Err()
})

为了保持小的闭包体,可以定义辅助函数来解析 Netlink 属性中的任意类型:

// parseFoo returns a function compatible with Do.
func parseFoo(f *Foo) func(b []byte) error {
    return func(b []byte) error {
		// Some parsing logic...
		foo, err := unpackFoo(b)
		if err != nil {
			return err
		}

		// Store foo in f by dereferencing the pointer.
		*f = foo
		return nil
	}
}

现在,这个辅助函数可以直接用于 Do :

var f Foo
ad.Do(parseFoo(&f))

此 API 为它的调用者提供了极大的灵活性。所有的错误传播都在内部处理,并通过从顶级解码器调用 Err 方法将错误冒泡到调用者。

结论

虽然花了一些时间和实验,但是我对 netlink.AttributeDecoder 中类似迭代器字节解析 API 感到非常满意。它非常适合于我的需求,感谢 Terin Stock,我们还添加了一个 对称编码器 API ,其灵感来自于解码器 API 的成功!

如果你正在开发一个你并不满意的包 API,标准库是寻找灵感的好地方!我也强烈建议与各种 Go 帮助社区 取得联系,因为有很多人非常愿意提供出色的建议和批评!

如果你有任何问题,请随时和我联系!我在 Gophers Slack 、 Github 和 Twitter 的称号是 mdlayher。

链接


以上所述就是小编给大家介绍的《探索 Go 中字节解析 API》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

马云如是说

马云如是说

朱甫 / 中国经济出版社 / 2008-1-1 / 39.80元

任何一个企业家的成功,都需要一种特立独行的精神。换尔言之,他一定是不断地否定别人的反对意见,坚持自己独特的观点,才能够真正走向大成功。在中国企业家群像里,马云就是这样一个特立独行的人。 目录 *永不放弃——马云论创业精神 *天下没有难做的生意——马云论经营理念 *B2B时代——马云论电子商务 *网络只是一个工具——马云论互联网与网络公司 *太多钱会坏事——马云论......一起来看看 《马云如是说》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具